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