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