001/*
002 *   Copyright (C) Christian Schulte <cs@schulte.it>, 2005-206
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 5051 2015-05-30 17:29:32Z 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.HashMap;
043import java.util.LinkedList;
044import java.util.List;
045import java.util.Locale;
046import java.util.Map;
047import java.util.ResourceBundle;
048import java.util.Set;
049import java.util.logging.Level;
050import javax.xml.bind.JAXBContext;
051import javax.xml.bind.Marshaller;
052import javax.xml.bind.Unmarshaller;
053import javax.xml.transform.Source;
054import org.w3c.dom.ls.LSResourceResolver;
055import org.xml.sax.EntityResolver;
056
057/**
058 * Model context interface.
059 * <p>
060 * <b>Use Cases:</b><br/><ul>
061 * <li>{@link #createContext(java.lang.String) }</li>
062 * <li>{@link #createEntityResolver(java.lang.String) }</li>
063 * <li>{@link #createMarshaller(java.lang.String) }</li>
064 * <li>{@link #createResourceResolver(java.lang.String) }</li>
065 * <li>{@link #createSchema(java.lang.String) }</li>
066 * <li>{@link #createUnmarshaller(java.lang.String) }</li>
067 * <li>{@link #findModel(java.lang.String) }</li>
068 * <li>{@link #findModel(org.jomc.modlet.Model) }</li>
069 * <li>{@link #processModel(org.jomc.modlet.Model) }</li>
070 * <li>{@link #validateModel(org.jomc.modlet.Model) }</li>
071 * <li>{@link #validateModel(java.lang.String, javax.xml.transform.Source) }</li>
072 * </ul>
073 *
074 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
075 * @version $JOMC: ModelContext.java 5051 2015-05-30 17:29:32Z schulte $
076 *
077 * @see ModelContextFactory
078 */
079public abstract class ModelContext
080{
081
082    /**
083     * Listener interface.
084     */
085    public abstract static class Listener
086    {
087
088        /**
089         * Creates a new {@code Listener} instance.
090         */
091        public Listener()
092        {
093            super();
094        }
095
096        /**
097         * Gets called on logging.
098         *
099         * @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}