ModelContext.java

/*
 *   Copyright (C) Christian Schulte <cs@schulte.it>, 2005-206
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     o Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     o Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *   $JOMC: ModelContext.java 5051 2015-05-30 17:29:32Z schulte $
 *
 */
package org.jomc.modlet;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.EntityResolver;

/**
 * Model context interface.
 * <p>
 * <b>Use Cases:</b><br/><ul>
 * <li>{@link #createContext(java.lang.String) }</li>
 * <li>{@link #createEntityResolver(java.lang.String) }</li>
 * <li>{@link #createMarshaller(java.lang.String) }</li>
 * <li>{@link #createResourceResolver(java.lang.String) }</li>
 * <li>{@link #createSchema(java.lang.String) }</li>
 * <li>{@link #createUnmarshaller(java.lang.String) }</li>
 * <li>{@link #findModel(java.lang.String) }</li>
 * <li>{@link #findModel(org.jomc.modlet.Model) }</li>
 * <li>{@link #processModel(org.jomc.modlet.Model) }</li>
 * <li>{@link #validateModel(org.jomc.modlet.Model) }</li>
 * <li>{@link #validateModel(java.lang.String, javax.xml.transform.Source) }</li>
 * </ul>
 *
 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
 * @version $JOMC: ModelContext.java 5051 2015-05-30 17:29:32Z schulte $
 *
 * @see ModelContextFactory
 */
public abstract class ModelContext
{

    /**
     * Listener interface.
     */
    public abstract static class Listener
    {

        /**
         * Creates a new {@code Listener} instance.
         */
        public Listener()
        {
            super();
        }

        /**
         * Gets called on logging.
         *
         * @param level The level of the event.
         * @param message The message of the event or {@code null}.
         * @param t The throwable of the event or {@code null}.
         *
         * @throws NullPointerException if {@code level} is {@code null}.
         */
        public void onLog( final Level level, final String message, final Throwable t )
        {
            if ( level == null )
            {
                throw new NullPointerException( "level" );
            }
        }

    }

    /**
     * Default {@code http://jomc.org/modlet} namespace schema system id.
     *
     * @see #getDefaultModletSchemaSystemId()
     */
    private static final String DEFAULT_MODLET_SCHEMA_SYSTEM_ID =
        "http://xml.jomc.org/modlet/jomc-modlet-1.9.xsd";

    /**
     * Log level events are logged at by default.
     *
     * @see #getDefaultLogLevel()
     */
    private static final Level DEFAULT_LOG_LEVEL = Level.WARNING;

    /**
     * Default log level.
     */
    private static volatile Level defaultLogLevel;

    /**
     * Default {@code http://jomc.org/model/modlet} namespace schema system id.
     */
    private static volatile String defaultModletSchemaSystemId;

    /**
     * Class name of the {@code ModelContext} implementation.
     */
    @Deprecated
    private static volatile String modelContextClassName;

    /**
     * The attributes of the instance.
     */
    private final Map<String, Object> attributes = new HashMap<String, Object>();

    /**
     * The class loader of the instance.
     */
    private ClassLoader classLoader;

    /**
     * Flag indicating the {@code classLoader} field is initialized.
     *
     * @since 1.2
     */
    private boolean classLoaderSet;

    /**
     * The listeners of the instance.
     */
    private List<Listener> listeners;

    /**
     * Log level of the instance.
     */
    private Level logLevel;

    /**
     * The {@code Modlets} of the instance.
     */
    private Modlets modlets;

    /**
     * Modlet namespace schema system id of the instance.
     */
    private String modletSchemaSystemId;

    /**
     * Creates a new {@code ModelContext} instance.
     *
     * @since 1.2
     */
    public ModelContext()
    {
        super();
        this.classLoader = null;
        this.classLoaderSet = false;
    }

    /**
     * Creates a new {@code ModelContext} instance taking a class loader.
     *
     * @param classLoader The class loader of the context.
     *
     * @see #getClassLoader()
     */
    public ModelContext( final ClassLoader classLoader )
    {
        super();
        this.classLoader = classLoader;
        this.classLoaderSet = true;
    }

    /**
     * Gets a set holding the names of all attributes of the context.
     *
     * @return An unmodifiable set holding the names of all attributes of the context.
     *
     * @see #clearAttribute(java.lang.String)
     * @see #getAttribute(java.lang.String)
     * @see #getAttribute(java.lang.String, java.lang.Object)
     * @see #setAttribute(java.lang.String, java.lang.Object)
     * @since 1.2
     */
    public Set<String> getAttributeNames()
    {
        return Collections.unmodifiableSet( this.attributes.keySet() );
    }

    /**
     * Gets an attribute of the context.
     *
     * @param name The name of the attribute to get.
     *
     * @return The value of the attribute with name {@code name}; {@code null} if no attribute matching {@code name} is
     * found.
     *
     * @throws NullPointerException if {@code name} is {@code null}.
     *
     * @see #getAttribute(java.lang.String, java.lang.Object)
     * @see #setAttribute(java.lang.String, java.lang.Object)
     * @see #clearAttribute(java.lang.String)
     */
    public Object getAttribute( final String name )
    {
        if ( name == null )
        {
            throw new NullPointerException( "name" );
        }

        return this.attributes.get( name );
    }

    /**
     * Gets an attribute of the context.
     *
     * @param name The name of the attribute to get.
     * @param def The value to return if no attribute matching {@code name} is found.
     *
     * @return The value of the attribute with name {@code name}; {@code def} if no such attribute is found.
     *
     * @throws NullPointerException if {@code name} is {@code null}.
     *
     * @see #getAttribute(java.lang.String)
     * @see #setAttribute(java.lang.String, java.lang.Object)
     * @see #clearAttribute(java.lang.String)
     */
    public Object getAttribute( final String name, final Object def )
    {
        if ( name == null )
        {
            throw new NullPointerException( "name" );
        }

        Object value = this.getAttribute( name );

        if ( value == null )
        {
            value = def;
        }

        return value;
    }

    /**
     * Sets an attribute in the context.
     *
     * @param name The name of the attribute to set.
     * @param value The value of the attribute to set.
     *
     * @return The previous value of the attribute with name {@code name}; {@code null} if no such value is found.
     *
     * @throws NullPointerException if {@code name} or {@code value} is {@code null}.
     *
     * @see #getAttribute(java.lang.String)
     * @see #getAttribute(java.lang.String, java.lang.Object)
     * @see #clearAttribute(java.lang.String)
     */
    public Object setAttribute( final String name, final Object value )
    {
        if ( name == null )
        {
            throw new NullPointerException( "name" );
        }
        if ( value == null )
        {
            throw new NullPointerException( "value" );
        }

        return this.attributes.put( name, value );
    }

    /**
     * Removes an attribute from the context.
     *
     * @param name The name of the attribute to remove.
     *
     * @throws NullPointerException if {@code name} is {@code null}.
     *
     * @see #getAttribute(java.lang.String)
     * @see #getAttribute(java.lang.String, java.lang.Object)
     * @see #setAttribute(java.lang.String, java.lang.Object)
     */
    public void clearAttribute( final String name )
    {
        if ( name == null )
        {
            throw new NullPointerException( "name" );
        }

        this.attributes.remove( name );
    }

    /**
     * Gets the class loader of the context.
     *
     * @return The class loader of the context or {@code null}, indicating the bootstrap class loader.
     *
     * @see #findClass(java.lang.String)
     * @see #findResource(java.lang.String)
     * @see #findResources(java.lang.String)
     */
    public ClassLoader getClassLoader()
    {
        if ( !this.classLoaderSet )
        {
            this.classLoader = this.getClass().getClassLoader();
            this.classLoaderSet = true;
        }

        return this.classLoader;
    }

    /**
     * Gets the listeners of the context.
     * <p>
     * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
     * to the returned list will be present inside the object. This is why there is no {@code set} method for the
     * listeners property.
     * </p>
     *
     * @return The list of listeners of the context.
     *
     * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
     */
    public List<Listener> getListeners()
    {
        if ( this.listeners == null )
        {
            this.listeners = new LinkedList<Listener>();
        }

        return this.listeners;
    }

    /**
     * Gets the default {@code http://jomc.org/modlet} namespace schema system id.
     * <p>
     * The default {@code http://jomc.org/modlet} namespace schema system id is controlled by system property
     * {@code org.jomc.modlet.ModelContext.defaultModletSchemaSystemId} holding a system id URI.
     * If that property is not set, the {@code http://xml.jomc.org/modlet/jomc-modlet-1.9.xsd} default is
     * returned.
     * </p>
     *
     * @return The default system id of the {@code http://jomc.org/modlet} namespace schema.
     *
     * @see #setDefaultModletSchemaSystemId(java.lang.String)
     */
    public static String getDefaultModletSchemaSystemId()
    {
        if ( defaultModletSchemaSystemId == null )
        {
            defaultModletSchemaSystemId = System.getProperty(
                "org.jomc.modlet.ModelContext.defaultModletSchemaSystemId", DEFAULT_MODLET_SCHEMA_SYSTEM_ID );

        }

        return defaultModletSchemaSystemId;
    }

    /**
     * Sets the default {@code http://jomc.org/modlet} namespace schema system id.
     *
     * @param value The new default {@code http://jomc.org/modlet} namespace schema system id or {@code null}.
     *
     * @see #getDefaultModletSchemaSystemId()
     */
    public static void setDefaultModletSchemaSystemId( final String value )
    {
        defaultModletSchemaSystemId = value;
    }

    /**
     * Gets the {@code http://jomc.org/modlet} namespace schema system id of the context.
     *
     * @return The {@code http://jomc.org/modlet} namespace schema system id of the context.
     *
     * @see #getDefaultModletSchemaSystemId()
     * @see #setModletSchemaSystemId(java.lang.String)
     */
    public final String getModletSchemaSystemId()
    {
        if ( this.modletSchemaSystemId == null )
        {
            this.modletSchemaSystemId = getDefaultModletSchemaSystemId();

            if ( this.isLoggable( Level.CONFIG ) )
            {
                this.log( Level.CONFIG,
                          getMessage( "defaultModletSchemaSystemIdInfo", this.modletSchemaSystemId ), null );

            }
        }

        return this.modletSchemaSystemId;
    }

    /**
     * Sets the {@code http://jomc.org/modlet} namespace schema system id of the context.
     *
     * @param value The new {@code http://jomc.org/modlet} namespace schema system id or {@code null}.
     *
     * @see #getModletSchemaSystemId()
     */
    public final void setModletSchemaSystemId( final String value )
    {
        final String oldModletSchemaSystemId = this.getModletSchemaSystemId();
        this.modletSchemaSystemId = value;

        if ( this.modlets != null )
        {
            for ( int i = 0, s0 = this.modlets.getModlet().size(); i < s0; i++ )
            {
                final Modlet m = this.modlets.getModlet().get( i );

                if ( m.getSchemas() != null )
                {
                    final Schema s = m.getSchemas().getSchemaBySystemId( oldModletSchemaSystemId );

                    if ( s != null )
                    {
                        s.setSystemId( value );
                    }
                }
            }
        }
    }

    /**
     * Gets the default log level events are logged at.
     * <p>
     * The default log level is controlled by system property
     * {@code org.jomc.modlet.ModelContext.defaultLogLevel} holding the log level to log events at by default.
     * If that property is not set, the {@code WARNING} default is returned.
     * </p>
     *
     * @return The log level events are logged at by default.
     *
     * @see #getLogLevel()
     * @see Level#parse(java.lang.String)
     */
    public static Level getDefaultLogLevel()
    {
        if ( defaultLogLevel == null )
        {
            defaultLogLevel = Level.parse( System.getProperty(
                "org.jomc.modlet.ModelContext.defaultLogLevel", DEFAULT_LOG_LEVEL.getName() ) );

        }

        return defaultLogLevel;
    }

    /**
     * Sets the default log level events are logged at.
     *
     * @param value The new default level events are logged at or {@code null}.
     *
     * @see #getDefaultLogLevel()
     */
    public static void setDefaultLogLevel( final Level value )
    {
        defaultLogLevel = value;
    }

    /**
     * Gets the log level of the context.
     *
     * @return The log level of the context.
     *
     * @see #getDefaultLogLevel()
     * @see #setLogLevel(java.util.logging.Level)
     * @see #isLoggable(java.util.logging.Level)
     */
    public final Level getLogLevel()
    {
        if ( this.logLevel == null )
        {
            this.logLevel = getDefaultLogLevel();

            if ( this.isLoggable( Level.CONFIG ) )
            {
                this.log( Level.CONFIG, getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ), null );
            }
        }

        return this.logLevel;
    }

    /**
     * Sets the log level of the context.
     *
     * @param value The new log level of the context or {@code null}.
     *
     * @see #getLogLevel()
     * @see #isLoggable(java.util.logging.Level)
     */
    public final void setLogLevel( final Level value )
    {
        this.logLevel = value;
    }

    /**
     * Checks if a message at a given level is provided to the listeners of the context.
     *
     * @param level The level to test.
     *
     * @return {@code true}, if messages at {@code level} are provided to the listeners of the context; {@code false},
     * if messages at {@code level} are not provided to the listeners of the context.
     *
     * @throws NullPointerException if {@code level} is {@code null}.
     *
     * @see #getLogLevel()
     * @see #setLogLevel(java.util.logging.Level)
     */
    public boolean isLoggable( final Level level )
    {
        if ( level == null )
        {
            throw new NullPointerException( "level" );
        }

        return level.intValue() >= this.getLogLevel().intValue();
    }

    /**
     * Notifies all listeners of the context.
     *
     * @param level The level of the event.
     * @param message The message of the event or {@code null}.
     * @param throwable The throwable of the event {@code null}.
     *
     * @throws NullPointerException if {@code level} is {@code null}.
     *
     * @see #getListeners()
     * @see #isLoggable(java.util.logging.Level)
     */
    public void log( final Level level, final String message, final Throwable throwable )
    {
        if ( level == null )
        {
            throw new NullPointerException( "level" );
        }

        if ( this.isLoggable( level ) )
        {
            for ( final Listener l : this.getListeners() )
            {
                l.onLog( level, message, throwable );
            }
        }
    }

    /**
     * Gets the {@code Modlets} of the context.
     * <p>
     * If no {@code Modlets} have been set using the {@code setModlets} method, this method calls the
     * {@code findModlets} method and the {@code processModlets} method to initialize the {@code Modlets} of the
     * context.
     * </p>
     * <p>
     * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
     * to the returned list will be present inside the object.
     * </p>
     *
     * @return The {@code Modlets} of the context.
     *
     * @throws ModelException if getting the {@code Modlets} of the context fails.
     *
     * @see #setModlets(org.jomc.modlet.Modlets)
     * @see #findModlets(org.jomc.modlet.Modlets)
     * @see #processModlets(org.jomc.modlet.Modlets)
     * @see #validateModlets(org.jomc.modlet.Modlets)
     */
    public final Modlets getModlets() throws ModelException
    {
        if ( this.modlets == null )
        {
            final Modlet modlet = new Modlet();
            modlet.setModel( ModletObject.MODEL_PUBLIC_ID );
            modlet.setName( getMessage( "projectName" ) );
            modlet.setVendor( getMessage( "projectVendor" ) );
            modlet.setVersion( getMessage( "projectVersion" ) );
            modlet.setSchemas( new Schemas() );

            final Schema schema = new Schema();
            schema.setPublicId( ModletObject.MODEL_PUBLIC_ID );
            schema.setSystemId( this.getModletSchemaSystemId() );
            schema.setContextId( ModletObject.class.getPackage().getName() );
            schema.setClasspathId( ModletObject.class.getPackage().getName().replace( '.', '/' )
                                       + "/jomc-modlet-1.9.xsd" );

            modlet.getSchemas().getSchema().add( schema );

            this.modlets = new Modlets();
            this.modlets.getModlet().add( modlet );

            long t0 = System.currentTimeMillis();
            final Modlets provided = this.findModlets( this.modlets );

            if ( this.isLoggable( Level.FINE ) )
            {
                this.log( Level.FINE, getMessage( "findModletsReport",
                                                  provided != null ? provided.getModlet().size() : 0,
                                                  System.currentTimeMillis() - t0 ), null );

            }

            if ( provided != null )
            {
                this.modlets = provided;
            }

            t0 = System.currentTimeMillis();
            final Modlets processed = this.processModlets( this.modlets );

            if ( this.isLoggable( Level.FINE ) )
            {
                this.log( Level.FINE, getMessage( "processModletsReport",
                                                  processed != null ? processed.getModlet().size() : 0,
                                                  System.currentTimeMillis() - t0 ), null );
            }

            if ( processed != null )
            {
                this.modlets = processed;
            }

            t0 = System.currentTimeMillis();
            final ModelValidationReport report = this.validateModlets( this.modlets );

            if ( this.isLoggable( Level.FINE ) )
            {
                this.log( Level.FINE, getMessage( "validateModletsReport",
                                                  this.modlets.getModlet().size(),
                                                  System.currentTimeMillis() - t0 ), null );
            }

            for ( final ModelValidationReport.Detail detail : report.getDetails() )
            {
                if ( this.isLoggable( detail.getLevel() ) )
                {
                    this.log( detail.getLevel(), detail.getMessage(), null );
                }
            }

            if ( !report.isModelValid() )
            {
                this.modlets = null;
                throw new ModelException( getMessage( "invalidModlets" ) );
            }
        }

        return this.modlets;
    }

    /**
     * Sets the {@code Modlets} of the context.
     *
     * @param value The new {@code Modlets} of the context or {@code null}.
     *
     * @see #getModlets()
     */
    public final void setModlets( final Modlets value )
    {
        this.modlets = value;
    }

    /**
     * Searches the context for a class with a given name.
     *
     * @param name The name of the class to search.
     *
     * @return A class object of the class with name {@code name} or {@code null}, if no such class is found.
     *
     * @throws NullPointerException if {@code name} is {@code null}.
     * @throws ModelException if searching fails.
     *
     * @see #getClassLoader()
     */
    public Class<?> findClass( final String name ) throws ModelException
    {
        if ( name == null )
        {
            throw new NullPointerException( "name" );
        }

        try
        {
            return Class.forName( name, false, this.getClassLoader() );
        }
        catch ( final ClassNotFoundException e )
        {
            if ( this.isLoggable( Level.FINE ) )
            {
                this.log( Level.FINE, getMessage( e ), e );
            }

            return null;
        }
    }

    /**
     * Searches the context for a resource with a given name.
     *
     * @param name The name of the resource to search.
     *
     * @return An URL object for reading the resource or {@code null}, if no such resource is found.
     *
     * @throws NullPointerException if {@code name} is {@code null}.
     * @throws ModelException if searching fails.
     *
     * @see #getClassLoader()
     */
    public URL findResource( final String name ) throws ModelException
    {
        if ( name == null )
        {
            throw new NullPointerException( "name" );
        }

        final long t0 = System.currentTimeMillis();
        final URL resource = this.getClassLoader() == null
                                 ? ClassLoader.getSystemResource( name )
                                 : this.getClassLoader().getResource( name );

        if ( this.isLoggable( Level.FINE ) )
        {
            this.log( Level.FINE, getMessage( "resourcesReport", name, System.currentTimeMillis() - t0 ), null );
        }

        return resource;
    }

    /**
     * Searches the context for resources with a given name.
     *
     * @param name The name of the resources to search.
     *
     * @return An enumeration of URL objects for reading the resources. If no resources are found, the enumeration will
     * be empty.
     *
     * @throws NullPointerException if {@code name} is {@code null}.
     * @throws ModelException if searching fails.
     *
     * @see #getClassLoader()
     */
    public Enumeration<URL> findResources( final String name ) throws ModelException
    {
        if ( name == null )
        {
            throw new NullPointerException( "name" );
        }

        try
        {
            final long t0 = System.currentTimeMillis();
            final Enumeration<URL> resources = this.getClassLoader() == null
                                                   ? ClassLoader.getSystemResources( name )
                                                   : this.getClassLoader().getResources( name );

            if ( this.isLoggable( Level.FINE ) )
            {
                this.log( Level.FINE, getMessage( "resourcesReport", name, System.currentTimeMillis() - t0 ), null );
            }

            return resources;
        }
        catch ( final IOException e )
        {
            throw new ModelException( getMessage( e ), e );
        }
    }

    /**
     * Searches the context for {@code Modlets}.
     *
     * @return The {@code Modlets} found in the context or {@code null}.
     *
     * @throws ModelException if searching {@code Modlets} fails.
     *
     * @see ModletProvider META-INF/services/org.jomc.modlet.ModletProvider
     * @see #getModlets()
     * @deprecated As of JOMC 1.6, replaced by {@link #findModlets(org.jomc.modlet.Modlets)}. This method will be
     * removed in JOMC 2.0.
     */
    @Deprecated
    public abstract Modlets findModlets() throws ModelException;

    /**
     * Searches the context for {@code Modlets}.
     *
     * @param modlets The {@code Modlets} currently being searched.
     *
     * @return The {@code Modlets} found in the context or {@code null}.
     *
     * @throws NullPointerException if {@code modlets} is {@code null}.
     * @throws ModelException if searching {@code Modlets} fails.
     *
     * @see ModletProvider META-INF/services/org.jomc.modlet.ModletProvider
     * @see #getModlets()
     * @since 1.6
     */
    public abstract Modlets findModlets( Modlets modlets ) throws ModelException;

    /**
     * Processes a list of {@code Modlet}s.
     *
     * @param modlets The {@code Modlets} currently being processed.
     *
     * @return The processed {@code Modlets} or {@code null}.
     *
     * @throws NullPointerException if {@code modlets} is {@code null}.
     * @throws ModelException if processing {@code Modlets} fails.
     *
     * @see ModletProcessor META-INF/services/org.jomc.modlet.ModletProcessor
     * @see #getModlets()
     * @since 1.6
     */
    public abstract Modlets processModlets( Modlets modlets ) throws ModelException;

    /**
     * Validates a list of {@code Modlet}s.
     *
     * @param modlets The {@code Modlets} to validate.
     *
     * @return Validation report.
     *
     * @throws NullPointerException if {@code modlets} is {@code null}.
     * @throws ModelException if validating {@code modlets} fails.
     *
     * @see ModletValidator META-INF/services/org.jomc.modlet.ModletValidator
     * @see #getModlets()
     * @since 1.9
     */
    public abstract ModelValidationReport validateModlets( Modlets modlets ) throws ModelException;

    /**
     * Creates a new {@code Model} instance.
     *
     * @param model The identifier of the {@code Model} to create.
     *
     * @return A new instance of the {@code Model} identified by {@code model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if creating a new {@code Model} instance fails.
     *
     * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelProvider.class.getName(), ModelProvider.class )
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract Model findModel( String model ) throws ModelException;

    /**
     * Populates a given {@code Model} instance.
     *
     * @param model The {@code Model} to populate.
     *
     * @return The populated model.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if populating {@code model} fails.
     *
     * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelProvider.class.getName(), ModelProvider.class )
     *
     * @since 1.2
     */
    public abstract Model findModel( Model model ) throws ModelException;

    /**
     * Gets the name of the class providing the default {@code ModelContext} implementation.
     * <p>
     * The name of the class providing the default {@code ModelContext} implementation returned by method
     * {@link #createModelContext(java.lang.ClassLoader)} is controlled by system property
     * {@code org.jomc.modlet.ModelContext.className}. If that property is not set, the name of the
     * {@link org.jomc.modlet.DefaultModelContext} class is returned.
     * </p>
     *
     * @return The name of the class providing the default {@code ModelContext} implementation.
     *
     * @see #setModelContextClassName(java.lang.String)
     *
     * @deprecated As of JOMC 1.2, replaced by class {@link ModelContextFactory}. This method will be removed in version
     * 2.0.
     */
    @Deprecated
    public static String getModelContextClassName()
    {
        if ( modelContextClassName == null )
        {
            modelContextClassName = System.getProperty( "org.jomc.modlet.ModelContext.className",
                                                        DefaultModelContext.class.getName() );

        }

        return modelContextClassName;
    }

    /**
     * Sets the name of the class providing the default {@code ModelContext} implementation.
     *
     * @param value The new name of the class providing the default {@code ModelContext} implementation or {@code null}.
     *
     * @see #getModelContextClassName()
     *
     * @deprecated As of JOMC 1.2, replaced by class {@link ModelContextFactory}. This method will be removed in version
     * 2.0.
     */
    @Deprecated
    public static void setModelContextClassName( final String value )
    {
        modelContextClassName = value;
    }

    /**
     * Creates a new default {@code ModelContext} instance.
     *
     * @param classLoader The class loader to create a new default {@code ModelContext} instance with or {@code null},
     * to create a new context using the platform's bootstrap class loader.
     *
     * @return A new {@code ModelContext} instance.
     *
     * @throws ModelException if creating a new {@code ModelContext} instance fails.
     *
     * @see #getModelContextClassName()
     *
     * @deprecated As of JOMC 1.2, replaced by method {@link ModelContextFactory#newModelContext(java.lang.ClassLoader)}.
     * This method will be removed in version 2.0.
     */
    public static ModelContext createModelContext( final ClassLoader classLoader ) throws ModelException
    {
        if ( getModelContextClassName().equals( DefaultModelContext.class.getName() ) )
        {
            return new DefaultModelContext( classLoader );
        }

        try
        {
            final Class<?> clazz = Class.forName( getModelContextClassName(), false, classLoader );

            if ( !ModelContext.class.isAssignableFrom( clazz ) )
            {
                throw new ModelException( getMessage( "illegalContextImplementation", getModelContextClassName(),
                                                      ModelContext.class.getName() ) );

            }

            final Constructor<? extends ModelContext> ctor =
                clazz.asSubclass( ModelContext.class ).getDeclaredConstructor( ClassLoader.class );

            return ctor.newInstance( classLoader );
        }
        catch ( final ClassNotFoundException e )
        {
            throw new ModelException( getMessage( "contextClassNotFound", getModelContextClassName() ), e );
        }
        catch ( final NoSuchMethodException e )
        {
            throw new ModelException( getMessage( "contextConstructorNotFound", getModelContextClassName() ), e );
        }
        catch ( final InstantiationException e )
        {
            final String message = getMessage( e );
            throw new ModelException( getMessage( "contextInstantiationException", getModelContextClassName(),
                                                  message != null ? " " + message : "" ), e );

        }
        catch ( final IllegalAccessException e )
        {
            final String message = getMessage( e );
            throw new ModelException( getMessage( "contextConstructorAccessDenied", getModelContextClassName(),
                                                  message != null ? " " + message : "" ), e );

        }
        catch ( final InvocationTargetException e )
        {
            String message = getMessage( e );
            if ( message == null && e.getTargetException() != null )
            {
                message = getMessage( e.getTargetException() );
            }

            throw new ModelException( getMessage( "contextConstructorException", getModelContextClassName(),
                                                  message != null ? " " + message : "" ), e );

        }
    }

    /**
     * Processes a {@code Model}.
     *
     * @param model The {@code Model} to process.
     *
     * @return The processed {@code Model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if processing {@code model} fails.
     *
     * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelProcessor.class.getName(), ModelProcessor.class )
     */
    public abstract Model processModel( Model model ) throws ModelException;

    /**
     * Validates a given {@code Model}.
     *
     * @param model The {@code Model} to validate.
     *
     * @return Validation report.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if validating {@code model} fails.
     *
     * @see #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class) createServiceObjects( model, ModelValidator.class.getName(), ModelValidator.class )
     * @see ModelValidationReport#isModelValid()
     */
    public abstract ModelValidationReport validateModel( Model model ) throws ModelException;

    /**
     * Validates a given model.
     *
     * @param model The identifier of the {@code Model} to use for validating {@code source}.
     * @param source A source providing the model to validate.
     *
     * @return Validation report.
     *
     * @throws NullPointerException if {@code model} or {@code source} is {@code null}.
     * @throws ModelException if validating the model fails.
     *
     * @see #createSchema(java.lang.String)
     * @see ModelValidationReport#isModelValid()
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract ModelValidationReport validateModel( String model, Source source ) throws ModelException;

    /**
     * Creates a new SAX entity resolver instance of a given model.
     *
     * @param model The identifier of the model to create a new SAX entity resolver of.
     *
     * @return A new SAX entity resolver instance of the model identified by {@code model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if creating a new SAX entity resolver instance fails.
     *
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract EntityResolver createEntityResolver( String model ) throws ModelException;

    /**
     * Creates a new SAX entity resolver instance for a given public identifier URI.
     *
     * @param publicId The public identifier URI to create a new SAX entity resolver for.
     *
     * @return A new SAX entity resolver instance for the public identifier URI {@code publicId}.
     *
     * @throws NullPointerException if {@code publicId} is {@code null}.
     * @throws ModelException if creating a new SAX entity resolver instance fails.
     *
     * @see ModletObject#PUBLIC_ID
     * @since 1.2
     * @deprecated As of JOMC 1.8, removed without replacement. This method will be removed in JOMC 2.0.
     */
    @Deprecated
    public abstract EntityResolver createEntityResolver( URI publicId ) throws ModelException;

    /**
     * Creates a new L/S resource resolver instance of a given model.
     *
     * @param model The identifier of the model to create a new L/S resource resolver of.
     *
     * @return A new L/S resource resolver instance of the model identified by {@code model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if creating a new L/S resource resolver instance fails.
     *
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract LSResourceResolver createResourceResolver( String model ) throws ModelException;

    /**
     * Creates a new L/S resource resolver instance for a given public identifier URI.
     *
     * @param publicId The public identifier URI to create a new L/S resource resolver for.
     *
     * @return A new L/S resource resolver instance for the public identifier URI {@code publicId}.
     *
     * @throws NullPointerException if {@code publicId} is {@code null}.
     * @throws ModelException if creating a new L/S resource resolver instance fails.
     *
     * @see ModletObject#PUBLIC_ID
     * @since 1.2
     * @deprecated As of JOMC 1.8, removed without replacement. This method will be removed in JOMC 2.0.
     */
    @Deprecated
    public abstract LSResourceResolver createResourceResolver( URI publicId ) throws ModelException;

    /**
     * Creates a new JAXP schema instance of a given model.
     *
     * @param model The identifier of the model to create a new JAXP schema instance of.
     *
     * @return A new JAXP schema instance of the model identified by {@code model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if creating a new JAXP schema instance fails.
     *
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract javax.xml.validation.Schema createSchema( String model ) throws ModelException;

    /**
     * Creates a new JAXP schema instance for a given public identifier URI.
     *
     * @param publicId The public identifier URI to create a new JAXP schema instance for.
     *
     * @return A new JAXP schema instance for the public identifier URI {@code publicId}.
     *
     * @throws NullPointerException if {@code publicId} is {@code null}.
     * @throws ModelException if creating a new JAXP schema instance fails.
     *
     * @see ModletObject#PUBLIC_ID
     * @since 1.2
     * @deprecated As of JOMC 1.8, removed without replacement. This method will be removed in JOMC 2.0.
     */
    @Deprecated
    public abstract javax.xml.validation.Schema createSchema( URI publicId ) throws ModelException;

    /**
     * Creates a new JAXB context instance of a given model.
     *
     * @param model The identifier of the model to create a new JAXB context instance of.
     *
     * @return A new JAXB context instance of the model identified by {@code model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if creating a new JAXB context instance fails.
     *
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract JAXBContext createContext( String model ) throws ModelException;

    /**
     * Creates a new JAXB context instance for a given public identifier URI.
     *
     * @param publicId The public identifier URI to create a new JAXB context instance for.
     *
     * @return A new JAXB context instance for the public identifier URI {@code publicId}.
     *
     * @throws NullPointerException if {@code publicId} is {@code null}.
     * @throws ModelException if creating a new JAXB context instance fails.
     *
     * @see ModletObject#PUBLIC_ID
     * @since 1.2
     * @deprecated As of JOMC 1.8, removed without replacement. This method will be removed in JOMC 2.0.
     */
    @Deprecated
    public abstract JAXBContext createContext( URI publicId ) throws ModelException;

    /**
     * Creates a new JAXB marshaller instance of a given model.
     *
     * @param model The identifier of the model to create a new JAXB marshaller instance of.
     *
     * @return A new JAXB marshaller instance of the model identified by {@code model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if creating a new JAXB marshaller instance fails.
     *
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract Marshaller createMarshaller( String model ) throws ModelException;

    /**
     * Creates a new JAXB marshaller instance for a given public identifier URI.
     *
     * @param publicId The public identifier URI to create a new JAXB marshaller instance for.
     *
     * @return A new JAXB marshaller instance for the public identifier URI {@code publicId}.
     *
     * @throws NullPointerException if {@code publicId} is {@code null}.
     * @throws ModelException if creating a new JAXB marshaller instance fails.
     *
     * @see ModletObject#PUBLIC_ID
     * @since 1.2
     * @deprecated As of JOMC 1.8, removed without replacement. This method will be removed in JOMC 2.0.
     */
    @Deprecated
    public abstract Marshaller createMarshaller( URI publicId ) throws ModelException;

    /**
     * Creates a new JAXB unmarshaller instance of a given model.
     *
     * @param model The identifier of the model to create a new JAXB unmarshaller instance of.
     *
     * @return A new JAXB unmarshaller instance of the model identified by {@code model}.
     *
     * @throws NullPointerException if {@code model} is {@code null}.
     * @throws ModelException if creating a new JAXB unmarshaller instance fails.
     *
     * @see ModletObject#MODEL_PUBLIC_ID
     */
    public abstract Unmarshaller createUnmarshaller( String model ) throws ModelException;

    /**
     * Creates a new JAXB unmarshaller instance for a given given public identifier URI.
     *
     * @param publicId The public identifier URI to create a new JAXB unmarshaller instance for.
     *
     * @return A new JAXB unmarshaller instance for the public identifier URI {@code publicId}.
     *
     * @throws NullPointerException if {@code publicId} is {@code null}.
     * @throws ModelException if creating a new JAXB unmarshaller instance fails.
     *
     * @see ModletObject#PUBLIC_ID
     * @since 1.2
     * @deprecated As of JOMC 1.8, removed without replacement. This method will be removed in JOMC 2.0.
     */
    @Deprecated
    public abstract Unmarshaller createUnmarshaller( URI publicId ) throws ModelException;

    /**
     * Creates service objects of a model.
     *
     * @param <T> The type of the service.
     * @param model The identifier of the {@code Model} to create service objects of.
     * @param service The identifier of the service to create objects of.
     * @param type The class of the type of the service.
     *
     * @return An ordered, unmodifiable collection of new service objects identified by {@code service} of the model
     * identified by {@code model}.
     *
     * @throws NullPointerException if {@code model}, {@code service} or {@code type} is {@code null}.
     * @throws ModelException if creating service objects fails.
     *
     * @see ModelProvider
     * @see ModelProcessor
     * @see ModelValidator
     *
     * @since 1.9
     */
    public abstract <T> Collection<? extends T> createServiceObjects( final String model, final String service,
                                                                      final Class<T> type )
        throws ModelException;

    /**
     * Creates a new service object.
     *
     * @param <T> The type of the service.
     * @param service The service to create a new object of.
     * @param type The class of the type of the service.
     *
     * @return An new service object for {@code service}.
     *
     * @throws NullPointerException if {@code service} or {@code type} is {@code null}.
     * @throws ModelException if creating the service object fails.
     *
     * @see ModletProvider
     * @see ModletProcessor
     * @see ModletValidator
     * @see ServiceFactory
     *
     * @since 1.2
     * @deprecated As of JOMC 1.9, please use method {@link #createServiceObjects(java.lang.String, java.lang.String, java.lang.Class)}.
     * This method will be removed in JOMC 2.0.
     */
    @Deprecated
    public abstract <T> T createServiceObject( final Service service, final Class<T> type ) throws ModelException;

    private static String getMessage( final String key, final Object... args )
    {
        return MessageFormat.format( ResourceBundle.getBundle(
            ModelContext.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );

    }

    private static String getMessage( final Throwable t )
    {
        return t != null
                   ? t.getMessage() != null && t.getMessage().trim().length() > 0
                         ? t.getMessage()
                         : getMessage( t.getCause() )
                   : null;

    }

}