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