View Javadoc

1   /*
2    *   Copyright (C) Christian Schulte, 2005-206
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: ModelContext.java 4613 2012-09-22 10:07:08Z schulte $
29   *
30   */
31  package org.jomc.modlet;
32  
33  import java.io.IOException;
34  import java.lang.reflect.Constructor;
35  import java.lang.reflect.InvocationTargetException;
36  import java.net.URI;
37  import java.net.URL;
38  import java.text.MessageFormat;
39  import java.util.Collections;
40  import java.util.Enumeration;
41  import java.util.HashMap;
42  import java.util.LinkedList;
43  import java.util.List;
44  import java.util.Locale;
45  import java.util.Map;
46  import java.util.ResourceBundle;
47  import java.util.Set;
48  import java.util.logging.Level;
49  import javax.xml.bind.JAXBContext;
50  import javax.xml.bind.JAXBException;
51  import javax.xml.bind.Marshaller;
52  import javax.xml.bind.Unmarshaller;
53  import javax.xml.bind.util.JAXBSource;
54  import javax.xml.transform.Source;
55  import javax.xml.validation.Validator;
56  import org.w3c.dom.ls.LSResourceResolver;
57  import org.xml.sax.EntityResolver;
58  import org.xml.sax.SAXException;
59  
60  /**
61   * Model context interface.
62   * <p><b>Use Cases:</b><br/><ul>
63   * <li>{@link #createContext(java.lang.String) }</li>
64   * <li>{@link #createContext(java.net.URI) }</li>
65   * <li>{@link #createEntityResolver(java.lang.String) }</li>
66   * <li>{@link #createEntityResolver(java.net.URI) }</li>
67   * <li>{@link #createMarshaller(java.lang.String) }</li>
68   * <li>{@link #createMarshaller(java.net.URI) }</li>
69   * <li>{@link #createResourceResolver(java.lang.String) }</li>
70   * <li>{@link #createResourceResolver(java.net.URI) }</li>
71   * <li>{@link #createSchema(java.lang.String) }</li>
72   * <li>{@link #createSchema(java.net.URI) }</li>
73   * <li>{@link #createUnmarshaller(java.lang.String) }</li>
74   * <li>{@link #createUnmarshaller(java.net.URI) }</li>
75   * <li>{@link #findModel(java.lang.String) }</li>
76   * <li>{@link #findModel(org.jomc.modlet.Model) }</li>
77   * <li>{@link #processModel(org.jomc.modlet.Model) }</li>
78   * <li>{@link #validateModel(org.jomc.modlet.Model) }</li>
79   * <li>{@link #validateModel(java.lang.String, javax.xml.transform.Source) }</li>
80   * </ul>
81   *
82   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
83   * @version $JOMC: ModelContext.java 4613 2012-09-22 10:07:08Z schulte $
84   *
85   * @see ModelContextFactory
86   */
87  public abstract class ModelContext
88  {
89  
90      /** Listener interface. */
91      public abstract static class Listener
92      {
93  
94          /** Creates a new {@code Listener} instance. */
95          public Listener()
96          {
97              super();
98          }
99  
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://xml.jomc.org/modlet/jomc-modlet-1.3.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" target="alexandria_uri">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://xml.jomc.org/modlet/jomc-modlet-1.3.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.3.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 }