org.codehaus.mojo.jaxb2.javageneration.AbstractJavaGeneratorMojo Maven / Gradle / Ivy
Show all versions of jaxb2-maven-plugin Show documentation
package org.codehaus.mojo.jaxb2.javageneration;
/*
* 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 com.sun.tools.xjc.Driver;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.codehaus.mojo.jaxb2.AbstractJaxbMojo;
import org.codehaus.mojo.jaxb2.NoSchemasException;
import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
import org.codehaus.mojo.jaxb2.shared.arguments.ArgumentBuilder;
import org.codehaus.mojo.jaxb2.shared.environment.EnvironmentFacet;
import org.codehaus.mojo.jaxb2.shared.environment.ToolExecutionEnvironment;
import org.codehaus.mojo.jaxb2.shared.environment.classloading.ThreadContextClassLoaderBuilder;
import org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet;
import org.codehaus.mojo.jaxb2.shared.environment.logging.LoggingHandlerEnvironmentFacet;
import org.codehaus.mojo.jaxb2.shared.environment.sysprops.SystemPropertyChangeEnvironmentFacet;
import org.codehaus.mojo.jaxb2.shared.environment.sysprops.SystemPropertySaveEnvironmentFacet;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import java.io.File;
import java.io.FileWriter;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Abstract superclass for Mojos generating Java source or binaries from XML schema(s) by invoking the JAXB XJC
* binding compiler. Most of the Configuration options for the AbstractJavaGeneratorMojo are set or copied to the
* XJC directly; refer to their documentation in the JAXB Reference Implementation
* site.
*
* @author Lennart Jörelid
* @see The JAXB Reference Implementation
*/
public abstract class AbstractJavaGeneratorMojo extends AbstractJaxbMojo {
private static final List PROXY_PROPERTY_KEYS = Arrays.asList("http.proxyHost", "http.proxyPort", "https.proxyHost", "https.proxyPort");
private static final int XJC_COMPLETED_OK = 0;
/**
* Corresponding XJC parameter: {@code catalog}.
* Specify catalog files to resolve external entity references.
* Supports TR9401, XCatalog, and OASIS XML Catalog format.
*/
@Parameter
protected File catalog;
/**
* Deprecated - will be removed in a future release
* From plugin version 2.4, this parameter will not be used.
* Instead, episode files are generated by default with all JAXB operations.
* Starting with plugin version 2.4, use the parameter {@link #episodeFileName} to provide a custom
* name of the generated episode File (or rely on the standard file name {@link #STANDARD_EPISODE_FILENAME}).
*
* @since 2.0
* @deprecated
*/
@Deprecated
@Parameter(defaultValue = "true")
protected boolean generateEpisode;
/**
* Corresponding XJC parameter: {@code episode}.
* Generate an episode file with the supplied name from this XJC compilation, so that other schemas that rely
* on this schema can be compiled later and rely on classes that are generated from this compilation.
* The generated episode file is simply a JAXB customization file (but with vendor extensions), normally known
* as a binding file with the suffix .xjb
.
* If the episodeFileName
parameter is not given, the episode file name is synthesized on the form
* "episode_" + executionID + ".xjb"
- typically something like episode_my_xjc.xjb, but
* it depends on the actual ID given in the execution element:
*
*
* <executions>
* <execution>
* <id>my_xjc</id>
* <goals>
* <goal>xjc</goal>
* </goals>
* </execution>
* </executions>
*
*
*
* @see #STANDARD_EPISODE_FILENAME
* @since 2.4
*/
@Parameter
protected String episodeFileName;
/**
* Sets the HTTP/HTTPS proxy to be used by the XJC, on the format
* {@code [user[:password]@]proxyHost[:proxyPort]}.
* All information is retrieved from the active proxy within the standard maven settings file.
*/
@Parameter(defaultValue = "${settings}", readonly = true)
protected Settings settings;
/**
* Defines the content type of sources for the XJC. To simplify usage of the JAXB2 maven plugin,
* all source files are assumed to have the same type of content.
* This parameter replaces the previous multiple-choice boolean configuration options for the
* jaxb2-maven-plugin (i.e. dtd, xmlschema, relaxng, relaxng-compact, wsdl), and
* corresponds to setting one of those flags as an XJC argument.
*
* @since 2.0
*/
@Parameter(defaultValue = "XmlSchema")
protected SourceContentType sourceType;
/**
* Corresponding XJC parameter: {@code npa}.
* Suppress the generation of package level annotations into {@code package-info.java}.
* Using this switch causes the generated code to internalize those annotations into the other
* generated classes.
*
* @since 2.0
*/
@Parameter(defaultValue = "false")
protected boolean noPackageLevelAnnotations;
/**
* Corresponding XJC parameter: {@code no-header}.
* Suppress the generation of a file header comment that includes some note and timestamp.
* Using this makes the generated code more diff-friendly.
*
* @since 2.0
*/
@Parameter(defaultValue = "false")
protected boolean noGeneratedHeaderComments;
/**
* Corresponding XJC parameter: {@code mark-generated}.
* This feature causes all of the generated code to have {@code @Generated} annotation.
*
* @since 2.0
*/
@Parameter(defaultValue = "false")
protected boolean addGeneratedAnnotation;
/**
* Corresponding XJC parameter: {@code nv}.
* By default, the XJC binding compiler performs strict validation of the source schema before processing it.
* Use this option to disable strict schema validation. This does not mean that the binding compiler will not
* perform any validation, it simply means that it will perform less-strict validation.
*
* @since 2.0
*/
@Parameter(defaultValue = "false")
protected boolean laxSchemaValidation;
/**
* Corresponding XJC parameter: {@code quiet}.
* Suppress compiler output, such as progress information and warnings.
*/
@Parameter(defaultValue = "false")
protected boolean quiet;
/**
* Corresponding XJC parameter: {@code verbose}.
* Tells XJC to be extra verbose, such as printing informational messages or displaying stack traces.
*/
@Parameter(property = "xjc.verbose", defaultValue = "false")
protected boolean verbose;
/**
* Corresponding XJC parameter: {@code extension}.
* By default, the XJC binding compiler strictly enforces the rules outlined in the Compatibility chapter of
* the JAXB Specification. Appendix E.2 defines a set of W3C XML Schema features that are not completely
* supported by JAXB v1.0. In some cases, you may be allowed to use them in the "-extension" mode enabled by
* this switch. In the default (strict) mode, you are also limited to using only the binding customizations
* defined in the specification.
*/
@Parameter(defaultValue = "true")
protected boolean extension;
/**
* Fails the Mojo execution if no XSDs/schemas are found.
*
* @since 1.3
*/
@Parameter(defaultValue = "true")
protected boolean failOnNoSchemas;
/**
* Removes all files from the output directory before running XJC.
*/
@Parameter(defaultValue = "true")
protected boolean clearOutputDir;
/**
* Corresponding XJC parameter: {@code readOnly}.
* By default, the XJC binding compiler does not write-protect the Java source files it generates.
* Use this option to force the XJC binding compiler to mark the generated Java sources read-only.
*
* @since 2.0
*/
@Parameter(defaultValue = "false")
protected boolean readOnly;
/**
* List of ordered extra arguments to the XJC command. Each extra argument is interpreted as a word, intended
* to be copied verbatim to the XJC argument list with spaces in between:
*
*
* <configuration>
* ...
* <arguments>
* <argument>-Xfluent-api</argument>
* <argument>somefile</argument>
* </arguments>
* </configuration>
*
*
* The arguments configured above yields the following extra arguments to the XJC command:
* -Xfluent-api -episode somefile
*
* @since 2.0
* @deprecated This should be removed in the 2.0+ release, as all arguments should be handled by other parameters.
*/
@Parameter(property = "xjc.arguments")
protected List arguments;
/**
* Corresponding XJC parameter: {@code enableIntrospection}.
* Enable correct generation of Boolean getters/setters to enable Bean Introspection APIs.
*
* @since 1.4
*/
@Parameter(defaultValue = "false")
private boolean enableIntrospection;
/**
* Corresponding XJC parameter: {@code p}.
* The package under which the source files will be generated. Quoting the XJC documentation:
* "Specifying a target package via this command-line option overrides any binding customization for package
* name and the default package name algorithm defined in the specification".
*/
@Parameter
protected String packageName;
/**
* Corresponding XJC parameter: {@code target}.
* Permitted values: {@code "2.0"} and {@code "2.1"}. Avoid generating code that relies on JAXB newer than the
* version given. This will allow the generated code to run with JAXB 2.0 runtime (such as JavaSE 6.)
*
* @since 1.3
*/
@Parameter
protected String target;
/**
* If provided, this parameter indicates that the XSDs used by XJC to generate Java code should be
* copied into the resulting artifact of this project (the JAR, WAR or whichever artifact type is generated).
* The value of the {@code xsdPathWithinArtifact} parameter is the relative path within the artifact where
* all source XSDs are copied to (hence the name "XSD Path Within Artifact").
* The target directory is created within the artifact if it does not already exist.
* If the {@code xsdPathWithinArtifact} parameter is not given, the XSDs used to generate Java code are
* not included within the project's artifact.
* Example:Adding the sample configuration below would copy all source XSDs to the given directory
* within the resulting JAR (and/or test-JAR). If the directory {@code META-INF/jaxb/xsd} does not exist, it
* will be created.
*
*
* <configuration>
* ...
* <xsdPathWithinArtifact>META-INF/jaxb/xsd</xsdPathWithinArtifact>
* </configuration>
*
*
* Note: This parameter was previously called {@code includeSchemasOutputPath}
* in the 1.x versions of this plugin, but was renamed and re-documented for improved usability and clarity.
*
* @since 2.0
*/
@Parameter
protected String xsdPathWithinArtifact;
/**
* If set to true
, the system property enableExternalEntityProcessing
is set for the
* duration of the Java generation by this plugin, permitting DTD sources to use external entity URIs such as
* file://
. Typically, this is used in conjunction with the sourceType
similar to the
* configuration snippet below where DTDs are used as sourceType and read from the src/main/dtd
* directory. This implies a file://
URI.
*
*
* <configuration>
* ...
* <sourceType>dtd</sourceType>
* <sources>
* <source>src/main/dtd</source>
* </sources>
* <externalEntityProcessing>true</externalEntityProcessing>
* </configuration>
*
*
*
* @since 2.4
*/
@Parameter(defaultValue = "false")
protected boolean externalEntityProcessing;
/**
* Java generation is required if any of the file products is outdated/stale.
* {@inheritDoc}
*/
@Override
protected boolean isReGenerationRequired() {
//
// Use the stale flag method to identify if we should re-generate the java source code from the supplied
// Xml Schema. Basically, we should regenerate the JAXB code if:
//
// a) The staleFile does not exist
// b) The staleFile exists and is older than one of the sources (XSD or XJB files).
// "Older" is determined by comparing the modification timestamp of the staleFile and the source files.
//
final File staleFile = getStaleFile();
final String debugPrefix = "StaleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]";
boolean stale = !staleFile.exists();
if (stale) {
getLog().debug(debugPrefix + " not found. JAXB (re-)generation required.");
} else {
final List sourceXSDs = getSources();
final List sourceXJBs = getSourceXJBs();
if (getLog().isDebugEnabled()) {
getLog().debug(debugPrefix + " found. Checking timestamps on source XSD and XJB "
+ "files to determine if JAXB (re-)generation is required.");
}
final long staleFileLastModified = staleFile.lastModified();
for (URL current : sourceXSDs) {
final URLConnection sourceXsdConnection;
try {
sourceXsdConnection = current.openConnection();
sourceXsdConnection.connect();
} catch (Exception e) {
// Can't determine if the staleFile is younger than this sourceXSD.
// Re-generate to be on the safe side.
stale = true;
break;
}
try {
if (sourceXsdConnection.getLastModified() > staleFileLastModified) {
if (getLog().isDebugEnabled()) {
getLog().debug(current.toString() + " is newer than the stale flag file.");
}
stale = true;
}
} finally {
if (sourceXsdConnection instanceof HttpURLConnection) {
((HttpURLConnection) sourceXsdConnection).disconnect();
}
}
}
for (File current : sourceXJBs) {
if (current.lastModified() > staleFileLastModified) {
if (getLog().isDebugEnabled()) {
getLog().debug(FileSystemUtilities.getCanonicalPath(current)
+ " is newer than the stale flag file.");
}
stale = true;
break;
}
}
}
// All done.
return stale;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean performExecution() throws MojoExecutionException, MojoFailureException {
boolean updateStaleFileTimestamp = false;
try {
// Setup the Tool's execution environment
ToolExecutionEnvironment environment = null;
try {
// Create a LocaleFacet if the user has configured an explicit Locale for the tool.
final LocaleFacet localeFacet = locale == null ? null : LocaleFacet.createFor(locale, getLog());
// Create the ToolExecutionEnvironment
environment = new ToolExecutionEnvironment(getLog(),
ThreadContextClassLoaderBuilder.createFor(this.getClass(), getLog(), getEncoding(false))
.addPaths(getClasspath()),
LoggingHandlerEnvironmentFacet.create(getLog(), getClass(), getEncoding(false)),
localeFacet);
// Add any extra configured EnvironmentFacets, as configured in the POM.
if (extraFacets != null) {
for (EnvironmentFacet current : extraFacets) {
environment.add(current);
}
}
// Handle extended properties?
if (externalEntityProcessing) {
final List sysPropChanges =
SystemPropertyChangeEnvironmentFacet.getBuilder(getLog())
.addOrChange("enableExternalEntityProcessing", "" + externalEntityProcessing)
.build();
for (SystemPropertyChangeEnvironmentFacet current : sysPropChanges) {
environment.add(current);
}
}
// XJC overwrites proxy properties if so inclined, so we use this facet to save them
for (String key : PROXY_PROPERTY_KEYS) {
environment.add(new SystemPropertySaveEnvironmentFacet(key, getLog()));
}
// Setup the environment.
environment.setup();
// Compile the XJC arguments
final String[] xjcArguments = getXjcArguments(environment.getClassPathAsArgument(), episodeFileName);
// Ensure that the outputDirectory exists, but only clear it if does not already
FileSystemUtilities.createDirectory(getOutputDirectory(), clearOutputDir);
// Do we need to re-create the episode file's parent directory.
final boolean reCreateEpisodeFileParentDirectory = generateEpisode && clearOutputDir;
if (reCreateEpisodeFileParentDirectory) {
getEpisodeFile(episodeFileName);
}
// Check the system properties.
logSystemPropertiesAndBasedir();
// Fire XJC
if (XJC_COMPLETED_OK != Driver.run(xjcArguments, new XjcLogAdapter(getLog()))) {
final StringBuilder errorMsgBuilder = new StringBuilder();
errorMsgBuilder.append("\n+=================== [XJC Error]\n");
errorMsgBuilder.append("|\n");
final List sourceXSDs = getSources();
for (int i = 0; i < sourceXSDs.size(); i++) {
errorMsgBuilder.append("| " + i + ": ").append(sourceXSDs.get(i).toString()).append("\n");
}
errorMsgBuilder.append("|\n");
errorMsgBuilder.append("+=================== [End XJC Error]\n");
throw new MojoExecutionException(errorMsgBuilder.toString());
}
// Indicate that the output directory was updated.
getBuildContext().refresh(getOutputDirectory());
// Update the modification timestamp of the staleFile.
updateStaleFileTimestamp = true;
} finally {
if (environment != null) {
environment.restore();
}
}
// Add the generated source root to the project, enabling tooling and other plugins to see them.
addGeneratedSourcesToProjectSourceRoot();
// Copy all source XSDs to the resulting artifact?
if (xsdPathWithinArtifact != null) {
final String buildOutputDirectory = getProject().getBuild().getOutputDirectory();
final File targetXsdDirectory = new File(buildOutputDirectory, xsdPathWithinArtifact);
FileUtils.forceMkdir(targetXsdDirectory);
for (URL current : getSources()) {
String fileName = null;
if ("file".equalsIgnoreCase(current.getProtocol())) {
fileName = new File(current.getPath()).getName();
} else if ("jar".equalsIgnoreCase(current.getProtocol())) {
// Typical JAR path
// jar:file:/path/to/aJar.jar!/some/path/xsd/aResource.xsd
final int bangIndex = current.toString().indexOf("!");
if (bangIndex == -1) {
throw new MojoExecutionException("Illegal JAR URL [" + current.toString()
+ "]: lacks a '!'");
}
final String internalPath = current.toString().substring(bangIndex + 1);
fileName = new File(internalPath).getName();
} else {
throw new MojoExecutionException("Could not extract FileName from URL [" + current + "]");
}
final File targetFile = new File(targetXsdDirectory, fileName);
if (targetFile.exists()) {
// TODO: Should we throw an exception here instead?
getLog().warn("File [" + FileSystemUtilities.getCanonicalPath(targetFile)
+ "] already exists. Not copying XSD file [" + current.getPath() + "] to it.");
}
IOUtil.copy(current.openStream(), new FileWriter(targetFile));
}
// Refresh the BuildContext
getBuildContext().refresh(targetXsdDirectory);
}
} catch (MojoExecutionException e) {
throw e;
} catch (NoSchemasException e) {
if (failOnNoSchemas) {
throw new MojoExecutionException("", e);
}
} catch (Exception e) {
throw new MojoExecutionException(e.getMessage(), e);
}
// All done.
return updateStaleFileTimestamp;
}
/**
* Override this method to acquire a List holding all URLs to the JAXB sources for which this
* AbstractJavaGeneratorMojo should generate Java files. Sources are assumed to be in the form given by
* the {@code sourceType} value.
*
* @return A non-null List holding URLs to sources for the XJC generation.
* @see #sourceType
*/
@Override
protected abstract List getSources();
/**
* Override this method to retrieve a list of Files to all XJB files for which this
* AbstractJavaGeneratorMojo should generate Java files.
*
* @return A non-null List holding binding files.
*/
protected abstract List getSourceXJBs();
/**
* Adds any directories containing the generated XJC classes to the appropriate Project compilation sources;
* either {@code TestCompileSourceRoot} or {@code CompileSourceRoot} depending on the exact Mojo implementation
* of this AbstractJavaGeneratorMojo.
*/
protected abstract void addGeneratedSourcesToProjectSourceRoot();
//
// Private helpers
//
private String[] getXjcArguments(final String classPath, final String episodeFileNameOrNull)
throws MojoExecutionException, NoSchemasException {
final ArgumentBuilder builder = new ArgumentBuilder();
// Add all flags on the form '-flagName'
builder.withFlag(true, sourceType.getXjcArgument());
builder.withFlag(noPackageLevelAnnotations, "npa");
builder.withFlag(laxSchemaValidation, "nv");
builder.withFlag(verbose, "verbose");
builder.withFlag(quiet, "quiet");
builder.withFlag(enableIntrospection, "enableIntrospection");
builder.withFlag(extension, "extension");
builder.withFlag(readOnly, "readOnly");
builder.withFlag(noGeneratedHeaderComments, "no-header");
builder.withFlag(addGeneratedAnnotation, "mark-generated");
// Add all arguments on the form '-argumentName argumentValue'
// (i.e. in 2 separate elements of the returned String[])
builder.withNamedArgument("httpproxy", getProxyString(settings.getActiveProxy()));
builder.withNamedArgument("encoding", getEncoding(true));
builder.withNamedArgument("p", packageName);
builder.withNamedArgument("target", target);
builder.withNamedArgument("d", getOutputDirectory().getAbsolutePath());
builder.withNamedArgument("classpath", classPath);
// We must add the -extension flag in order to generate the episode file.
if (!extension) {
if (getLog().isInfoEnabled()) {
getLog().info("Adding 'extension' flag to XJC arguments, to generate an episode "
+ "file named '" + (episodeFileName == null ? STANDARD_EPISODE_FILENAME : episodeFileName)
+ "'. (XJCs 'episode' argument requires that the 'extension' argument is provided).");
}
}
builder.withFlag(true, "extension");
final File episodeFile = getEpisodeFile(episodeFileNameOrNull);
builder.withNamedArgument("episode", FileSystemUtilities.getCanonicalPath(episodeFile));
if (catalog != null) {
builder.withNamedArgument("catalog", FileSystemUtilities.getCanonicalPath(catalog));
}
if (arguments != null) {
builder.withPreCompiledArguments(arguments);
}
for (File current : getSourceXJBs()) {
// Shorten the argument?
// final String strippedXjbPath = FileSystemUtilities.relativize(
// current.getAbsolutePath(), getProject().getBasedir());
// Each XJB must be given as a separate argument.
builder.withPreCompiledArguments(Arrays.asList("-b", current.getAbsolutePath()));
}
final List sourceXSDs = getSources();
if (sourceXSDs.isEmpty()) {
// If we have no XSDs, we are not going to be able to run XJC.
getLog().warn("No XSD files found. Please check your plugin configuration.");
throw new NoSchemasException();
} else {
final List unwrappedSourceXSDs = new ArrayList();
for (URL current : sourceXSDs) {
// Shorten the argument if possible.
if ("file".equalsIgnoreCase(current.getProtocol())) {
try {
unwrappedSourceXSDs.add(new File(current.toURI()).getPath());
} catch (final URISyntaxException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
} else {
unwrappedSourceXSDs.add(current.toString());
}
}
builder.withPreCompiledArguments(unwrappedSourceXSDs);
}
// All done.
return logAndReturnToolArguments(builder.build(), "XJC");
}
private String getProxyString(final Proxy activeProxy) {
// Check sanity
if (activeProxy == null) {
return null;
}
// The XJC proxy argument should be on the form
// [user[:password]@]proxyHost[:proxyPort]
//
// builder.withNamedArgument("httpproxy", httpproxy);
//
final StringBuilder proxyBuilder = new StringBuilder();
if (activeProxy.getUsername() != null) {
// Start with the username.
proxyBuilder.append(activeProxy.getUsername());
// Append the password if provided.
if (activeProxy.getPassword() != null) {
proxyBuilder.append(":").append(activeProxy.getPassword());
}
proxyBuilder.append("@");
}
// Append hostname and port.
proxyBuilder.append(activeProxy.getHost()).append(":").append(activeProxy.getPort());
// All done.
return proxyBuilder.toString();
}
}