001    /*
002     *   Copyright (C) Christian Schulte, 2005-206
003     *   All rights reserved.
004     *
005     *   Redistribution and use in source and binary forms, with or without
006     *   modification, are permitted provided that the following conditions
007     *   are met:
008     *
009     *     o Redistributions of source code must retain the above copyright
010     *       notice, this list of conditions and the following disclaimer.
011     *
012     *     o Redistributions in binary form must reproduce the above copyright
013     *       notice, this list of conditions and the following disclaimer in
014     *       the documentation and/or other materials provided with the
015     *       distribution.
016     *
017     *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018     *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019     *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020     *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021     *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022     *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023     *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024     *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025     *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026     *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027     *
028     *   $JOMC: ModelContext.java 4256 2012-02-13 06:32:10Z schulte2005 $
029     *
030     */
031    package org.jomc.modlet;
032    
033    import java.io.IOException;
034    import java.lang.reflect.Constructor;
035    import java.lang.reflect.InvocationTargetException;
036    import java.net.URI;
037    import java.net.URL;
038    import java.text.MessageFormat;
039    import java.util.Collections;
040    import java.util.Enumeration;
041    import java.util.HashMap;
042    import java.util.LinkedList;
043    import java.util.List;
044    import java.util.Locale;
045    import java.util.Map;
046    import java.util.ResourceBundle;
047    import java.util.Set;
048    import java.util.logging.Level;
049    import javax.xml.bind.JAXBContext;
050    import javax.xml.bind.JAXBException;
051    import javax.xml.bind.Marshaller;
052    import javax.xml.bind.Unmarshaller;
053    import javax.xml.bind.util.JAXBSource;
054    import javax.xml.transform.Source;
055    import javax.xml.validation.Validator;
056    import org.w3c.dom.ls.LSResourceResolver;
057    import org.xml.sax.EntityResolver;
058    import org.xml.sax.SAXException;
059    
060    /**
061     * Model context interface.
062     * <p><b>Use Cases:</b><br/><ul>
063     * <li>{@link #createContext(java.lang.String) }</li>
064     * <li>{@link #createContext(java.net.URI) }</li>
065     * <li>{@link #createEntityResolver(java.lang.String) }</li>
066     * <li>{@link #createEntityResolver(java.net.URI) }</li>
067     * <li>{@link #createMarshaller(java.lang.String) }</li>
068     * <li>{@link #createMarshaller(java.net.URI) }</li>
069     * <li>{@link #createResourceResolver(java.lang.String) }</li>
070     * <li>{@link #createResourceResolver(java.net.URI) }</li>
071     * <li>{@link #createSchema(java.lang.String) }</li>
072     * <li>{@link #createSchema(java.net.URI) }</li>
073     * <li>{@link #createUnmarshaller(java.lang.String) }</li>
074     * <li>{@link #createUnmarshaller(java.net.URI) }</li>
075     * <li>{@link #findModel(java.lang.String) }</li>
076     * <li>{@link #findModel(org.jomc.modlet.Model) }</li>
077     * <li>{@link #processModel(org.jomc.modlet.Model) }</li>
078     * <li>{@link #validateModel(org.jomc.modlet.Model) }</li>
079     * <li>{@link #validateModel(java.lang.String, javax.xml.transform.Source) }</li>
080     * </ul>
081     *
082     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
083     * @version $JOMC: ModelContext.java 4256 2012-02-13 06:32:10Z schulte2005 $
084     *
085     * @see ModelContextFactory
086     */
087    public abstract class ModelContext
088    {
089    
090        /** Listener interface. */
091        public abstract static class Listener
092        {
093    
094            /** Creates a new {@code Listener} instance. */
095            public Listener()
096            {
097                super();
098            }
099    
100            /**
101             * Gets called on logging.
102             *
103             * @param level The level of the event.
104             * @param message The message of the event or {@code null}.
105             * @param t The throwable of the event or {@code null}.
106             *
107             * @throws NullPointerException if {@code level} is {@code null}.
108             */
109            public void onLog( final Level level, final String message, final Throwable t )
110            {
111                if ( level == null )
112                {
113                    throw new NullPointerException( "level" );
114                }
115            }
116    
117        }
118    
119        /**
120         * Default {@code http://jomc.org/modlet} namespace schema system id.
121         * @see #getDefaultModletSchemaSystemId()
122         */
123        private static final String DEFAULT_MODLET_SCHEMA_SYSTEM_ID =
124            "http://jomc.sourceforge.net/modlet/jomc-modlet-1.2.xsd";
125    
126        /**
127         * Log level events are logged at by default.
128         * @see #getDefaultLogLevel()
129         */
130        private static final Level DEFAULT_LOG_LEVEL = Level.WARNING;
131    
132        /** Default log level. */
133        private static volatile Level defaultLogLevel;
134    
135        /** Default {@code http://jomc.org/model/modlet} namespace schema system id. */
136        private static volatile String defaultModletSchemaSystemId;
137    
138        /** Class name of the {@code ModelContext} implementation. */
139        @Deprecated
140        private static volatile String modelContextClassName;
141    
142        /** The attributes of the instance. */
143        private final Map<String, Object> attributes = new HashMap<String, Object>();
144    
145        /** The class loader of the instance. */
146        private ClassLoader classLoader;
147    
148        /**
149         * Flag indicating the {@code classLoader} field is initialized.
150         * @since 1.2
151         */
152        private boolean classLoaderSet;
153    
154        /** The listeners of the instance. */
155        private List<Listener> listeners;
156    
157        /** Log level of the instance. */
158        private Level logLevel;
159    
160        /** The {@code Modlets} of the instance. */
161        private Modlets modlets;
162    
163        /** Modlet namespace schema system id of the instance. */
164        private String modletSchemaSystemId;
165    
166        /**
167         * Creates a new {@code ModelContext} instance.
168         * @since 1.2
169         */
170        public ModelContext()
171        {
172            super();
173            this.classLoader = null;
174            this.classLoaderSet = false;
175        }
176    
177        /**
178         * Creates a new {@code ModelContext} instance taking a class loader.
179         *
180         * @param classLoader The class loader of the context.
181         *
182         * @see #getClassLoader()
183         */
184        public ModelContext( final ClassLoader classLoader )
185        {
186            super();
187            this.classLoader = classLoader;
188            this.classLoaderSet = true;
189        }
190    
191        /**
192         * Gets a set holding the names of all attributes of the context.
193         *
194         * @return An unmodifiable set holding the names of all attributes of the context.
195         *
196         * @see #clearAttribute(java.lang.String)
197         * @see #getAttribute(java.lang.String)
198         * @see #getAttribute(java.lang.String, java.lang.Object)
199         * @see #setAttribute(java.lang.String, java.lang.Object)
200         * @since 1.2
201         */
202        public Set<String> getAttributeNames()
203        {
204            return Collections.unmodifiableSet( this.attributes.keySet() );
205        }
206    
207        /**
208         * Gets an attribute of the context.
209         *
210         * @param name The name of the attribute to get.
211         *
212         * @return The value of the attribute with name {@code name}; {@code null} if no attribute matching {@code name} is
213         * found.
214         *
215         * @throws NullPointerException if {@code name} is {@code null}.
216         *
217         * @see #getAttribute(java.lang.String, java.lang.Object)
218         * @see #setAttribute(java.lang.String, java.lang.Object)
219         * @see #clearAttribute(java.lang.String)
220         */
221        public Object getAttribute( final String name )
222        {
223            if ( name == null )
224            {
225                throw new NullPointerException( "name" );
226            }
227    
228            return this.attributes.get( name );
229        }
230    
231        /**
232         * Gets an attribute of the context.
233         *
234         * @param name The name of the attribute to get.
235         * @param def The value to return if no attribute matching {@code name} is found.
236         *
237         * @return The value of the attribute with name {@code name}; {@code def} if no such attribute is found.
238         *
239         * @throws NullPointerException if {@code name} is {@code null}.
240         *
241         * @see #getAttribute(java.lang.String)
242         * @see #setAttribute(java.lang.String, java.lang.Object)
243         * @see #clearAttribute(java.lang.String)
244         */
245        public Object getAttribute( final String name, final Object def )
246        {
247            if ( name == null )
248            {
249                throw new NullPointerException( "name" );
250            }
251    
252            Object value = this.getAttribute( name );
253    
254            if ( value == null )
255            {
256                value = def;
257            }
258    
259            return value;
260        }
261    
262        /**
263         * Sets an attribute in the context.
264         *
265         * @param name The name of the attribute to set.
266         * @param value The value of the attribute to set.
267         *
268         * @return The previous value of the attribute with name {@code name}; {@code null} if no such value is found.
269         *
270         * @throws NullPointerException if {@code name} or {@code value} is {@code null}.
271         *
272         * @see #getAttribute(java.lang.String)
273         * @see #getAttribute(java.lang.String, java.lang.Object)
274         * @see #clearAttribute(java.lang.String)
275         */
276        public Object setAttribute( final String name, final Object value )
277        {
278            if ( name == null )
279            {
280                throw new NullPointerException( "name" );
281            }
282            if ( value == null )
283            {
284                throw new NullPointerException( "value" );
285            }
286    
287            return this.attributes.put( name, value );
288        }
289    
290        /**
291         * Removes an attribute from the context.
292         *
293         * @param name The name of the attribute to remove.
294         *
295         * @throws NullPointerException if {@code name} is {@code null}.
296         *
297         * @see #getAttribute(java.lang.String)
298         * @see #getAttribute(java.lang.String, java.lang.Object)
299         * @see #setAttribute(java.lang.String, java.lang.Object)
300         */
301        public void clearAttribute( final String name )
302        {
303            if ( name == null )
304            {
305                throw new NullPointerException( "name" );
306            }
307    
308            this.attributes.remove( name );
309        }
310    
311        /**
312         * Gets the class loader of the context.
313         *
314         * @return The class loader of the context or {@code null}, indicating the bootstrap class loader.
315         *
316         * @see #findClass(java.lang.String)
317         * @see #findResource(java.lang.String)
318         * @see #findResources(java.lang.String)
319         */
320        public ClassLoader getClassLoader()
321        {
322            if ( !this.classLoaderSet )
323            {
324                this.classLoader = this.getClass().getClassLoader();
325                this.classLoaderSet = true;
326            }
327    
328            return this.classLoader;
329        }
330    
331        /**
332         * Gets the listeners of the context.
333         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
334         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
335         * listeners property.</p>
336         *
337         * @return The list of listeners of the context.
338         *
339         * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
340         */
341        public List<Listener> getListeners()
342        {
343            if ( this.listeners == null )
344            {
345                this.listeners = new LinkedList<Listener>();
346            }
347    
348            return this.listeners;
349        }
350    
351        /**
352         * Gets the default {@code http://jomc.org/modlet} namespace schema system id.
353         * <p>The default {@code http://jomc.org/modlet} namespace schema system id is controlled by system property
354         * {@code org.jomc.modlet.ModelContext.defaultModletSchemaSystemId} holding a system id URI.
355         * If that property is not set, the {@code http://jomc.sourceforge.net/modlet/jomc-modlet-1.2.xsd} default is
356         * returned.</p>
357         *
358         * @return The default system id of the {@code http://jomc.org/modlet} namespace schema.
359         *
360         * @see #setDefaultModletSchemaSystemId(java.lang.String)
361         */
362        public static String getDefaultModletSchemaSystemId()
363        {
364            if ( defaultModletSchemaSystemId == null )
365            {
366                defaultModletSchemaSystemId = System.getProperty(
367                    "org.jomc.modlet.ModelContext.defaultModletSchemaSystemId", DEFAULT_MODLET_SCHEMA_SYSTEM_ID );
368    
369            }
370    
371            return defaultModletSchemaSystemId;
372        }
373    
374        /**
375         * Sets the default {@code http://jomc.org/modlet} namespace schema system id.
376         *
377         * @param value The new default {@code http://jomc.org/modlet} namespace schema system id or {@code null}.
378         *
379         * @see #getDefaultModletSchemaSystemId()
380         */
381        public static void setDefaultModletSchemaSystemId( final String value )
382        {
383            defaultModletSchemaSystemId = value;
384        }
385    
386        /**
387         * Gets the {@code http://jomc.org/modlet} namespace schema system id of the context.
388         *
389         * @return The {@code http://jomc.org/modlet} namespace schema system id of the context.
390         *
391         * @see #getDefaultModletSchemaSystemId()
392         * @see #setModletSchemaSystemId(java.lang.String)
393         */
394        public final String getModletSchemaSystemId()
395        {
396            if ( this.modletSchemaSystemId == null )
397            {
398                this.modletSchemaSystemId = getDefaultModletSchemaSystemId();
399    
400                if ( this.isLoggable( Level.CONFIG ) )
401                {
402                    this.log( Level.CONFIG,
403                              getMessage( "defaultModletSchemaSystemIdInfo", this.modletSchemaSystemId ), null );
404    
405                }
406            }
407    
408            return this.modletSchemaSystemId;
409        }
410    
411        /**
412         * Sets the {@code http://jomc.org/modlet} namespace schema system id of the context.
413         *
414         * @param value The new {@code http://jomc.org/modlet} namespace schema system id or {@code null}.
415         *
416         * @see #getModletSchemaSystemId()
417         */
418        public final void setModletSchemaSystemId( final String value )
419        {
420            final String oldModletSchemaSystemId = this.getModletSchemaSystemId();
421            this.modletSchemaSystemId = value;
422    
423            if ( this.modlets != null )
424            {
425                for ( int i = 0, s0 = this.modlets.getModlet().size(); i < s0; i++ )
426                {
427                    final Modlet m = this.modlets.getModlet().get( i );
428    
429                    if ( m.getSchemas() != null )
430                    {
431                        final Schema s = m.getSchemas().getSchemaBySystemId( oldModletSchemaSystemId );
432    
433                        if ( s != null )
434                        {
435                            s.setSystemId( value );
436                        }
437                    }
438                }
439            }
440        }
441    
442        /**
443         * Gets the default log level events are logged at.
444         * <p>The default log level is controlled by system property
445         * {@code org.jomc.modlet.ModelContext.defaultLogLevel} holding the log level to log events at by default.
446         * If that property is not set, the {@code WARNING} default is returned.</p>
447         *
448         * @return The log level events are logged at by default.
449         *
450         * @see #getLogLevel()
451         * @see Level#parse(java.lang.String)
452         */
453        public static Level getDefaultLogLevel()
454        {
455            if ( defaultLogLevel == null )
456            {
457                defaultLogLevel = Level.parse( System.getProperty(
458                    "org.jomc.modlet.ModelContext.defaultLogLevel", DEFAULT_LOG_LEVEL.getName() ) );
459    
460            }
461    
462            return defaultLogLevel;
463        }
464    
465        /**
466         * Sets the default log level events are logged at.
467         *
468         * @param value The new default level events are logged at or {@code null}.
469         *
470         * @see #getDefaultLogLevel()
471         */
472        public static void setDefaultLogLevel( final Level value )
473        {
474            defaultLogLevel = value;
475        }
476    
477        /**
478         * Gets the log level of the context.
479         *
480         * @return The log level of the context.
481         *
482         * @see #getDefaultLogLevel()
483         * @see #setLogLevel(java.util.logging.Level)
484         * @see #isLoggable(java.util.logging.Level)
485         */
486        public final Level getLogLevel()
487        {
488            if ( this.logLevel == null )
489            {
490                this.logLevel = getDefaultLogLevel();
491    
492                if ( this.isLoggable( Level.CONFIG ) )
493                {
494                    this.log( Level.CONFIG, getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ), null );
495                }
496            }
497    
498            return this.logLevel;
499        }
500    
501        /**
502         * Sets the log level of the context.
503         *
504         * @param value The new log level of the context or {@code null}.
505         *
506         * @see #getLogLevel()
507         * @see #isLoggable(java.util.logging.Level)
508         */
509        public final void setLogLevel( final Level value )
510        {
511            this.logLevel = value;
512        }
513    
514        /**
515         * Checks if a message at a given level is provided to the listeners of the context.
516         *
517         * @param level The level to test.
518         *
519         * @return {@code true}, if messages at {@code level} are provided to the listeners of the context; {@code false},
520         * if messages at {@code level} are not provided to the listeners of the context.
521         *
522         * @throws NullPointerException if {@code level} is {@code null}.
523         *
524         * @see #getLogLevel()
525         * @see #setLogLevel(java.util.logging.Level)
526         */
527        public boolean isLoggable( final Level level )
528        {
529            if ( level == null )
530            {
531                throw new NullPointerException( "level" );
532            }
533    
534            return level.intValue() >= this.getLogLevel().intValue();
535        }
536    
537        /**
538         * Notifies all listeners of the context.
539         *
540         * @param level The level of the event.
541         * @param message The message of the event or {@code null}.
542         * @param throwable The throwable of the event {@code null}.
543         *
544         * @throws NullPointerException if {@code level} is {@code null}.
545         *
546         * @see #getListeners()
547         * @see #isLoggable(java.util.logging.Level)
548         */
549        public void log( final Level level, final String message, final Throwable throwable )
550        {
551            if ( level == null )
552            {
553                throw new NullPointerException( "level" );
554            }
555    
556            if ( this.isLoggable( level ) )
557            {
558                for ( Listener l : this.getListeners() )
559                {
560                    l.onLog( level, message, throwable );
561                }
562            }
563        }
564    
565        /**
566         * Gets the {@code Modlets} of the context.
567         * <p>If no {@code Modlets} have been set using the {@code setModlets} method, this method calls the
568         * {@code findModlets} method to initialize the {@code Modlets} of the context.</p>
569         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
570         * to the returned list will be present inside the object.</p>
571         *
572         * @return The {@code Modlets} of the context.
573         *
574         * @throws ModelException if getting the {@code Modlets} of the context fails.
575         *
576         * @see #setModlets(org.jomc.modlet.Modlets)
577         * @see #findModlets()
578         */
579        public final Modlets getModlets() throws ModelException
580        {
581            try
582            {
583                if ( this.modlets == null )
584                {
585                    final Modlet modlet = new Modlet();
586                    modlet.setName( getMessage( "projectName" ) );
587                    modlet.setVendor( getMessage( "projectVendor" ) );
588                    modlet.setVersion( getMessage( "projectVersion" ) );
589                    modlet.setSchemas( new Schemas() );
590    
591                    final Schema schema = new Schema();
592                    schema.setPublicId( ModletObject.MODEL_PUBLIC_ID );
593                    schema.setSystemId( this.getModletSchemaSystemId() );
594                    schema.setContextId( ModletObject.class.getPackage().getName() );
595                    schema.setClasspathId( ModletObject.class.getPackage().getName().replace( '.', '/' )
596                                           + "/jomc-modlet-1.2.xsd" );
597    
598                    modlet.getSchemas().getSchema().add( schema );
599    
600                    this.modlets = new Modlets();
601                    this.modlets.getModlet().add( modlet );
602    
603                    final Modlets provided = this.findModlets();
604    
605                    for ( Modlet m : provided.getModlet() )
606                    {
607                        if ( this.modlets.getModlet( m.getName() ) == null )
608                        {
609                            this.modlets.getModlet().add( m );
610                        }
611                        else if ( this.isLoggable( Level.WARNING ) )
612                        {
613                            this.log( Level.WARNING, getMessage( "ignoringRedundantModlet", m.getName() ), null );
614                        }
615                    }
616    
617                    final javax.xml.validation.Schema modletSchema = this.createSchema( ModletObject.MODEL_PUBLIC_ID );
618                    final Validator validator = modletSchema.newValidator();
619                    validator.validate( new JAXBSource( this.createContext( ModletObject.MODEL_PUBLIC_ID ),
620                                                        new ObjectFactory().createModlets( this.modlets ) ) );
621    
622                }
623    
624                return this.modlets;
625            }
626            catch ( final IOException e )
627            {
628                this.modlets = null;
629                throw new ModelException( getMessage( e ), e );
630            }
631            catch ( final JAXBException e )
632            {
633                this.modlets = null;
634                String message = getMessage( e );
635                if ( message == null && e.getLinkedException() != null )
636                {
637                    message = getMessage( e.getLinkedException() );
638                }
639    
640                throw new ModelException( message, e );
641            }
642            catch ( final SAXException e )
643            {
644                this.modlets = null;
645                String message = getMessage( e );
646                if ( message == null && e.getException() != null )
647                {
648                    message = getMessage( e.getException() );
649                }
650    
651                throw new ModelException( message, e );
652            }
653        }
654    
655        /**
656         * Sets the {@code Modlets} of the context.
657         *
658         * @param value The new {@code Modlets} of the context or {@code null}.
659         *
660         * @see #getModlets()
661         */
662        public final void setModlets( final Modlets value )
663        {
664            this.modlets = value;
665        }
666    
667        /**
668         * Searches the context for a class with a given name.
669         *
670         * @param name The name of the class to search.
671         *
672         * @return A class object of the class with name {@code name} or {@code null}, if no such class is found.
673         *
674         * @throws NullPointerException if {@code name} is {@code null}.
675         * @throws ModelException if searching fails.
676         *
677         * @see #getClassLoader()
678         */
679        public Class<?> findClass( final String name ) throws ModelException
680        {
681            if ( name == null )
682            {
683                throw new NullPointerException( "name" );
684            }
685    
686            try
687            {
688                return Class.forName( name, false, this.getClassLoader() );
689            }
690            catch ( final ClassNotFoundException e )
691            {
692                if ( this.isLoggable( Level.FINE ) )
693                {
694                    this.log( Level.FINE, getMessage( e ), e );
695                }
696    
697                return null;
698            }
699        }
700    
701        /**
702         * Searches the context for a resource with a given name.
703         *
704         * @param name The name of the resource to search.
705         *
706         * @return An URL object for reading the resource or {@code null}, if no such resource is found.
707         *
708         * @throws NullPointerException if {@code name} is {@code null}.
709         * @throws ModelException if searching fails.
710         *
711         * @see #getClassLoader()
712         */
713        public URL findResource( final String name ) throws ModelException
714        {
715            if ( name == null )
716            {
717                throw new NullPointerException( "name" );
718            }
719    
720            if ( this.getClassLoader() == null )
721            {
722                return ClassLoader.getSystemResource( name );
723            }
724            else
725            {
726                return this.getClassLoader().getResource( name );
727            }
728        }
729    
730        /**
731         * Searches the context for resources with a given name.
732         *
733         * @param name The name of the resources to search.
734         *
735         * @return An enumeration of URL objects for reading the resources. If no resources are found, the enumeration will
736         * be empty.
737         *
738         * @throws NullPointerException if {@code name} is {@code null}.
739         * @throws ModelException if searching fails.
740         *
741         * @see #getClassLoader()
742         */
743        public Enumeration<URL> findResources( final String name ) throws ModelException
744        {
745            if ( name == null )
746            {
747                throw new NullPointerException( "name" );
748            }
749    
750            try
751            {
752                if ( this.getClassLoader() == null )
753                {
754                    return ClassLoader.getSystemResources( name );
755                }
756                else
757                {
758                    return this.getClassLoader().getResources( name );
759                }
760            }
761            catch ( final IOException e )
762            {
763                throw new ModelException( getMessage( e ), e );
764            }
765        }
766    
767        /**
768         * Searches the context for {@code Modlets}.
769         *
770         * @return The {@code Modlets} found in the context.
771         *
772         * @throws ModelException if searching {@code Modlets} fails.
773         *
774         * @see ModletProvider META-INF/services/org.jomc.modlet.ModletProvider
775         * @see #getModlets()
776         */
777        public abstract Modlets findModlets() throws ModelException;
778    
779        /**
780         * Creates a new {@code Model} instance.
781         *
782         * @param model The identifier of the {@code Model} to create.
783         *
784         * @return A new instance of the {@code Model} identified by {@code model}.
785         *
786         * @throws NullPointerException if {@code model} is {@code null}.
787         * @throws ModelException if creating a new {@code Model} instance fails.
788         *
789         * @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class) createServiceObject( <i>service</i>, ModelProvider.class )
790         * @see ModletObject#MODEL_PUBLIC_ID
791         */
792        public abstract Model findModel( String model ) throws ModelException;
793    
794        /**
795         * Populates a given {@code Model} instance.
796         *
797         * @param model The {@code Model} to populate.
798         *
799         * @return The populated model.
800         *
801         * @throws NullPointerException if {@code model} is {@code null}.
802         * @throws ModelException if populating {@code model} fails.
803         *
804         * @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class) createServiceObject( <i>service</i>, ModelProvider.class )
805         *
806         * @since 1.2
807         */
808        public abstract Model findModel( Model model ) throws ModelException;
809    
810        /**
811         * Gets the name of the class providing the default {@code ModelContext} implementation.
812         * <p>The name of the class providing the default {@code ModelContext} implementation returned by method
813         * {@link #createModelContext(java.lang.ClassLoader)} is controlled by system property
814         * {@code org.jomc.modlet.ModelContext.className}. If that property is not set, the name of the
815         * {@link org.jomc.modlet.DefaultModelContext} class is returned.</p>
816         *
817         * @return The name of the class providing the default {@code ModelContext} implementation.
818         *
819         * @see #setModelContextClassName(java.lang.String)
820         *
821         * @deprecated As of JOMC 1.2, replaced by class {@link ModelContextFactory}. This method will be removed in version
822         * 2.0.
823         */
824        @Deprecated
825        public static String getModelContextClassName()
826        {
827            if ( modelContextClassName == null )
828            {
829                modelContextClassName = System.getProperty( "org.jomc.modlet.ModelContext.className",
830                                                            DefaultModelContext.class.getName() );
831    
832            }
833    
834            return modelContextClassName;
835        }
836    
837        /**
838         * Sets the name of the class providing the default {@code ModelContext} implementation.
839         *
840         * @param value The new name of the class providing the default {@code ModelContext} implementation or {@code null}.
841         *
842         * @see #getModelContextClassName()
843         *
844         * @deprecated As of JOMC 1.2, replaced by class {@link ModelContextFactory}. This method will be removed in version
845         * 2.0.
846         */
847        @Deprecated
848        public static void setModelContextClassName( final String value )
849        {
850            modelContextClassName = value;
851        }
852    
853        /**
854         * Creates a new default {@code ModelContext} instance.
855         *
856         * @param classLoader The class loader to create a new default {@code ModelContext} instance with or {@code null},
857         * to create a new context using the platform's bootstrap class loader.
858         *
859         * @return A new {@code ModelContext} instance.
860         *
861         * @throws ModelException if creating a new {@code ModelContext} instance fails.
862         *
863         * @see #getModelContextClassName()
864         *
865         * @deprecated As of JOMC 1.2, replaced by method {@link ModelContextFactory#newModelContext(java.lang.ClassLoader)}.
866         * This method will be removed in version 2.0.
867         */
868        public static ModelContext createModelContext( final ClassLoader classLoader ) throws ModelException
869        {
870            if ( getModelContextClassName().equals( DefaultModelContext.class.getName() ) )
871            {
872                return new DefaultModelContext( classLoader );
873            }
874    
875            try
876            {
877                final Class<?> clazz = Class.forName( getModelContextClassName(), false, classLoader );
878    
879                if ( !ModelContext.class.isAssignableFrom( clazz ) )
880                {
881                    throw new ModelException( getMessage( "illegalContextImplementation", getModelContextClassName(),
882                                                          ModelContext.class.getName() ) );
883    
884                }
885    
886                final Constructor<? extends ModelContext> ctor =
887                    clazz.asSubclass( ModelContext.class ).getDeclaredConstructor( ClassLoader.class );
888    
889                return ctor.newInstance( classLoader );
890            }
891            catch ( final ClassNotFoundException e )
892            {
893                throw new ModelException( getMessage( "contextClassNotFound", getModelContextClassName() ), e );
894            }
895            catch ( final NoSuchMethodException e )
896            {
897                throw new ModelException( getMessage( "contextConstructorNotFound", getModelContextClassName() ), e );
898            }
899            catch ( final InstantiationException e )
900            {
901                final String message = getMessage( e );
902                throw new ModelException( getMessage( "contextInstantiationException", getModelContextClassName(),
903                                                      message != null ? " " + message : "" ), e );
904    
905            }
906            catch ( final IllegalAccessException e )
907            {
908                final String message = getMessage( e );
909                throw new ModelException( getMessage( "contextConstructorAccessDenied", getModelContextClassName(),
910                                                      message != null ? " " + message : "" ), e );
911    
912            }
913            catch ( final InvocationTargetException e )
914            {
915                String message = getMessage( e );
916                if ( message == null && e.getTargetException() != null )
917                {
918                    message = getMessage( e.getTargetException() );
919                }
920    
921                throw new ModelException( getMessage( "contextConstructorException", getModelContextClassName(),
922                                                      message != null ? " " + message : "" ), e );
923    
924            }
925        }
926    
927        /**
928         * Creates a new service object.
929         *
930         * @param <T> The type of the service.
931         * @param service The service to create a new object of.
932         * @param type The class of the type of the service.
933         *
934         * @return An new service object for {@code service}.
935         *
936         * @throws NullPointerException if {@code service} or {@code type} is {@code null}.
937         * @throws ModelException if creating the service object fails.
938         *
939         * @see ModelProvider
940         * @see ModelProcessor
941         * @see ModelValidator
942         *
943         * @since 1.2
944         */
945        public abstract <T> T createServiceObject( final Service service, final Class<T> type ) throws ModelException;
946    
947        /**
948         * Creates a new SAX entity resolver instance of a given model.
949         *
950         * @param model The identifier of the model to create a new SAX entity resolver of.
951         *
952         * @return A new SAX entity resolver instance of the model identified by {@code model}.
953         *
954         * @throws NullPointerException if {@code model} is {@code null}.
955         * @throws ModelException if creating a new SAX entity resolver instance fails.
956         *
957         * @see ModletObject#MODEL_PUBLIC_ID
958         */
959        public abstract EntityResolver createEntityResolver( String model ) throws ModelException;
960    
961        /**
962         * Creates a new SAX entity resolver instance for a given public identifier URI.
963         *
964         * @param publicId The public identifier URI to create a new SAX entity resolver for.
965         *
966         * @return A new SAX entity resolver instance for the public identifier URI {@code publicId}.
967         *
968         * @throws NullPointerException if {@code publicId} is {@code null}.
969         * @throws ModelException if creating a new SAX entity resolver instance fails.
970         *
971         * @see ModletObject#PUBLIC_ID
972         * @since 1.2
973         */
974        public abstract EntityResolver createEntityResolver( URI publicId ) throws ModelException;
975    
976        /**
977         * Creates a new L/S resource resolver instance of a given model.
978         *
979         * @param model The identifier of the model to create a new L/S resource resolver of.
980         *
981         * @return A new L/S resource resolver instance of the model identified by {@code model}.
982         *
983         * @throws NullPointerException if {@code model} is {@code null}.
984         * @throws ModelException if creating a new L/S resource resolver instance fails.
985         *
986         * @see ModletObject#MODEL_PUBLIC_ID
987         */
988        public abstract LSResourceResolver createResourceResolver( String model ) throws ModelException;
989    
990        /**
991         * Creates a new L/S resource resolver instance for a given public identifier URI.
992         *
993         * @param publicId The public identifier URI to create a new L/S resource resolver for.
994         *
995         * @return A new L/S resource resolver instance for the public identifier URI {@code publicId}.
996         *
997         * @throws NullPointerException if {@code publicId} is {@code null}.
998         * @throws ModelException if creating a new L/S resource resolver instance fails.
999         *
1000         * @see ModletObject#PUBLIC_ID
1001         * @since 1.2
1002         */
1003        public abstract LSResourceResolver createResourceResolver( URI publicId ) throws ModelException;
1004    
1005        /**
1006         * Creates a new JAXP schema instance of a given model.
1007         *
1008         * @param model The identifier of the model to create a new JAXP schema instance of.
1009         *
1010         * @return A new JAXP schema instance of the model identified by {@code model}.
1011         *
1012         * @throws NullPointerException if {@code model} is {@code null}.
1013         * @throws ModelException if creating a new JAXP schema instance fails.
1014         *
1015         * @see ModletObject#MODEL_PUBLIC_ID
1016         */
1017        public abstract javax.xml.validation.Schema createSchema( String model ) throws ModelException;
1018    
1019        /**
1020         * Creates a new JAXP schema instance for a given public identifier URI.
1021         *
1022         * @param publicId The public identifier URI to create a new JAXP schema instance for.
1023         *
1024         * @return A new JAXP schema instance for the public identifier URI {@code publicId}.
1025         *
1026         * @throws NullPointerException if {@code publicId} is {@code null}.
1027         * @throws ModelException if creating a new JAXP schema instance fails.
1028         *
1029         * @see ModletObject#PUBLIC_ID
1030         * @since 1.2
1031         */
1032        public abstract javax.xml.validation.Schema createSchema( URI publicId ) throws ModelException;
1033    
1034        /**
1035         * Creates a new JAXB context instance of a given model.
1036         *
1037         * @param model The identifier of the model to create a new JAXB context instance of.
1038         *
1039         * @return A new JAXB context instance of the model identified by {@code model}.
1040         *
1041         * @throws NullPointerException if {@code model} is {@code null}.
1042         * @throws ModelException if creating a new JAXB context instance fails.
1043         *
1044         * @see ModletObject#MODEL_PUBLIC_ID
1045         */
1046        public abstract JAXBContext createContext( String model ) throws ModelException;
1047    
1048        /**
1049         * Creates a new JAXB context instance for a given public identifier URI.
1050         *
1051         * @param publicId The public identifier URI to create a new JAXB context instance for.
1052         *
1053         * @return A new JAXB context instance for the public identifier URI {@code publicId}.
1054         *
1055         * @throws NullPointerException if {@code publicId} is {@code null}.
1056         * @throws ModelException if creating a new JAXB context instance fails.
1057         *
1058         * @see ModletObject#PUBLIC_ID
1059         * @since 1.2
1060         */
1061        public abstract JAXBContext createContext( URI publicId ) throws ModelException;
1062    
1063        /**
1064         * Creates a new JAXB marshaller instance of a given model.
1065         *
1066         * @param model The identifier of the model to create a new JAXB marshaller instance of.
1067         *
1068         * @return A new JAXB marshaller instance of the model identified by {@code model}.
1069         *
1070         * @throws NullPointerException if {@code model} is {@code null}.
1071         * @throws ModelException if creating a new JAXB marshaller instance fails.
1072         *
1073         * @see ModletObject#MODEL_PUBLIC_ID
1074         */
1075        public abstract Marshaller createMarshaller( String model ) throws ModelException;
1076    
1077        /**
1078         * Creates a new JAXB marshaller instance for a given public identifier URI.
1079         *
1080         * @param publicId The public identifier URI to create a new JAXB marshaller instance for.
1081         *
1082         * @return A new JAXB marshaller instance for the public identifier URI {@code publicId}.
1083         *
1084         * @throws NullPointerException if {@code publicId} is {@code null}.
1085         * @throws ModelException if creating a new JAXB marshaller instance fails.
1086         *
1087         * @see ModletObject#PUBLIC_ID
1088         * @since 1.2
1089         */
1090        public abstract Marshaller createMarshaller( URI publicId ) throws ModelException;
1091    
1092        /**
1093         * Creates a new JAXB unmarshaller instance of a given model.
1094         *
1095         * @param model The identifier of the model to create a new JAXB unmarshaller instance of.
1096         *
1097         * @return A new JAXB unmarshaller instance of the model identified by {@code model}.
1098         *
1099         * @throws NullPointerException if {@code model} is {@code null}.
1100         * @throws ModelException if creating a new JAXB unmarshaller instance fails.
1101         *
1102         * @see ModletObject#MODEL_PUBLIC_ID
1103         */
1104        public abstract Unmarshaller createUnmarshaller( String model ) throws ModelException;
1105    
1106        /**
1107         * Creates a new JAXB unmarshaller instance for a given given public identifier URI.
1108         *
1109         * @param publicId The public identifier URI to create a new JAXB unmarshaller instance for.
1110         *
1111         * @return A new JAXB unmarshaller instance for the public identifier URI {@code publicId}.
1112         *
1113         * @throws NullPointerException if {@code publicId} is {@code null}.
1114         * @throws ModelException if creating a new JAXB unmarshaller instance fails.
1115         *
1116         * @see ModletObject#PUBLIC_ID
1117         * @since 1.2
1118         */
1119        public abstract Unmarshaller createUnmarshaller( URI publicId ) throws ModelException;
1120    
1121        /**
1122         * Processes a {@code Model}.
1123         *
1124         * @param model The {@code Model} to process.
1125         *
1126         * @return The processed {@code Model}.
1127         *
1128         * @throws NullPointerException if {@code model} is {@code null}.
1129         * @throws ModelException if processing {@code model} fails.
1130         *
1131         * @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class) createServiceObject( <i>service</i>, ModelProcessor.class )
1132         */
1133        public abstract Model processModel( Model model ) throws ModelException;
1134    
1135        /**
1136         * Validates a given {@code Model}.
1137         *
1138         * @param model The {@code Model} to validate.
1139         *
1140         * @return Validation report.
1141         *
1142         * @throws NullPointerException if {@code model} is {@code null}.
1143         * @throws ModelException if validating the modules fails.
1144         *
1145         * @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class) createServiceObject( <i>service</i>, ModelValidator.class )
1146         * @see ModelValidationReport#isModelValid()
1147         */
1148        public abstract ModelValidationReport validateModel( Model model ) throws ModelException;
1149    
1150        /**
1151         * Validates a given model.
1152         *
1153         * @param model The identifier of the {@code Model} to use for validating {@code source}.
1154         * @param source A source providing the model to validate.
1155         *
1156         * @return Validation report.
1157         *
1158         * @throws NullPointerException if {@code model} or {@code source} is {@code null}.
1159         * @throws ModelException if validating the model fails.
1160         *
1161         * @see #createSchema(java.lang.String)
1162         * @see ModelValidationReport#isModelValid()
1163         * @see ModletObject#MODEL_PUBLIC_ID
1164         */
1165        public abstract ModelValidationReport validateModel( String model, Source source ) throws ModelException;
1166    
1167        private static String getMessage( final String key, final Object... args )
1168        {
1169            return MessageFormat.format( ResourceBundle.getBundle(
1170                ModelContext.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
1171    
1172        }
1173    
1174        private static String getMessage( final Throwable t )
1175        {
1176            return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
1177        }
1178    
1179    }