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