org.codehaus.mojo.jaxb2.AbstractSchemagenMojo Maven / Gradle / Ivy
Show all versions of jaxb2-maven-plugin Show documentation
package org.codehaus.mojo.jaxb2;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.codehaus.mojo.jaxb2.helpers.SchemagenHelper;
import org.codehaus.mojo.jaxb2.helpers.SimpleNamespaceResolver;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.Scanner;
import org.codehaus.plexus.util.StringUtils;
import org.sonatype.plexus.build.incremental.BuildContext;
import org.sonatype.plexus.build.incremental.DefaultBuildContext;
import com.sun.tools.jxc.SchemaGenerator;
/**
* @author rfscholte
* @since 1.3
*/
public abstract class AbstractSchemagenMojo
extends AbstractMojo
{
/**
* Name of the generated schema file, emitted by the SchemaGenerator.
* According to the JAXB Schema Generator documentation:
* "There is no way to control the name of the generated schema files at this time."
*/
private static final String SCHEMAGEN_EMITTED_FILENAME = "schema1.xsd";
/**
* @component
*/
private BuildContext buildContext;
/**
* The default maven project object.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* A List holding desired schema mappings, each of which binds a schema namespace URI to its desired prefix
* [optional] and the name of the resulting schema file [optional]. All given elements (uri, prefix, file) must be
* unique within the configuration; no two elements may have the same values.
*
*
*
* The example schema configuration below maps two namespace uris to prefixes and generated file names. This implies
* that http://some/namespace will be represented by the prefix some within the generated XML
* Schema files; creating namespace definitions on the form xmlns:some="http://some/namespace", and
* corresponding uses on the form <xs:element minOccurs="0"
* ref="some:anOptionalElementInSomeNamespace"/>. Moreover, the file element defines that the
* http://some/namespace definitions will be written to the file some_schema.xsd, and that all
* import references will be on the form <xs:import namespace="http://some/namespace"
* schemaLocation="some_schema.xsd"/>
*
*
*
* The example configuration below also performs identical operations for the namespace uri
* http://another/namespace with the prefix another and the file another_schema.xsd.
*
*
*
*
* <schemas>
* <schema>
* <uri>http://some/namespace</uri>
* <toPrefix>some</toPrefix>
* <toFile>some_schema.xsd</toFile>
* <schema>
* <uri>http://another/namespace</uri>
* <toPrefix>another</toPrefix>
* <toFile>another_schema.xsd</toFile>
* </schema>
* </schemas>
*
*
* @parameter
* @since 1.4
*/
private List transformSchemas;
/**
* A list of inclusion filters for the generator. At least one file has to be specified.
*
* @parameter
* @required
*/
private Set includes = new HashSet();
/**
* A list of exclusion filters for the generator.
*
* @parameter
*/
private Set excludes = new HashSet();
/**
* The granularity in milliseconds of the last modification date for testing whether a source needs recompilation.
*
* @parameter expression="${lastModGranularityMs}" default-value="0"
*/
private int staleMillis;
public void execute()
throws MojoExecutionException, MojoFailureException
{
if ( getLog().isDebugEnabled() )
{
Package jaxbImplPackage = SchemaGenerator.class.getPackage();
getLog().debug( "Using SchemaGen of " + jaxbImplPackage.getImplementationTitle() + " version "
+ jaxbImplPackage.getImplementationVersion() );
}
if ( "pom".equals( project.getPackaging() ) )
{
return;
}
if ( includes.isEmpty() )
{
throw new MojoExecutionException( "At least one file has to be included" );
}
if ( isOutputStale() )
{
String includePaths = StringUtils.join( includes.toArray(), "," );
String excludePaths = StringUtils.join( excludes.toArray(), "," );
Set includedSources = new HashSet();
for ( String path : getCompileSourceRoots() )
{
File sourceDir = new File( path );
if ( sourceDir.exists() && sourceDir.isDirectory() )
{
try
{
includedSources.addAll( FileUtils.getFileNames( sourceDir, includePaths, excludePaths, true ) );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error retrieving files in: \'" + sourceDir + "\' ", e );
}
}
else
{
getLog().info( "Source directory \'" + sourceDir
+ "\' doesn't exist. Ignoring directory in schema generation." );
}
}
List args = new ArrayList();
StringBuilder classPath = new StringBuilder();
try
{
List classpathFiles = getClasspathElements( project );
classPath = new StringBuilder();
for ( String classpathFile : classpathFiles )
{
classPath.append( classpathFile );
classPath.append( File.pathSeparatorChar );
}
}
catch ( DependencyResolutionRequiredException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
if ( !getOutputDirectory().exists() && !getOutputDirectory().mkdirs() )
{
throw new MojoExecutionException( "Could not create directory "
+ getOutputDirectory().getAbsolutePath() );
}
try
{
args.add( "-d" );
args.add( getOutputDirectory().getAbsolutePath() );
args.add( "-classpath" );
args.add( classPath.toString() );
args.addAll( includedSources );
if ( getLog().isDebugEnabled() )
{
getLog().debug( "Args for SchemaGenerator: " + args );
}
SchemaGenerator.run( args.toArray( new String[0] ) );
}
catch ( Exception e )
{
throw new MojoExecutionException( "Failed to generate schema", e );
}
if ( transformSchemas != null )
{
// Check configuration - we cannot have duplicate namespace URI, prefix or file.
SchemagenHelper.validateSchemasInPluginConfiguration( transformSchemas );
if ( hasRenamingSchemas() )
{
try
{
FileUtils.copyDirectory( getOutputDirectory(), getWorkDirectory() );
}
catch ( IOException e )
{
throw new MojoExecutionException( e.getMessage() );
}
}
// Acquire resolvers for all generated files.
final Map resolverMap =
SchemagenHelper.getFileNameToResolverMap( getOutputDirectory() );
// Transform all namespace prefixes as requested.
SchemagenHelper.replaceNamespacePrefixes( resolverMap, transformSchemas, getLog(), getOutputDirectory() );
// Rename all generated schema files as requested.
SchemagenHelper.renameGeneratedSchemaFiles( resolverMap, transformSchemas, getLog(),
getOutputDirectory() );
}
buildContext.refresh( getOutputDirectory() );
}
else
{
getLog().info( "No updated sources found - skipping schema generation." );
}
}
/**
* Checks if there have been any changes to the sources since the last build and therefore the generated files are
* not up-to-date and need to be re-generated.
*/
private boolean isOutputStale()
throws MojoExecutionException
{
if ( buildContext instanceof DefaultBuildContext )
{
// Here we can't use the buildContext to determine if everything is up-to-date, as
// DefaultBuildContext behaves as if all files were just created.
return commandLineStalenessCheck();
}
else
{
return buildContextStalenessCheck();
}
}
private boolean commandLineStalenessCheck()
throws MojoExecutionException
{
SourceInclusionScanner staleSourceScanner = new StaleSourceScanner( staleMillis, includes, excludes );
SourceMapping mapping = new SingleTargetSourceMapping( ".java", SCHEMAGEN_EMITTED_FILENAME );
staleSourceScanner.addSourceMapping( mapping );
// Look inside every compileSourceRoot
for ( String path : getCompileSourceRoots() )
{
File sourceDir = new File( path );
try
{
Set includedSources = staleSourceScanner.getIncludedSources( sourceDir, getOutputDirectory() );
if ( !includedSources.isEmpty() )
{
return true;
}
}
catch ( InclusionScanException e )
{
throw new MojoExecutionException( "Error scanning source root: \'" + sourceDir + "\' "
+ "for stale files to recompile.", e );
}
}
return false;
}
private boolean buildContextStalenessCheck()
{
String[] includesArray = this.includes.toArray( new String[0] );
String[] excludesArray = this.excludes.toArray( new String[0] );
// Check for modified files
for ( String path : getCompileSourceRoots() )
{
File sourceDir = new File( path );
Scanner modifiedScanner = this.buildContext.newScanner( sourceDir );
modifiedScanner.setIncludes( includesArray );
modifiedScanner.setExcludes( excludesArray );
modifiedScanner.scan();
String[] includedFiles = modifiedScanner.getIncludedFiles();
if ( includedFiles.length != 0 )
{
return true;
}
}
// Check for deleted files
for ( String path : getCompileSourceRoots() )
{
File sourceDir = new File( path );
Scanner deletedScanner = this.buildContext.newDeleteScanner( sourceDir );
deletedScanner.setIncludes( includesArray );
deletedScanner.setExcludes( excludesArray );
deletedScanner.scan();
String[] includedFiles = deletedScanner.getIncludedFiles();
if ( includedFiles.length != 0 )
{
return true;
}
}
return false;
}
/**
* Check if any schema will be renamed.
*
* @return {@code true} if a schema will be renamed, otherwise {@code false}
*/
private boolean hasRenamingSchemas()
{
if ( transformSchemas != null )
{
for ( TransformSchema schema : transformSchemas )
{
if ( schema.getToFile() != null )
{
return true;
}
}
}
return false;
}
/**
* @return The directory where the schema files should be placed.
*/
protected abstract File getOutputDirectory();
/**
* If files will be renamed, keep the original files here.
*
* @return the work directory
*/
protected abstract File getWorkDirectory();
protected abstract List getCompileSourceRoots();
protected abstract List getClasspathElements( MavenProject project )
throws DependencyResolutionRequiredException;
}