001/*
002 *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: ModelContext.java 5305 2016-08-30 21:46:23Z schulte $
029 *
030 */
031package org.jomc.modlet;
032
033import java.io.IOException;
034import java.lang.reflect.Constructor;
035import java.lang.reflect.InvocationTargetException;
036import java.net.URI;
037import java.net.URL;
038import java.text.MessageFormat;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.Enumeration;
042import java.util.List;
043import java.util.Locale;
044import java.util.Map;
045import java.util.ResourceBundle;
046import java.util.Set;
047import java.util.concurrent.ConcurrentHashMap;
048import java.util.concurrent.CopyOnWriteArrayList;
049import java.util.concurrent.ExecutorService;
050import java.util.logging.Level;
051import javax.xml.bind.JAXBContext;
052import javax.xml.bind.Marshaller;
053import javax.xml.bind.Unmarshaller;
054import javax.xml.transform.Source;
055import org.w3c.dom.ls.LSResourceResolver;
056import org.xml.sax.EntityResolver;
057
058/**
059 * Model context interface.
060 * <p>
061 * <b>Use Cases:</b><br/><ul>
062 * <li>{@link #createContext(java.lang.String) }</li>
063 * <li>{@link #createEntityResolver(java.lang.String) }</li>
064 * <li>{@link #createMarshaller(java.lang.String) }</li>
065 * <li>{@link #createResourceResolver(java.lang.String) }</li>
066 * <li>{@link #createSchema(java.lang.String) }</li>
067 * <li>{@link #createUnmarshaller(java.lang.String) }</li>
068 * <li>{@link #findModel(java.lang.String) }</li>
069 * <li>{@link #findModel(org.jomc.modlet.Model) }</li>
070 * <li>{@link #processModel(org.jomc.modlet.Model) }</li>
071 * <li>{@link #validateModel(org.jomc.modlet.Model) }</li>
072 * <li>{@link #validateModel(java.lang.String, javax.xml.transform.Source) }</li>
073 * </ul>
074 *
075 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
076 * @version $JOMC: ModelContext.java 5305 2016-08-30 21:46:23Z schulte $
077 *
078 * @see ModelContextFactory
079 */
080public abstract class ModelContext
081{
082
083    /**
084     * Listener interface.
085     */
086    public abstract static class Listener
087    {
088
089        /**
090         * Creates a new {@code Listener} instance.
091         */
092        public Listener()
093        {
094            super();
095        }
096
097        /**
098         * Gets called on logging.
099         *
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}