org.codehaus.mojo.jaxb2.AbstractXjcMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaxb2-maven-plugin Show documentation
Show all versions of jaxb2-maven-plugin Show documentation
Mojo's JAXB-2 Maven plugin is used to create an object graph
from XSDs based on the JAXB 2.x implementation and to generate XSDs
from JAXB annotated Java classes.
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.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.sonatype.plexus.build.incremental.BuildContext;
import org.xml.sax.SAXParseException;
import com.sun.tools.xjc.Driver;
import com.sun.tools.xjc.XJCListener;
/**
* Abstract class for parsing XML schemas and binding resources to produce a corresponding object
* model based on the JAXB XJC binding compiler.
*/
public abstract class AbstractXjcMojo
extends AbstractMojo
{
/**
* @component
*/
private BuildContext buildContext;
/**
* The default maven project object.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* The optional directory where generated resources can be placed, generated by addons/plugins.
*
* @parameter
*/
protected File generatedResourcesDirectory;
/**
* The package under which the source files will be generated.
*
* @parameter
*/
protected String packageName;
/**
* Catalog file to resolve external entity references. Supports TR9401,
* XCatalog, and OASIS XML Catalog format.
*
* @parameter
*/
protected File catalog;
/**
* Set HTTP/HTTPS proxy. Format is [user[:password]@]proxyHost[:proxyPort]
*
* @parameter
*/
protected String httpproxy;
/**
* List of files to use for bindings, comma delimited. If none, then all xjb
* files are used in the bindingDirectory.
*
* @parameter
*/
protected String bindingFiles;
/**
* List of files to use for schemas, comma delimited. If none, then all xsd
* files are used in the schemaDirectory. This parameter also accepts Ant-style file patterns.
* Note: you can only use either the 'schemaFiles' or the 'schemaListFileName'
* option (you may not use both at once!).
*
* @parameter
*/
protected String schemaFiles;
/**
* A filename containing the list of files to use for schemas, comma delimited.
* If none, then all xsd files are used in the schemaDirectory.
* Note: you can only use either the 'schemaFiles' or the 'schemaListFileName'
* option (you may not use both at once!).
*
* @parameter
*/
protected String schemaListFileName;
/**
* Treat input schemas as XML DTD (experimental, unsupported).
*
* @parameter default-value="false"
*/
protected boolean dtd;
/**
* Suppress generation of package level annotations (package-info.java).
*
* @parameter default-value="false"
*/
protected boolean npa;
/**
* Do not perform strict validation of the input schema(s).
*
* @parameter default-value="false"
*/
protected boolean nv;
/**
* Treat input schemas as RELAX NG (experimental, unsupported).
*
* @parameter default-value="false"
*/
protected boolean relaxng;
/**
* Treat input as RELAX NG compact syntax (experimental, unsupported).
*
* @parameter default-value="false"
*/
protected boolean relaxngCompact;
/**
* Suppress compiler output.
*
* @parameter default-value="false"
*/
protected boolean quiet;
/**
* Generated files will be in read-only mode.
*
* @parameter default-value="false"
* @deprecated Not suitable for a Maven build.
*/
protected boolean readOnly;
/**
* Be extra verbose.
*
* @parameter expression="${xjc.verbose}" default-value="false"
*/
protected boolean verbose;
/**
* Treat input as WSDL and compile schemas inside it (experimental, unsupported).
*
* @parameter default-value="false"
*/
protected boolean wsdl;
/**
* Treat input as W3C XML Schema (default).
*
* @parameter default-value="true"
*/
protected boolean xmlschema;
/**
* Allow to use the JAXB Vendor Extensions.
*
* @parameter default-value="false"
*/
protected boolean extension;
/**
* Allow generation of explicit annotations that are needed for JAXB2 to work on RetroTranslator.
*
* @parameter default-value="false"
*/
protected boolean explicitAnnotation;
/**
* Space separated string of extra arguments, for instance -Xfluent-api -episode somefile
; These
* will be passed on to XJC as "-Xfluent-api" "-episode" "somefile"
options.
*
* @parameter expression="${xjc.arguments}"
*/
protected String arguments;
/**
* The output path to include in your jar/war/etc if you wish to include your schemas in your artifact.
*
* @parameter
*/
protected String includeSchemasOutputPath;
/**
* Clears the output directory on each run. Defaults to 'true' but if false, will not clear the directory.
*
* @parameter default-value="true"
*/
protected boolean clearOutputDir;
/**
* Specifies the runtime environment in which the generated code is supposed to run, if older than the
* JAXB version used by the plugin (for example "2.0"). This will create generated code that doesn't use
* any newer JAXB features. Thus, allowing the generated code to run with an earlier JAXB 2.x runtime.
*
* @parameter
* @since 1.3
*/
protected String target;
/**
* Fails the mojo if no schemas are found.
*
* @parameter default-value="true"
* @since 1.3
*/
protected boolean failOnNoSchemas;
/**
* Enable correct generation of Boolean getters/setters to enable Bean Introspection apis.
*
* @parameter default-value="false"
* @since 1.4
*/
private boolean enableIntrospection;
/**
* The character encoding for the generated Java source files.
*
* @parameter default-value="${project.build.sourceEncoding}"
*/
private String encoding;
public AbstractXjcMojo()
{
super();
}
public void execute()
throws MojoExecutionException
{
if ( getLog().isDebugEnabled() )
{
Package jaxbImplPackage = Driver.class.getPackage();
getLog().debug( "Using XJC of " + jaxbImplPackage.getImplementationTitle() +
" version " + jaxbImplPackage.getImplementationVersion() );
}
try
{
if ( isOutputStale() )
{
getLog().info( "Generating source..." );
prepareDirectory( getOutputDirectory() );
if ( generatedResourcesDirectory != null )
{
prepareDirectory( generatedResourcesDirectory );
}
// Need to build a URLClassloader since Maven removed it form
// the chain
ClassLoader parent = this.getClass().getClassLoader();
List classpathFiles = getClasspathElements( project );
List urls = new ArrayList( classpathFiles.size() + 1 );
StringBuilder classPath = new StringBuilder();
for ( String classpathFile : classpathFiles )
{
getLog().debug( classpathFile );
urls.add( new File( classpathFile ).toURI().toURL() );
classPath.append( classpathFile );
classPath.append( File.pathSeparatorChar );
}
urls.add( new File( project.getBuild().getOutputDirectory() ).toURI().toURL() );
URLClassLoader cl = new URLClassLoader( urls.toArray( new URL[0] ), parent );
// Set the new classloader
Thread.currentThread().setContextClassLoader( cl );
try
{
ArrayList args = getXJCArgs( classPath.toString() );
MojoXjcListener xjcListener = new MojoXjcListener();
// Run XJC
if ( 0 != Driver.run( args.toArray( new String[args.size()] ), xjcListener ) )
{
String msg = "Could not process schema";
if ( null != schemaFiles )
{
URL xsds[] = getXSDFiles();
msg += xsds.length > 1 ? "s:" : ":";
for ( int i = 0; i < xsds.length; i++ )
{
msg += "\n " + xsds[i].getFile();
}
}
else
{
msg += " files in directory " + getSchemaDirectory();
}
throw new MojoExecutionException( msg );
}
// Workaround until upgrading to a JAXB impl that supports configuring
// the output char encoding (see http://java.net/jira/browse/JAXB-499)
changeEncoding( xjcListener.files );
buildContext.refresh( getOutputDirectory() );
touchStaleFile();
}
finally
{
// Set back the old classloader
Thread.currentThread().setContextClassLoader( parent );
}
}
else
{
getLog().info( "No changes detected in schema or binding files - skipping source generation." );
}
addCompileSourceRoot( project );
if ( generatedResourcesDirectory != null )
{
Resource resource = new Resource();
resource.setDirectory( generatedResourcesDirectory.getAbsolutePath() );
addResource( project, resource );
buildContext.refresh( generatedResourcesDirectory );
}
if ( includeSchemasOutputPath != null )
{
File includeSchemasOutputDirectory =
new File( project.getBuild().getOutputDirectory(), includeSchemasOutputPath );
FileUtils.forceMkdir( includeSchemasOutputDirectory );
copyXSDs( includeSchemasOutputDirectory );
buildContext.refresh( includeSchemasOutputDirectory );
}
}
catch ( NoSchemasException e )
{
if ( failOnNoSchemas )
{
throw new MojoExecutionException( "No schemas have been found" );
}
else
{
getLog().warn( "Skipping xjc execution, no schemas have been found" );
}
}
catch ( MojoExecutionException e )
{
throw e;
}
catch ( Exception e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
}
protected abstract void addCompileSourceRoot( MavenProject project );
protected abstract void addResource( MavenProject project, Resource resource );
protected void copyXSDs( File targetBaseDir )
throws MojoExecutionException
{
URL srcFiles[] = getXSDFiles();
for ( int j = 0; j < srcFiles.length; j++ )
{
URL from = srcFiles[j];
// the '/' is the URL-separator
File to = new File( targetBaseDir, FileUtils.removePath( from.getPath(), '/' ) );
try
{
FileUtils.copyURLToFile( from, to );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error copying file", e );
}
}
}
private void prepareDirectory( File dir )
throws MojoExecutionException
{
// If the directory exists, whack it to start fresh
if ( clearOutputDir && dir.exists() )
{
try
{
FileUtils.deleteDirectory( dir );
}
catch ( IOException e )
{
throw new MojoExecutionException( "Error cleaning directory " + dir.getAbsolutePath(), e );
}
}
if ( !dir.exists() )
{
if ( !dir.mkdirs() )
{
throw new MojoExecutionException( "Could not create directory " + dir.getAbsolutePath() );
}
}
}
private ArrayList getXJCArgs( String classPath )
throws MojoExecutionException, NoSchemasException
{
ArrayList args = new ArrayList();
if ( npa )
{
args.add( "-npa" );
}
if ( nv )
{
args.add( "-nv" );
}
if ( dtd )
{
args.add( "-dtd" );
}
if ( verbose )
{
args.add( "-verbose" );
}
if ( quiet )
{
args.add( "-quiet" );
}
if ( readOnly )
{
getLog().warn( "The readOnly parameter is deprecated. Support will be removed in a future version." );
args.add( "-readOnly" );
}
if ( relaxng )
{
args.add( "-relaxng" );
}
if ( relaxngCompact )
{
args.add( "-relaxng-compact" );
}
if ( wsdl )
{
args.add( "-wsdl" );
}
if ( xmlschema )
{
args.add( "-xmlschema" );
}
if ( explicitAnnotation )
{
args.add( "-XexplicitAnnotation" );
}
if ( httpproxy != null )
{
args.add( "-httpproxy" );
args.add( httpproxy );
}
if ( packageName != null )
{
args.add( "-p" );
args.add( packageName );
}
if ( catalog != null )
{
args.add( "-catalog" );
args.add( catalog.getAbsolutePath() );
}
if ( extension )
{
args.add( "-extension" );
}
if ( target != null )
{
args.add( "-target" );
args.add( target );
}
if ( enableIntrospection )
{
args.add( "-enableIntrospection" );
}
if ( arguments != null && arguments.trim().length() > 0 )
{
try
{
String[] argList = CommandLineUtils.translateCommandline( arguments );
for ( int argIndex = 0; argIndex < argList.length; argIndex++ )
{
args.add( argList[argIndex] );
}
}
catch ( Exception e )
{
throw new MojoExecutionException( "failed to split property arguments" );
}
}
args.add( "-d" );
args.add( getOutputDirectory().getAbsolutePath() );
args.add( "-classpath" );
args.add( classPath );
// Bindings
File bindings[] = getBindingFiles();
for ( int i = 0; i < bindings.length; i++ )
{
args.add( "-b" );
args.add( bindings[i].getAbsolutePath() );
}
List schemas = new ArrayList();
// XSDs
if ( schemaFiles != null || schemaListFileName != null )
{
URL xsds[] = getXSDFiles();
for ( int i = 0; i < xsds.length; i++ )
{
schemas.add( xsds[i].toString() );
}
}
else
{
if ( getSchemaDirectory().exists() && getSchemaDirectory().isDirectory() )
{
File[] schemaFiles = getSchemaDirectory().listFiles( new XSDFile( getLog() ) );
if ( schemaFiles != null && schemaFiles.length > 0 )
{
schemas.add( getSchemaDirectory().getAbsolutePath() );
}
}
}
if ( schemas.isEmpty() )
{
throw new NoSchemasException();
}
args.addAll( schemas );
getLog().debug( "JAXB XJC args: " + args );
return args;
}
/**
* Gets all the entries in the given schemaListFileName and adds them to the list
* of files to send to xjc.
*
* @throws MojoExecutionException if an error occurs
*/
protected void getSchemasFromFileListing( List files )
throws MojoExecutionException
{
// check that the given file exists
File schemaListFile = new File( schemaListFileName );
// create a scanner over the input file
Scanner scanner = null;
try
{
scanner = new Scanner( schemaListFile ).useDelimiter( "," );
}
catch ( FileNotFoundException e )
{
throw new MojoExecutionException(
"schemaListFileName: " + schemaListFileName + " could not be found - error:" + e.getMessage(), e );
}
// scan the file and add to the list for processing
String nextToken = null;
File nextFile = null;
while ( scanner.hasNext() )
{
nextToken = scanner.next();
URL url;
try
{
url = new URL( nextToken );
}
catch ( MalformedURLException e )
{
getLog().debug( nextToken + " doesn't look like a URL..." );
nextFile = new File( getSchemaDirectory(), nextToken.trim() );
try
{
url = nextFile.toURI().toURL();
}
catch ( MalformedURLException e2 )
{
throw new MojoExecutionException( "Unable to convert file to a URL.", e2 );
}
}
files.add( url );
}
}
/**
* Returns a file array of xjb files to translate to object models.
*
* @return An array of binding files to be parsed by the schema compiler.
*/
public final File[] getBindingFiles()
{
List bindings = new ArrayList();
if ( bindingFiles != null )
{
for ( StringTokenizer st = new StringTokenizer( bindingFiles, "," ); st.hasMoreTokens(); )
{
String schemaName = st.nextToken();
bindings.add( new File( getBindingDirectory(), schemaName ) );
}
}
else
{
getLog().debug( "The binding Directory is " + getBindingDirectory() );
File[] files = getBindingDirectory().listFiles( new XJBFile() );
if ( files != null )
{
for ( int i = 0; i < files.length; i++ )
{
bindings.add( files[i] );
}
}
}
return bindings.toArray( new File[]{ } );
}
/**
* Returns a file array of xsd files to translate to object models.
*
* @return An array of schema files to be parsed by the schema compiler.
*/
public final URL[] getXSDFiles()
throws MojoExecutionException
{
// illegal option check
if ( schemaFiles != null && schemaListFileName != null )
{
// make sure user didn't specify both schema input options
throw new MojoExecutionException( "schemaFiles and schemaListFileName options were provided, "
+ "these options may not be used together - schemaFiles: "
+ schemaFiles + "; schemaListFileName: " + schemaListFileName );
}
List xsdFiles = new ArrayList();
if ( schemaFiles != null )
{
DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir( getSchemaDirectory() );
scanner.setIncludes( schemaFiles.split( "," ) );
scanner.scan();
for ( String schemaName : scanner.getIncludedFiles() )
{
URL url = null;
try
{
url = new URL( schemaName.trim() );
}
catch ( MalformedURLException e )
{
try
{
url = new File( getSchemaDirectory(), schemaName ).toURI().toURL();
}
catch ( MalformedURLException e2 )
{
throw new MojoExecutionException( "Unable to convert file to a URL.", e2 );
}
}
xsdFiles.add( url );
}
}
else if ( schemaListFileName != null )
{
// add all the contents from the schemaListFileName file on disk
getSchemasFromFileListing( xsdFiles );
}
else
{
getLog().debug( "The schema Directory is " + getSchemaDirectory() );
File[] files = getSchemaDirectory().listFiles( new XSDFile( getLog() ) );
if ( files != null )
{
for ( int i = 0; i < files.length; i++ )
{
try
{
xsdFiles.add( files[i].toURI().toURL() );
}
catch ( MalformedURLException e )
{
throw new MojoExecutionException( "Unable to convert file to a URL.", e );
}
}
}
}
return xsdFiles.toArray( new URL[xsdFiles.size()] );
}
/**
* Returns true if any one of the files in the XSD/XJB array is newer than
* the staleFlag
file.
*
* @return True if any input file has been modified since the last build.
*/
private boolean isOutputStale()
throws MojoExecutionException
{
// We don't use BuildContext for staleness detection, but use the stale flag
// approach regardless of the runtime environment.
URL[] sourceXsds = getXSDFiles();
File[] sourceXjbs = getBindingFiles();
boolean stale = !getStaleFile().exists();
if ( !stale )
{
getLog().debug( "Stale flag file exists, comparing to xsds and xjbs." );
long staleMod = getStaleFile().lastModified();
for ( int i = 0; i < sourceXsds.length; i++ )
{
URLConnection connection;
try
{
connection = sourceXsds[i].openConnection();
connection.connect();
}
catch ( IOException e )
{
stale = true;
break;
}
try
{
if ( connection.getLastModified() > staleMod )
{
getLog().debug( sourceXsds[i].toString() + " is newer than the stale flag file." );
stale = true;
break;
}
}
finally
{
if ( connection instanceof HttpURLConnection )
{
( (HttpURLConnection) connection ).disconnect();
}
}
}
for ( int i = 0; i < sourceXjbs.length; i++ )
{
if ( sourceXjbs[i].lastModified() > staleMod )
{
getLog().debug( sourceXjbs[i].getName() + " is newer than the stale flag file." );
stale = true;
break;
}
}
}
return stale;
}
private void touchStaleFile()
throws IOException
{
if ( !getStaleFile().exists() )
{
getStaleFile().getParentFile().mkdirs();
getStaleFile().createNewFile();
getLog().debug( "Stale flag file created." );
}
else
{
getStaleFile().setLastModified( System.currentTimeMillis() );
}
}
private void changeEncoding( List files )
throws IOException
{
String to = encoding;
String from = System.getProperty( "file.encoding" );
boolean enableEncode = to != null && to.trim().length() > 0 && !to.equals( from );
if ( enableEncode )
{
getLog().debug( "Changing encoding of generated source files from " + from
+ " to " + to );
for ( Iterator < String > it = files.iterator(); it.hasNext(); )
{
File file = new File( getOutputDirectory(), it.next() );
if ( file.exists() )
{
String data = FileUtils.fileRead( file );
FileUtils.fileWrite( file.getAbsolutePath(), to, data );
}
else
{
getLog().warn( "Expected file " + file.getAbsolutePath()
+ " not found when changing encoding" );
}
}
}
}
protected abstract File getStaleFile();
protected abstract File getOutputDirectory();
protected abstract File getSchemaDirectory();
protected abstract File getBindingDirectory();
protected abstract List getClasspathElements( MavenProject project )
throws DependencyResolutionRequiredException;
/**
* A class used to look up .xjb documents from a given directory.
*/
final class XJBFile
implements FileFilter
{
/**
* Returns true if the file ends with an xjb extension.
*
* @param file The file being reviewed by the filter.
* @return true if an xjb file.
*/
public boolean accept( final File file )
{
return file.isFile() && file.getName().endsWith( ".xjb" );
}
}
/**
* A class used to look up .xsd documents from a given directory.
*/
final class XSDFile
implements FileFilter
{
private Log log;
public XSDFile( Log log )
{
this.log = log;
}
/**
* Returns true if the file ends with an xsd extension.
*
* @param file The file being reviewed by the filter.
* @return true if an xsd file.
*/
public boolean accept( final java.io.File file )
{
boolean accept = file.isFile() && file.getName().endsWith( ".xsd" );
if ( log.isDebugEnabled() )
{
log.debug( "accept " + accept + " for file " + file.getPath() );
}
return accept;
}
}
/**
* Class to tap into Maven's logging facility
*/
class MojoXjcListener
extends XJCListener
{
List files = new ArrayList();
private String location( SAXParseException e )
{
return StringUtils.defaultString( e.getPublicId(), e.getSystemId() ) + "[" + e.getLineNumber() + ","
+ e.getColumnNumber() + "]";
}
public void error( SAXParseException arg0 )
{
getLog().error( location( arg0 ), arg0 );
}
public void fatalError( SAXParseException arg0 )
{
getLog().error( location( arg0 ), arg0 );
}
public void warning( SAXParseException arg0 )
{
getLog().warn( location( arg0 ), arg0 );
}
public void info( SAXParseException arg0 )
{
getLog().warn( location( arg0 ), arg0 );
}
public void message( String arg0 )
{
getLog().info( arg0 );
}
public void generatedFile( String arg0 )
{
getLog().info( arg0 );
files.add( arg0 );
}
}
}