MergeModulesTask.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: MergeModulesTask.java 5043 2015-05-27 07:03:39Z schulte $
*
*/
package org.jomc.ant;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBResult;
import javax.xml.bind.util.JAXBSource;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.jomc.ant.types.NameType;
import org.jomc.ant.types.ResourceType;
import org.jomc.ant.types.TransformerResourceType;
import org.jomc.model.Module;
import org.jomc.model.Modules;
import org.jomc.model.ObjectFactory;
import org.jomc.model.modlet.DefaultModelProvider;
import org.jomc.modlet.ModelContext;
import org.jomc.modlet.ModelException;
import org.jomc.modlet.ModelValidationReport;
/**
* Task for merging module resources.
*
* @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
* @version $JOMC: MergeModulesTask.java 5043 2015-05-27 07:03:39Z schulte $
*/
public final class MergeModulesTask extends JomcModelTask
{
/**
* The encoding of the module resource.
*/
private String moduleEncoding;
/**
* File to write the merged module to.
*/
private File moduleFile;
/**
* The name of the merged module.
*/
private String moduleName;
/**
* The version of the merged module.
*/
private String moduleVersion;
/**
* The vendor of the merged module.
*/
private String moduleVendor;
/**
* Included modules.
*/
private Set<NameType> moduleIncludes;
/**
* Excluded modules.
*/
private Set<NameType> moduleExcludes;
/**
* XSLT documents to use for transforming model objects.
*/
private List<TransformerResourceType> modelObjectStylesheetResources;
/**
* Creates a new {@code MergeModulesTask} instance.
*/
public MergeModulesTask()
{
super();
}
/**
* Gets the file to write the merged module to.
*
* @return The file to write the merged module to or {@code null}.
*
* @see #setModuleFile(java.io.File)
*/
public File getModuleFile()
{
return this.moduleFile;
}
/**
* Sets the file to write the merged module to.
*
* @param value The new file to write the merged module to or {@code null}.
*
* @see #getModuleFile()
*/
public void setModuleFile( final File value )
{
this.moduleFile = value;
}
/**
* Gets the encoding of the module resource.
*
* @return The encoding of the module resource.
*
* @see #setModuleEncoding(java.lang.String)
*/
public String getModuleEncoding()
{
if ( this.moduleEncoding == null )
{
this.moduleEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
}
return this.moduleEncoding;
}
/**
* Sets the encoding of the module resource.
*
* @param value The new encoding of the module resource or {@code null}.
*
* @see #getModuleEncoding()
*/
public void setModuleEncoding( final String value )
{
this.moduleEncoding = value;
}
/**
* Gets the name of the merged module.
*
* @return The name of the merged module or {@code null}.
*
* @see #setModuleName(java.lang.String)
*/
public String getModuleName()
{
return this.moduleName;
}
/**
* Sets the name of the merged module.
*
* @param value The new name of the merged module or {@code null}.
*
* @see #getModuleName()
*/
public void setModuleName( final String value )
{
this.moduleName = value;
}
/**
* Gets the version of the merged module.
*
* @return The version of the merged module or {@code null}.
*
* @see #setModuleVersion(java.lang.String)
*/
public String getModuleVersion()
{
return this.moduleVersion;
}
/**
* Sets the version of the merged module.
*
* @param value The new version of the merged module or {@code null}.
*
* @see #getModuleVersion()
*/
public void setModuleVersion( final String value )
{
this.moduleVersion = value;
}
/**
* Gets the vendor of the merged module.
*
* @return The vendor of the merge module or {@code null}.
*
* @see #setModuleVendor(java.lang.String)
*/
public String getModuleVendor()
{
return this.moduleVendor;
}
/**
* Sets the vendor of the merged module.
*
* @param value The new vendor of the merged module or {@code null}.
*
* @see #getModuleVendor()
*/
public void setModuleVendor( final String value )
{
this.moduleVendor = value;
}
/**
* Gets a set of module names to include.
* <p>
* This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
* to the returned set will be present inside the object. This is why there is no {@code set} method for the
* module includes property.
* </p>
*
* @return A set of module names to include.
*
* @see #createModuleInclude()
*/
public Set<NameType> getModuleIncludes()
{
if ( this.moduleIncludes == null )
{
this.moduleIncludes = new HashSet<NameType>();
}
return this.moduleIncludes;
}
/**
* Creates a new {@code moduleInclude} element instance.
*
* @return A new {@code moduleInclude} element instance.
*
* @see #getModuleIncludes()
*/
public NameType createModuleInclude()
{
final NameType moduleInclude = new NameType();
this.getModuleIncludes().add( moduleInclude );
return moduleInclude;
}
/**
* Gets a set of module names to exclude.
* <p>
* This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
* to the returned set will be present inside the object. This is why there is no {@code set} method for the
* module excludes property.
* </p>
*
* @return A set of module names to exclude.
*
* @see #createModuleExclude()
*/
public Set<NameType> getModuleExcludes()
{
if ( this.moduleExcludes == null )
{
this.moduleExcludes = new HashSet<NameType>();
}
return this.moduleExcludes;
}
/**
* Creates a new {@code moduleExclude} element instance.
*
* @return A new {@code moduleExclude} element instance.
*
* @see #getModuleExcludes()
*/
public NameType createModuleExclude()
{
final NameType moduleExclude = new NameType();
this.getModuleExcludes().add( moduleExclude );
return moduleExclude;
}
/**
* Gets the XSLT documents to use for transforming model objects.
* <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
* model object stylesheet resources property.
* </p>
*
* @return The XSLT documents to use for transforming model objects.
*
* @see #createModelObjectStylesheetResource()
*/
public List<TransformerResourceType> getModelObjectStylesheetResources()
{
if ( this.modelObjectStylesheetResources == null )
{
this.modelObjectStylesheetResources = new LinkedList<TransformerResourceType>();
}
return this.modelObjectStylesheetResources;
}
/**
* Creates a new {@code modelObjectStylesheetResource} element instance.
*
* @return A new {@code modelObjectStylesheetResource} element instance.
*
* @see #getModelObjectStylesheetResources()
*/
public TransformerResourceType createModelObjectStylesheetResource()
{
final TransformerResourceType modelObjectStylesheetResource = new TransformerResourceType();
this.getModelObjectStylesheetResources().add( modelObjectStylesheetResource );
return modelObjectStylesheetResource;
}
/**
* {@inheritDoc}
*/
@Override
public void preExecuteTask() throws BuildException
{
super.preExecuteTask();
this.assertNotNull( "moduleFile", this.getModuleFile() );
this.assertNotNull( "moduleName", this.getModuleName() );
this.assertNamesNotNull( this.getModuleExcludes() );
this.assertNamesNotNull( this.getModuleIncludes() );
this.assertLocationsNotNull( this.getModelObjectStylesheetResources() );
}
/**
* Merges module resources.
*
* @throws BuildException if merging module resources fails.
*/
@Override
public void executeTask() throws BuildException
{
ProjectClassLoader classLoader = null;
boolean suppressExceptionOnClose = true;
try
{
this.log( Messages.getMessage( "mergingModules", this.getModel() ) );
classLoader = this.newProjectClassLoader();
final Modules modules = new Modules();
final Set<ResourceType> resources = new HashSet<ResourceType>( this.getModuleResources() );
final ModelContext context = this.newModelContext( classLoader );
final Marshaller marshaller = context.createMarshaller( this.getModel() );
final Unmarshaller unmarshaller = context.createUnmarshaller( this.getModel() );
if ( this.isModelResourceValidationEnabled() )
{
unmarshaller.setSchema( context.createSchema( this.getModel() ) );
}
if ( resources.isEmpty() )
{
final ResourceType defaultResource = new ResourceType();
defaultResource.setLocation( DefaultModelProvider.getDefaultModuleLocation() );
defaultResource.setOptional( true );
resources.add( defaultResource );
}
for ( final ResourceType resource : resources )
{
final URL[] urls = this.getResources( context, resource.getLocation() );
if ( urls.length == 0 )
{
if ( resource.isOptional() )
{
this.logMessage( Level.WARNING, Messages.getMessage( "moduleResourceNotFound",
resource.getLocation() ) );
}
else
{
throw new BuildException(
Messages.getMessage( "moduleResourceNotFound", resource.getLocation() ),
this.getLocation() );
}
}
for ( int i = urls.length - 1; i >= 0; i-- )
{
InputStream in = null;
suppressExceptionOnClose = true;
try
{
this.logMessage( Level.FINEST, Messages.getMessage( "reading", urls[i].toExternalForm() ) );
final URLConnection con = urls[i].openConnection();
con.setConnectTimeout( resource.getConnectTimeout() );
con.setReadTimeout( resource.getReadTimeout() );
con.connect();
in = con.getInputStream();
final Source source = new StreamSource( in, urls[i].toURI().toASCIIString() );
Object o = unmarshaller.unmarshal( source );
if ( o instanceof JAXBElement<?> )
{
o = ( (JAXBElement<?>) o ).getValue();
}
if ( o instanceof Module )
{
modules.getModule().add( (Module) o );
}
else if ( o instanceof Modules )
{
modules.getModule().addAll( ( (Modules) o ).getModule() );
}
else
{
this.log( Messages.getMessage( "unsupportedModuleResource", urls[i].toExternalForm() ),
Project.MSG_WARN );
}
suppressExceptionOnClose = false;
}
catch ( final SocketTimeoutException e )
{
String message = Messages.getMessage( e );
message = Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" );
if ( resource.isOptional() )
{
this.getProject().log( message, e, Project.MSG_WARN );
}
else
{
throw new BuildException( message, e, this.getLocation() );
}
}
catch ( final IOException e )
{
String message = Messages.getMessage( e );
message = Messages.getMessage( "resourceFailure", message != null ? " " + message : "" );
if ( resource.isOptional() )
{
this.getProject().log( message, e, Project.MSG_WARN );
}
else
{
throw new BuildException( message, e, this.getLocation() );
}
}
finally
{
try
{
if ( in != null )
{
in.close();
}
}
catch ( final IOException e )
{
if ( suppressExceptionOnClose )
{
this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
}
else
{
throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
}
}
}
}
suppressExceptionOnClose = true;
}
for ( final Iterator<Module> it = modules.getModule().iterator(); it.hasNext(); )
{
final Module module = it.next();
if ( !this.isModuleIncluded( module ) || this.isModuleExcluded( module ) )
{
it.remove();
this.log( Messages.getMessage( "excludingModule", module.getName() ) );
}
else
{
this.log( Messages.getMessage( "includingModule", module.getName() ) );
}
}
Module classpathModule = null;
if ( this.isModelObjectClasspathResolutionEnabled() )
{
classpathModule = modules.getClasspathModule( Modules.getDefaultClasspathModuleName(), classLoader );
if ( classpathModule != null && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
{
modules.getModule().add( classpathModule );
}
else
{
classpathModule = null;
}
}
final ModelValidationReport validationReport = context.validateModel(
this.getModel(), new JAXBSource( marshaller, new ObjectFactory().createModules( modules ) ) );
this.logValidationReport( context, validationReport );
if ( !validationReport.isModelValid() )
{
throw new ModelException( Messages.getMessage( "invalidModel", this.getModel() ) );
}
if ( classpathModule != null )
{
modules.getModule().remove( classpathModule );
}
Module mergedModule = modules.getMergedModule( this.getModuleName() );
mergedModule.setVendor( this.getModuleVendor() );
mergedModule.setVersion( this.getModuleVersion() );
for ( int i = 0, s0 = this.getModelObjectStylesheetResources().size(); i < s0; i++ )
{
final Transformer transformer =
this.getTransformer( this.getModelObjectStylesheetResources().get( i ) );
if ( transformer != null )
{
final JAXBSource source =
new JAXBSource( marshaller, new ObjectFactory().createModule( mergedModule ) );
final JAXBResult result = new JAXBResult( unmarshaller );
transformer.transform( source, result );
if ( result.getResult() instanceof JAXBElement<?>
&& ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Module )
{
mergedModule = (Module) ( (JAXBElement<?>) result.getResult() ).getValue();
}
else
{
throw new BuildException( Messages.getMessage(
"illegalTransformationResult",
this.getModelObjectStylesheetResources().get( i ).getLocation() ), this.getLocation() );
}
}
}
this.log( Messages.getMessage( "writingEncoded", this.getModuleFile().getAbsolutePath(),
this.getModuleEncoding() ) );
marshaller.setProperty( Marshaller.JAXB_ENCODING, this.getModuleEncoding() );
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
marshaller.setSchema( context.createSchema( this.getModel() ) );
marshaller.marshal( new ObjectFactory().createModule( mergedModule ), this.getModuleFile() );
suppressExceptionOnClose = false;
}
catch ( final URISyntaxException e )
{
throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
}
catch ( final JAXBException e )
{
String message = Messages.getMessage( e );
if ( message == null )
{
message = Messages.getMessage( e.getLinkedException() );
}
throw new BuildException( message, e, this.getLocation() );
}
catch ( final TransformerConfigurationException e )
{
throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
}
catch ( final TransformerException e )
{
throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
}
catch ( final ModelException e )
{
throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
}
finally
{
try
{
if ( classLoader != null )
{
classLoader.close();
}
}
catch ( final IOException e )
{
if ( suppressExceptionOnClose )
{
this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
}
else
{
throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
}
}
}
}
/**
* Tests inclusion of a given module based on property {@code moduleIncludes}.
*
* @param module The module to test.
*
* @return {@code true}, if {@code module} is included based on property {@code moduleIncludes}.
*
* @throws NullPointerException if {@code module} is {@code null}.
*
* @see #getModuleIncludes()
*/
public boolean isModuleIncluded( final Module module )
{
if ( module == null )
{
throw new NullPointerException( "module" );
}
for ( final NameType include : this.getModuleIncludes() )
{
if ( include.getName().equals( module.getName() ) )
{
return true;
}
}
return this.getModuleIncludes().isEmpty() ? true : false;
}
/**
* Tests exclusion of a given module based on property {@code moduleExcludes}.
*
* @param module The module to test.
*
* @return {@code true}, if {@code module} is excluded based on property {@code moduleExcludes}.
*
* @throws NullPointerException if {@code module} is {@code null}.
*
* @see #getModuleExcludes()
*/
public boolean isModuleExcluded( final Module module )
{
if ( module == null )
{
throw new NullPointerException( "module" );
}
for ( final NameType exclude : this.getModuleExcludes() )
{
if ( exclude.getName().equals( module.getName() ) )
{
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public MergeModulesTask clone()
{
final MergeModulesTask clone = (MergeModulesTask) super.clone();
clone.moduleFile = this.moduleFile != null ? new File( this.moduleFile.getAbsolutePath() ) : null;
if ( this.moduleExcludes != null )
{
clone.moduleExcludes = new HashSet<NameType>( this.moduleExcludes.size() );
for ( final NameType e : this.moduleExcludes )
{
clone.moduleExcludes.add( e.clone() );
}
}
if ( this.moduleIncludes != null )
{
clone.moduleIncludes = new HashSet<NameType>( this.moduleIncludes.size() );
for ( final NameType e : this.moduleIncludes )
{
clone.moduleIncludes.add( e.clone() );
}
}
if ( this.modelObjectStylesheetResources != null )
{
clone.modelObjectStylesheetResources =
new ArrayList<TransformerResourceType>( this.modelObjectStylesheetResources.size() );
for ( final TransformerResourceType e : this.modelObjectStylesheetResources )
{
clone.modelObjectStylesheetResources.add( e.clone() );
}
}
return clone;
}
}