org.jomc.modlet.DefaultModelContext Maven / Gradle / Ivy
/*
* Copyright (C) Christian Schulte, 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: DefaultModelContext.java 4654 2012-11-15 22:28:26Z schulte $
*
*/
package org.jomc.modlet;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.logging.Level;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Default {@code ModelContext} implementation.
*
* @author Christian Schulte
* @version $JOMC: DefaultModelContext.java 4654 2012-11-15 22:28:26Z schulte $
* @see ModelContextFactory
*/
public class DefaultModelContext extends ModelContext
{
/**
* Constant for the name of the model context attribute backing property {@code providerLocation}.
* @see #getProviderLocation()
* @see ModelContext#getAttribute(java.lang.String)
* @since 1.2
*/
public static final String PROVIDER_LOCATION_ATTRIBUTE_NAME =
"org.jomc.modlet.DefaultModelContext.providerLocationAttribute";
/**
* Constant for the name of the model context attribute backing property {@code platformProviderLocation}.
* @see #getPlatformProviderLocation()
* @see ModelContext#getAttribute(java.lang.String)
* @since 1.2
*/
public static final String PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME =
"org.jomc.modlet.DefaultModelContext.platformProviderLocationAttribute";
/** Supported schema name extensions. */
private static final String[] SCHEMA_EXTENSIONS = new String[]
{
"xsd"
};
/**
* Class path location searched for providers by default.
* @see #getDefaultProviderLocation()
*/
private static final String DEFAULT_PROVIDER_LOCATION = "META-INF/services";
/**
* Location searched for platform providers by default.
* @see #getDefaultPlatformProviderLocation()
*/
private static final String DEFAULT_PLATFORM_PROVIDER_LOCATION =
new StringBuilder( 255 ).append( System.getProperty( "java.home" ) ).append( File.separator ).append( "lib" ).
append( File.separator ).append( "jomc.properties" ).toString();
/**
* Constant for the service identifier of marshaller listener services.
* @since 1.2
*/
private static final String MARSHALLER_LISTENER_SERVICE = "javax.xml.bind.Marshaller.Listener";
/**
* Constant for the service identifier of unmarshaller listener services.
* @since 1.2
*/
private static final String UNMARSHALLER_LISTENER_SERVICE = "javax.xml.bind.Unmarshaller.Listener";
/** Default provider location. */
private static volatile String defaultProviderLocation;
/** Default platform provider location. */
private static volatile String defaultPlatformProviderLocation;
/** Cached schema resources. */
private Reference> cachedSchemaResources = new SoftReference>( null );
/** Provider location of the instance. */
private String providerLocation;
/** Platform provider location of the instance. */
private String platformProviderLocation;
/**
* Creates a new {@code DefaultModelContext} instance.
* @since 1.2
*/
public DefaultModelContext()
{
super();
}
/**
* Creates a new {@code DefaultModelContext} instance taking a class loader.
*
* @param classLoader The class loader of the context.
*/
public DefaultModelContext( final ClassLoader classLoader )
{
super( classLoader );
}
/**
* Gets the default location searched for provider resources.
* The default provider location is controlled by system property
* {@code org.jomc.modlet.DefaultModelContext.defaultProviderLocation} holding the location to search
* for provider resources by default. If that property is not set, the {@code META-INF/services} default is
* returned.
*
* @return The location searched for provider resources by default.
*
* @see #setDefaultProviderLocation(java.lang.String)
*/
public static String getDefaultProviderLocation()
{
if ( defaultProviderLocation == null )
{
defaultProviderLocation = System.getProperty(
"org.jomc.modlet.DefaultModelContext.defaultProviderLocation", DEFAULT_PROVIDER_LOCATION );
}
return defaultProviderLocation;
}
/**
* Sets the default location searched for provider resources.
*
* @param value The new default location to search for provider resources or {@code null}.
*
* @see #getDefaultProviderLocation()
*/
public static void setDefaultProviderLocation( final String value )
{
defaultProviderLocation = value;
}
/**
* Gets the location searched for provider resources.
*
* @return The location searched for provider resources.
*
* @see #getDefaultProviderLocation()
* @see #setProviderLocation(java.lang.String)
* @see #PROVIDER_LOCATION_ATTRIBUTE_NAME
*/
public final String getProviderLocation()
{
if ( this.providerLocation == null )
{
this.providerLocation = getDefaultProviderLocation();
if ( DEFAULT_PROVIDER_LOCATION.equals( this.providerLocation )
&& this.getAttribute( PROVIDER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
{
final String contextProviderLocation = (String) this.getAttribute( PROVIDER_LOCATION_ATTRIBUTE_NAME );
if ( this.isLoggable( Level.CONFIG ) )
{
this.log( Level.CONFIG, getMessage( "contextProviderLocationInfo",
contextProviderLocation ), null );
}
this.providerLocation = null;
return contextProviderLocation;
}
else if ( this.isLoggable( Level.CONFIG ) )
{
this.log( Level.CONFIG, getMessage( "defaultProviderLocationInfo", this.providerLocation ), null );
}
}
return this.providerLocation;
}
/**
* Sets the location searched for provider resources.
*
* @param value The new location to search for provider resources or {@code null}.
*
* @see #getProviderLocation()
*/
public final void setProviderLocation( final String value )
{
this.providerLocation = value;
}
/**
* Gets the default location searched for platform provider resources.
* The default platform provider location is controlled by system property
* {@code org.jomc.modlet.DefaultModelContext.defaultPlatformProviderLocation} holding the location to
* search for platform provider resources by default. If that property is not set, the
* {@code /lib/jomc.properties} default is returned.
*
* @return The location searched for platform provider resources by default.
*
* @see #setDefaultPlatformProviderLocation(java.lang.String)
*/
public static String getDefaultPlatformProviderLocation()
{
if ( defaultPlatformProviderLocation == null )
{
defaultPlatformProviderLocation = System.getProperty(
"org.jomc.modlet.DefaultModelContext.defaultPlatformProviderLocation",
DEFAULT_PLATFORM_PROVIDER_LOCATION );
}
return defaultPlatformProviderLocation;
}
/**
* Sets the default location searched for platform provider resources.
*
* @param value The new default location to search for platform provider resources or {@code null}.
*
* @see #getDefaultPlatformProviderLocation()
*/
public static void setDefaultPlatformProviderLocation( final String value )
{
defaultPlatformProviderLocation = value;
}
/**
* Gets the location searched for platform provider resources.
*
* @return The location searched for platform provider resources.
*
* @see #getDefaultPlatformProviderLocation()
* @see #setPlatformProviderLocation(java.lang.String)
* @see #PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME
*/
public final String getPlatformProviderLocation()
{
if ( this.platformProviderLocation == null )
{
this.platformProviderLocation = getDefaultPlatformProviderLocation();
if ( DEFAULT_PLATFORM_PROVIDER_LOCATION.equals( this.platformProviderLocation )
&& this.getAttribute( PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME ) instanceof String )
{
final String contextPlatformProviderLocation =
(String) this.getAttribute( PLATFORM_PROVIDER_LOCATION_ATTRIBUTE_NAME );
if ( this.isLoggable( Level.CONFIG ) )
{
this.log( Level.CONFIG, getMessage( "contextPlatformProviderLocationInfo",
contextPlatformProviderLocation ), null );
}
this.platformProviderLocation = null;
return contextPlatformProviderLocation;
}
else if ( this.isLoggable( Level.CONFIG ) )
{
this.log( Level.CONFIG,
getMessage( "defaultPlatformProviderLocationInfo", this.platformProviderLocation ), null );
}
}
return this.platformProviderLocation;
}
/**
* Sets the location searched for platform provider resources.
*
* @param value The new location to search for platform provider resources or {@code null}.
*
* @see #getPlatformProviderLocation()
*/
public final void setPlatformProviderLocation( final String value )
{
this.platformProviderLocation = value;
}
/**
* {@inheritDoc}
* This method loads {@code ModletProvider} classes setup via the platform provider configuration file and
* {@code /org.jomc.modlet.ModletProvider} resources to return a list of {@code Modlets}.
*
* @see #getProviderLocation()
* @see #getPlatformProviderLocation()
* @see ModletProvider#findModlets(org.jomc.modlet.ModelContext)
*/
@Override
public Modlets findModlets() throws ModelException
{
final Modlets modlets = new Modlets();
final Collection providers = this.loadProviders( ModletProvider.class );
for ( ModletProvider provider : providers )
{
if ( this.isLoggable( Level.FINER ) )
{
this.log( Level.FINER, getMessage( "creatingModlets", provider.toString() ), null );
}
final Modlets provided = provider.findModlets( this );
if ( provided != null )
{
if ( this.isLoggable( Level.FINEST ) )
{
for ( Modlet m : provided.getModlet() )
{
this.log( Level.FINEST,
getMessage( "modletInfo", m.getName(), m.getModel(),
m.getVendor() != null
? m.getVendor() : getMessage( "noVendor" ),
m.getVersion() != null
? m.getVersion() : getMessage( "noVersion" ) ), null );
if ( m.getSchemas() != null )
{
for ( Schema s : m.getSchemas().getSchema() )
{
this.log( Level.FINEST,
getMessage( "modletSchemaInfo", m.getName(), s.getPublicId(), s.getSystemId(),
s.getContextId() != null
? s.getContextId() : getMessage( "noContext" ),
s.getClasspathId() != null
? s.getClasspathId() : getMessage( "noClasspathId" ) ), null );
}
}
if ( m.getServices() != null )
{
for ( Service s : m.getServices().getService() )
{
this.log( Level.FINEST, getMessage( "modletServiceInfo", m.getName(), s.getOrdinal(),
s.getIdentifier(), s.getClazz() ), null );
}
}
}
}
modlets.getModlet().addAll( provided.getModlet() );
}
}
return modlets;
}
/**
* {@inheritDoc}
* This method loads all {@code ModelProvider} service classes of the model identified by {@code model} to create
* a new {@code Model} instance.
*
* @see #findModel(org.jomc.modlet.Model)
* @see ModelProvider#findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
*/
@Override
public Model findModel( final String model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
final Model m = new Model();
m.setIdentifier( model );
return this.findModel( m );
}
/**
* {@inheritDoc}
* This method loads all {@code ModelProvider} service classes of the given model to populate the given model
* instance.
*
* @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class)
* @see ModelProvider#findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
*
* @since 1.2
*/
@Override
public Model findModel( final Model model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
Model m = model.clone();
final Services services = this.getModlets().getServices( m.getIdentifier() );
if ( services != null )
{
for ( Service service : services.getServices( ModelProvider.class ) )
{
final ModelProvider modelProvider = this.createServiceObject( service, ModelProvider.class );
if ( this.isLoggable( Level.FINER ) )
{
this.log( Level.FINER, getMessage( "creatingModel", m.getIdentifier(), modelProvider.toString() ),
null );
}
final Model provided = modelProvider.findModel( this, m );
if ( provided != null )
{
m = provided;
}
}
}
return m;
}
/**
* {@inheritDoc}
* @since 1.2
*/
@Override
public T createServiceObject( final Service service, final Class type ) throws ModelException
{
if ( service == null )
{
throw new NullPointerException( "service" );
}
if ( type == null )
{
throw new NullPointerException( "type" );
}
try
{
final Class> clazz = this.findClass( service.getClazz() );
if ( clazz == null )
{
throw new ModelException( getMessage( "serviceNotFound", service.getOrdinal(), service.getIdentifier(),
service.getClazz() ) );
}
if ( !type.isAssignableFrom( clazz ) )
{
throw new ModelException( getMessage( "illegalService", service.getOrdinal(), service.getIdentifier(),
service.getClazz(), type.getName() ) );
}
final T serviceObject = clazz.asSubclass( type ).newInstance();
for ( int i = 0, s0 = service.getProperty().size(); i < s0; i++ )
{
final Property p = service.getProperty().get( i );
this.setProperty( serviceObject, p.getName(), p.getValue() );
}
return serviceObject;
}
catch ( final InstantiationException e )
{
throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e );
}
catch ( final IllegalAccessException e )
{
throw new ModelException( getMessage( "failedCreatingObject", service.getClazz() ), e );
}
}
@Override
public EntityResolver createEntityResolver( final String model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
return this.createEntityResolver( this.getModlets().getSchemas( model ) );
}
@Override
public EntityResolver createEntityResolver( final URI publicId ) throws ModelException
{
if ( publicId == null )
{
throw new NullPointerException( "publicId" );
}
return this.createEntityResolver( this.getModlets().getSchemas( publicId ) );
}
@Override
public LSResourceResolver createResourceResolver( final String model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
return this.createResourceResolver( this.createEntityResolver( model ) );
}
@Override
public LSResourceResolver createResourceResolver( final URI publicId ) throws ModelException
{
if ( publicId == null )
{
throw new NullPointerException( "publicId" );
}
return this.createResourceResolver( this.createEntityResolver( publicId ) );
}
@Override
public javax.xml.validation.Schema createSchema( final String model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
return this.createSchema( this.getModlets().getSchemas( model ), this.createEntityResolver( model ),
this.createResourceResolver( model ), model, null );
}
@Override
public javax.xml.validation.Schema createSchema( final URI publicId ) throws ModelException
{
if ( publicId == null )
{
throw new NullPointerException( "publicId" );
}
return this.createSchema( this.getModlets().getSchemas( publicId ), this.createEntityResolver( publicId ),
this.createResourceResolver( publicId ), null, publicId );
}
@Override
public JAXBContext createContext( final String model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
return this.createContext( this.getModlets().getSchemas( model ), model, null );
}
@Override
public JAXBContext createContext( final URI publicId ) throws ModelException
{
if ( publicId == null )
{
throw new NullPointerException( "publicId" );
}
return this.createContext( this.getModlets().getSchemas( publicId ), null, publicId );
}
@Override
public Marshaller createMarshaller( final String model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
return this.createMarshaller( this.getModlets().getSchemas( model ), this.getModlets().getServices( model ),
model, null );
}
@Override
public Marshaller createMarshaller( final URI publicId ) throws ModelException
{
if ( publicId == null )
{
throw new NullPointerException( "publicId" );
}
return this.createMarshaller( this.getModlets().getSchemas( publicId ), null, null, publicId );
}
@Override
public Unmarshaller createUnmarshaller( final String model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
return this.createUnmarshaller( this.getModlets().getSchemas( model ), this.getModlets().getServices( model ),
model, null );
}
@Override
public Unmarshaller createUnmarshaller( final URI publicId ) throws ModelException
{
if ( publicId == null )
{
throw new NullPointerException( "publicId" );
}
return this.createUnmarshaller( this.getModlets().getSchemas( publicId ), null, null, publicId );
}
/**
* {@inheritDoc}
* This method loads all {@code ModelProcessor} service classes of {@code model} to process the given
* {@code Model}.
*
* @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class)
* @see ModelProcessor#processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
*/
@Override
public Model processModel( final Model model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
Model processed = model;
final Services services = this.getModlets().getServices( model.getIdentifier() );
if ( services != null )
{
for ( Service service : services.getServices( ModelProcessor.class ) )
{
final ModelProcessor modelProcessor = this.createServiceObject( service, ModelProcessor.class );
if ( this.isLoggable( Level.FINER ) )
{
this.log( Level.FINER, getMessage( "processingModel", model.getIdentifier(),
modelProcessor.toString() ), null );
}
final Model current = modelProcessor.processModel( this, processed );
if ( current != null )
{
processed = current;
}
}
}
return processed;
}
/**
* {@inheritDoc}
* This method loads all {@code ModelValidator} service classes of {@code model} to validate the given
* {@code Model}.
*
* @see #createServiceObject(org.jomc.modlet.Service, java.lang.Class)
* @see ModelValidator#validateModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
*/
@Override
public ModelValidationReport validateModel( final Model model ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
final Services services = this.getModlets().getServices( model.getIdentifier() );
final ModelValidationReport report = new ModelValidationReport();
if ( services != null )
{
for ( Service service : services.getServices( ModelValidator.class ) )
{
final ModelValidator modelValidator = this.createServiceObject( service, ModelValidator.class );
if ( this.isLoggable( Level.FINER ) )
{
this.log( Level.FINER, getMessage( "validatingModel", model.getIdentifier(),
modelValidator.toString() ), null );
}
final ModelValidationReport current = modelValidator.validateModel( this, model );
if ( current != null )
{
report.getDetails().addAll( current.getDetails() );
}
}
}
return report;
}
/**
* {@inheritDoc}
*
* @see #createSchema(java.lang.String)
*/
@Override
public ModelValidationReport validateModel( final String model, final Source source ) throws ModelException
{
if ( model == null )
{
throw new NullPointerException( "model" );
}
if ( source == null )
{
throw new NullPointerException( "source" );
}
final javax.xml.validation.Schema schema = this.createSchema( model );
final Validator validator = schema.newValidator();
final ModelErrorHandler modelErrorHandler = new ModelErrorHandler( this );
validator.setErrorHandler( modelErrorHandler );
try
{
validator.validate( source );
}
catch ( final SAXException e )
{
String message = getMessage( e );
if ( message == null && e.getException() != null )
{
message = getMessage( e.getException() );
}
if ( this.isLoggable( Level.FINE ) )
{
this.log( Level.FINE, message, e );
}
if ( modelErrorHandler.getReport().isModelValid() )
{
throw new ModelException( message, e );
}
}
catch ( final IOException e )
{
throw new ModelException( getMessage( e ), e );
}
return modelErrorHandler.getReport();
}
private Collection loadProviders( final Class providerClass ) throws ModelException
{
try
{
final String providerNamePrefix = providerClass.getName() + ".";
final Map providers = new TreeMap( new Comparator()
{
public int compare( final String key1, final String key2 )
{
return key1.compareTo( key2 );
}
} );
final File platformProviders = new File( this.getPlatformProviderLocation() );
if ( platformProviders.exists() )
{
if ( this.isLoggable( Level.FINEST ) )
{
this.log( Level.FINEST, getMessage( "processing", platformProviders.getAbsolutePath() ), null );
}
InputStream in = null;
boolean suppressExceptionOnClose = true;
final java.util.Properties p = new java.util.Properties();
try
{
in = new FileInputStream( platformProviders );
p.load( in );
suppressExceptionOnClose = false;
}
finally
{
try
{
if ( in != null )
{
in.close();
}
}
catch ( final IOException e )
{
if ( suppressExceptionOnClose )
{
this.log( Level.SEVERE, getMessage( e ), e );
}
else
{
throw e;
}
}
}
for ( Map.Entry