All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.jaxb2.AbstractJaxbMojo Maven / Gradle / Ivy

Go to download

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.

There is a newer version: 3.2.0
Show newest version
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 org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
import org.codehaus.mojo.jaxb2.shared.Validate;
import org.codehaus.mojo.jaxb2.shared.environment.EnvironmentFacet;
import org.codehaus.mojo.jaxb2.shared.filters.Filter;
import org.codehaus.mojo.jaxb2.shared.filters.pattern.FileFilterAdapter;
import org.codehaus.mojo.jaxb2.shared.filters.pattern.PatternFileFilter;
import org.codehaus.mojo.jaxb2.shared.version.DependencyInfo;
import org.codehaus.mojo.jaxb2.shared.version.DependsFileParser;
import org.sonatype.plexus.build.incremental.BuildContext;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

/**
 * Abstract Mojo which collects common infrastructure, required and needed
 * by all subclass Mojos in the JAXB2 maven plugin codebase.
 *
 * @author Lennart Jörelid
 */
public abstract class AbstractJaxbMojo extends AbstractMojo {

    /**
     * Standard name of the generated JAXB episode file.
     */
    public static final String STANDARD_EPISODE_FILENAME = "sun-jaxb.episode";

    /**
     * Standard name of the package-info.java file which may contain
     * JAXB annotations and Package JavaDoc.
     */
    public static final String PACKAGE_INFO_FILENAME = "package-info.java";

    /**
     * Platform-independent newline control string.
     */
    public static final String NEWLINE = System.getProperty("line.separator");

    /**
     * Pattern matching strings containing whitespace (or consisting only of whitespace).
     */
    public static final Pattern CONTAINS_WHITESPACE = Pattern.compile("(\\S*\\s+\\S*)+", Pattern.UNICODE_CASE);

    /**
     * 

Standard excludes Filters for all Java generator Mojos. * The List is unmodifiable, and contains Filters on the following form:

*
     *     
     *         // The standard exclude filters contain simple, exclude pattern filters.
     *         final List<Filter<File>> tmp = new ArrayList<Filter<File>>();
     *         tmp.add(new PatternFileFilter(Arrays.asList({"README.*", "\\.xml", "\\.txt"}), true));
     *         tmp.add(new FileFilterAdapter(new FileFilter() {
     *
     *             @Override
     *             public boolean accept(final File aFileOrDir) {
     *
     *                 // Check sanity
     *                 if (aFileOrDir == null) {
     *                     return false;
     *                 }
     *
     *                 final String name = aFileOrDir.getName();
     *
     *                 // Ignore hidden files and CVS directories
     *                 return name.startsWith(".")
     *                         || (aFileOrDir.isDirectory() && name.equals("CVS"));
     *
     *             }
     *         }));
     *     
     * 
*

Note! Since the plugin is currently developed in jdk 1.7-compliant code, we cannot * use lambdas within Filters just yet.

*/ public static final List> STANDARD_EXCLUDE_FILTERS; private static final List RELEVANT_GROUPIDS = Arrays.asList("com.sun.xml.bind", "jakarta.xml.bind"); private static final String OWN_ARTIFACT_ID = "jaxb2-maven-plugin"; private static final String SYSTEM_FILE_ENCODING_PROPERTY = "file.encoding"; private static final String[] STANDARD_EXCLUDE_SUFFIXES = {"README.*", "\\.xml", "\\.txt"}; private static final String[] STANDARD_PRELOADED_CLASSES = { "com.sun.tools.xjc.addon.episode.package-info", "com.sun.tools.xjc.reader.xmlschema.bindinfo.package-info", "org.glassfish.jaxb.core.v2.model.core.package-info", "org.glassfish.jaxb.runtime.v2.model.runtime.package-info", "org.glassfish.jaxb.core.v2.schemagen.episode.package-info", "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.package-info" }; static { // The standard exclude filters contain simple, exclude pattern filters. final List> tmp = new ArrayList>(); tmp.add(new PatternFileFilter(Arrays.asList(STANDARD_EXCLUDE_SUFFIXES), true)); tmp.add(new FileFilterAdapter(new FileFilter() { @Override public boolean accept(final File aFileOrDir) { // Check sanity if (aFileOrDir == null) { return false; } final String name = aFileOrDir.getName(); // Ignore hidden files and CVS directories return name.startsWith(".") || (aFileOrDir.isDirectory() && name.equals("CVS")); } })); // Make STANDARD_EXCLUDE_FILTERS be unmodifiable. STANDARD_EXCLUDE_FILTERS = Collections.unmodifiableList(tmp); // TODO: These are hardcoded. Move to overridable using a system property? // Preload relevant package-info classes to work around MNG-6506. try { final ClassLoader cl = AbstractJaxbMojo.class.getClassLoader(); for(String current : STANDARD_PRELOADED_CLASSES) { cl.loadClass(current); } } catch (ClassNotFoundException ex) { throw new Error(ex); } } /** * The Plexus BuildContext is used to identify files or directories modified since last build, * implying functionality used to define if java generation must be performed again. */ @Component private BuildContext buildContext; /** * The injected Maven project. */ @Parameter(defaultValue = "${project}", readonly = true) private MavenProject project; /** * Note that the execution parameter will be injected ONLY if this plugin is executed as part * of a maven standard lifecycle - as opposed to directly invoked with a direct invocation. * When firing this mojo directly (i.e. {@code mvn xjc:something} or {@code mvn schemagen:something}), the * {@code execution} object will not be injected. */ @Parameter(defaultValue = "${mojoExecution}", readonly = true) private MojoExecution execution; /** *

The directory where the staleFile is found. * The staleFile assists in determining if re-generation of JAXB build products is required.

*

While it is permitted to re-define the staleFileDirectory, it is recommended to keep it * below the ${project.build.directory}, to ensure that JAXB code or XSD re-generation * occurs after cleaning the project.

* * @since 2.0 */ @Parameter(defaultValue = "${project.build.directory}/jaxb2", readonly = true, required = true) protected File staleFileDirectory; /** *

Defines the encoding used by XJC (for generating Java Source files) and schemagen (for generating XSDs). * The corresponding argument parameter for XJC and SchemaGen is: {@code encoding}.

*

The algorithm for finding the encoding to use is as follows * (where the first non-null value found is used for encoding): *

    *
  1. If the configuration property is explicitly given within the plugin's configuration, use that value.
  2. *
  3. If the Maven property project.build.sourceEncoding is defined, use its value.
  4. *
  5. Otherwise use the value from the system property file.encoding.
  6. *
*

* * @see #getEncoding(boolean) * @since 2.0 */ @Parameter(defaultValue = "${project.build.sourceEncoding}") private String encoding; /** *

A Locale definition to create and set the system (default) Locale when the XJB or SchemaGen tools executes. * The Locale will be reset to its default value after the execution of XJC or SchemaGen is complete.

*

The configuration parameter must be supplied on the form {@code language[,country[,variant]]}, * such as {@code sv,SE} or {@code fr}. Refer to * {@code org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet.createFor(String, Log)} for further * information.

*

Example (assigns french locale):

*
     *     
     *         <configuration>
     *              <locale>fr</locale>
     *         </configuration>
     *     
     * 
* * @see org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet#createFor(String, Log) * @see Locale#getAvailableLocales() * @since 2.2 */ @Parameter(required = false) protected String locale; /** *

Defines a set of extra EnvironmentFacet instances which are used to further configure the * ToolExecutionEnvironment used by this plugin to fire XJC or SchemaGen.

*

Example: If you implement the EnvironmentFacet interface in the class * {@code org.acme.MyCoolEnvironmentFacetImplementation}, its {@code setup()} method is called before the * XJC or SchemaGen tools are executed to setup some facet of their Execution environment. Correspondingly, the * {@code restore()} method in your {@code org.acme.MyCoolEnvironmentFacetImplementation} class is invoked after * the XJC or SchemaGen execution terminates.

*
     *     
     *         <configuration>
     *         ...
     *              <extraFacets>
     *                  <extraFacet implementation="org.acme.MyCoolEnvironmentFacetImplementation" />
     *              </extraFacets>
     *         ...
     *         </configuration>
     *     
     * 
* * @see EnvironmentFacet * @see org.codehaus.mojo.jaxb2.shared.environment.ToolExecutionEnvironment#add(EnvironmentFacet) * @since 2.2 */ @Parameter(required = false) protected List extraFacets; /** * Adds the supplied Resource to the project using the appropriate scope (i.e. resource or testResource) * depending on the exact implementation of this AbstractJaxbMojo. * * @param resource The resource to add. */ protected abstract void addResource(final Resource resource); /** * The Plexus BuildContext is used to identify files or directories modified since last build, * implying functionality used to define if java generation must be performed again. * * @return the active Plexus BuildContext. */ protected final BuildContext getBuildContext() { return getInjectedObject(buildContext, "buildContext"); } /** * @return The active MavenProject. */ protected final MavenProject getProject() { return getInjectedObject(project, "project"); } /** * @return The active MojoExecution. */ public MojoExecution getExecution() { return getInjectedObject(execution, "execution"); } /** * {@inheritDoc} */ @Override public final void execute() throws MojoExecutionException, MojoFailureException { // 0) Get the log and its relevant level final Log log = getLog(); final boolean isDebugEnabled = log.isDebugEnabled(); final boolean isInfoEnabled = log.isInfoEnabled(); // 1) Should we skip execution? if (shouldExecutionBeSkipped()) { if (isDebugEnabled) { log.debug("Skipping execution, as instructed."); } return; } // 2) Printout relevant version information. if (isDebugEnabled) { logPluginAndJaxbDependencyInfo(); } // 3) Are generated files stale? if (isReGenerationRequired()) { if (performExecution()) { // As instructed by the performExecution() method, update // the timestamp of the stale File. updateStaleFileTimestamp(); // Hack to support M2E buildContext.refresh(getOutputDirectory()); } else if (isInfoEnabled) { log.info("Not updating staleFile timestamp as instructed."); } } else if (isInfoEnabled) { log.info("No changes detected in schema or binding files - skipping JAXB generation."); } // 4) If the output directories exist, add them to the MavenProject's source directories if (getOutputDirectory().exists() && getOutputDirectory().isDirectory()) { final String canonicalPathToOutputDirectory = FileSystemUtilities.getCanonicalPath(getOutputDirectory()); if (log.isDebugEnabled()) { log.debug("Adding existing JAXB outputDirectory [" + canonicalPathToOutputDirectory + "] to Maven's sources."); } // Add the output Directory. getProject().addCompileSourceRoot(canonicalPathToOutputDirectory); } } /** * Implement this method to check if this AbstractJaxbMojo should skip executing altogether. * * @return {@code true} to indicate that this AbstractJaxbMojo should bail out of its execute method. */ protected abstract boolean shouldExecutionBeSkipped(); /** * @return {@code true} to indicate that this AbstractJaxbMojo should be run since its generated files were * either stale or not present, and {@code false} otherwise. */ protected abstract boolean isReGenerationRequired(); /** *

Implement this method to perform this Mojo's execution. * This method will only be called if {@code !shouldExecutionBeSkipped() && isReGenerationRequired()}.

* * @return {@code true} if the timestamp of the stale file should be updated. * @throws MojoExecutionException if an unexpected problem occurs. * Throwing this exception causes a "BUILD ERROR" message to be displayed. * @throws MojoFailureException if an expected problem (such as a compilation failure) occurs. * Throwing this exception causes a "BUILD FAILURE" message to be displayed. */ protected abstract boolean performExecution() throws MojoExecutionException, MojoFailureException; /** * Override this method to acquire a List holding all URLs to the sources which this * AbstractJaxbMojo should use to produce its output (XSDs files for AbstractXsdGeneratorMojos and * Java Source Code for AbstractJavaGeneratorMojos). * * @return A non-null List holding URLs to sources used by this AbstractJaxbMojo to produce its output. */ protected abstract List getSources(); /** * Retrieves the directory where the generated files should be written to. * * @return the directory where the generated files should be written to. */ protected abstract File getOutputDirectory(); /** * Retrieves the configured List of paths from which this AbstractJaxbMojo and its internal toolset * (XJC or SchemaGen) should read bytecode classes. * * @return the configured List of paths from which this AbstractJaxbMojo and its internal toolset (XJC or * SchemaGen) should read classes. * @throws org.apache.maven.plugin.MojoExecutionException if the classpath could not be retrieved. */ protected abstract List getClasspath() throws MojoExecutionException; /** * Convenience method to invoke when some plugin configuration is incorrect. * Will output the problem as a warning with some degree of log formatting. * * @param propertyName The name of the problematic property. * @param description The problem description. */ @SuppressWarnings("all") protected void warnAboutIncorrectPluginConfiguration(final String propertyName, final String description) { final StringBuilder builder = new StringBuilder(); builder.append("\n+=================== [Incorrect Plugin Configuration Detected]\n"); builder.append("|\n"); builder.append("| Property : " + propertyName + "\n"); builder.append("| Problem : " + description + "\n"); builder.append("|\n"); builder.append("+=================== [End Incorrect Plugin Configuration Detected]\n\n"); getLog().warn(builder.toString().replace("\n", NEWLINE)); } /** * @param arguments The final arguments to be passed to a JAXB tool (XJC or SchemaGen). * @param toolName The name of the tool. * @return the arguments, untouched. */ protected final String[] logAndReturnToolArguments(final String[] arguments, final String toolName) { // Check sanity Validate.notNull(arguments, "arguments"); if (getLog().isDebugEnabled()) { final StringBuilder argBuilder = new StringBuilder(); argBuilder.append("\n+=================== [" + arguments.length + " " + toolName + " Arguments]\n"); argBuilder.append("|\n"); for (int i = 0; i < arguments.length; i++) { argBuilder.append("| [").append(i).append("]: ").append(arguments[i]).append("\n"); } argBuilder.append("|\n"); argBuilder.append("+=================== [End " + arguments.length + " " + toolName + " Arguments]\n\n"); getLog().debug(argBuilder.toString().replace("\n", NEWLINE)); } // All done. return arguments; } /** * Retrieves the last name part of the stale file. * The full name of the stale file will be generated by pre-pending {@code "." + getExecution().getExecutionId()} * before this staleFileName. * * @return The name of the stale file used by this AbstractJavaGeneratorMojo to detect staleness amongst its * generated files. */ protected abstract String getStaleFileName(); /** * Acquires the staleFile for this execution * * @return the staleFile (used to define where) for this execution */ protected final File getStaleFile() { final String staleFileName = "." + (getExecution() == null ? "nonExecutionJaxb" : getExecution().getExecutionId()) + "-" + getStaleFileName(); return new File(staleFileDirectory, staleFileName); } /** *

The algorithm for finding the encoding to use is as follows (where the first non-null value found * is used for encoding):

*
    *
  1. If the configuration property is explicitly given within the plugin's configuration, use that value.
  2. *
  3. If the Maven property project.build.sourceEncoding is defined, use its value.
  4. *
  5. Otherwise use the value from the system property file.encoding.
  6. *
* * @param warnIfPlatformEncoding Defines if a warning should be logged if encoding is not configured but * the platform encoding (system property {@code file.encoding}) is used * @return The encoding to be used by this AbstractJaxbMojo and its tools. * @see #encoding */ protected final String getEncoding(final boolean warnIfPlatformEncoding) { // Harvest information final boolean configuredEncoding = encoding != null; final String fileEncoding = System.getProperty(SYSTEM_FILE_ENCODING_PROPERTY); final String effectiveEncoding = configuredEncoding ? encoding : fileEncoding; // Should we warn if using platform encoding (i.e. platform dependent)? if (!configuredEncoding && warnIfPlatformEncoding) { getLog().warn("Using platform encoding [" + effectiveEncoding + "], i.e. build is platform dependent!"); } else if (getLog().isDebugEnabled()) { getLog().debug("Using " + (configuredEncoding ? "explicitly configured" : "system property") + " encoding [" + effectiveEncoding + "]"); } // All Done. return effectiveEncoding; } /** * Retrieves a File to the JAXB Episode (which is normally written during the XJC process). * Moreover, ensures that the parent directory of that File is created, to enable writing the File. * * @param episodeFileName {@code null} to indicate that the standard episode file name ("sun-jaxb.episode") * should be used, and otherwise a non-empty name which should be used * as the episode file name. * @return A non-null File where the JAXB episode file should be written. * @throws MojoExecutionException if the parent directory of the episode file could not be created. */ protected File getEpisodeFile(final String episodeFileName) throws MojoExecutionException { // Get the execution ID final String executionID = getExecution() != null && getExecution().getExecutionId() != null ? getExecution().getExecutionId() : null; final String effectiveEpisodeFileName = episodeFileName == null ? (executionID == null ? STANDARD_EPISODE_FILENAME : "episode_" + executionID) : episodeFileName; if (effectiveEpisodeFileName.isEmpty()) { throw new MojoExecutionException("Cannot handle null or empty JAXB Episode filename. " + "Check 'episodeFileName' configuration property."); } // Find or create the episode directory. final Path episodePath; final File generatedJaxbEpisodeDirectory; try { final Path path = Paths.get(getOutputDirectory().getAbsolutePath(), "META-INF", "JAXB"); episodePath = java.nio.file.Files.createDirectories(path); generatedJaxbEpisodeDirectory = episodePath.toFile(); if (getLog().isInfoEnabled()) { getLog().info("Created EpisodePath [" + episodePath.toString() + "]: " + (generatedJaxbEpisodeDirectory.exists() && generatedJaxbEpisodeDirectory.isDirectory())); } } catch (IOException e) { throw new MojoExecutionException("Could not create output directory.", e); } if (!generatedJaxbEpisodeDirectory.exists() || !generatedJaxbEpisodeDirectory.isDirectory()) { throw new MojoExecutionException("Could not create directory [" + episodePath.toString() + "]"); } // Is there already an episode file here? File episodeFile = new File(generatedJaxbEpisodeDirectory, effectiveEpisodeFileName + ".xjb"); final AtomicInteger index = new AtomicInteger(1); while (episodeFile.exists()) { episodeFile = new File(generatedJaxbEpisodeDirectory, effectiveEpisodeFileName + "_" + index.getAndIncrement() + ".xjb"); } // Add the (generated) outputDirectory to the Resources. final Resource outputDirectoryResource = new Resource(); outputDirectoryResource.setDirectory(getOutputDirectory().getAbsolutePath()); outputDirectoryResource.setIncludes(Collections.singletonList("**/" + episodeFile.getName())); this.addResource(outputDirectoryResource); // All Done. return episodeFile; } // // Private helpers // private void logPluginAndJaxbDependencyInfo() { if (getLog().isDebugEnabled()) { final StringBuilder builder = new StringBuilder(); builder.append("\n+=================== [Brief Plugin Build Dependency Information]\n"); builder.append("|\n"); builder.append("| Note: These dependencies pertain to what was used to build *the plugin*.\n"); builder.append("| Check project dependencies to see the ones used in *your build*.\n"); builder.append("|\n"); // Find the dependency and version information within the dependencies.properties file. final SortedMap versionMap = DependsFileParser.getVersionMap(OWN_ARTIFACT_ID); builder.append("|\n"); builder.append("| Plugin's own information\n"); builder.append("| GroupId : " + versionMap.get(DependsFileParser.OWN_GROUPID_KEY) + "\n"); builder.append("| ArtifactID : " + versionMap.get(DependsFileParser.OWN_ARTIFACTID_KEY) + "\n"); builder.append("| Version : " + versionMap.get(DependsFileParser.OWN_VERSION_KEY) + "\n"); builder.append("| Buildtime : " + versionMap.get(DependsFileParser.BUILDTIME_KEY) + "\n"); builder.append("|\n"); builder.append("| Plugin's JAXB-related dependencies\n"); builder.append("|\n"); final SortedMap diMap = DependsFileParser.createDependencyInfoMap(versionMap); int dependencyIndex = 0; for (Map.Entry current : diMap.entrySet()) { final String key = current.getKey().trim(); for (String currentRelevantGroupId : RELEVANT_GROUPIDS) { if (key.startsWith(currentRelevantGroupId)) { final DependencyInfo di = current.getValue(); builder.append("| " + (++dependencyIndex) + ") [" + di.getArtifactId() + "]\n"); builder.append("| GroupId : " + di.getGroupId() + "\n"); builder.append("| ArtifactID : " + di.getArtifactId() + "\n"); builder.append("| Version : " + di.getVersion() + "\n"); builder.append("| Scope : " + di.getScope() + "\n"); builder.append("| Type : " + di.getType() + "\n"); builder.append("|\n"); } } } builder.append("+=================== [End Brief Plugin Build Dependency Information]\n\n"); getLog().debug(builder.toString().replace("\n", NEWLINE)); } } private T getInjectedObject(final T objectOrNull, final String objectName) { if (objectOrNull == null) { getLog().error( "Found null '" + objectName + "', implying that Maven @Component injection was not done properly."); } return objectOrNull; } private void updateStaleFileTimestamp() throws MojoExecutionException { final File staleFile = getStaleFile(); if (!staleFile.exists()) { // Ensure that the staleFileDirectory exists FileSystemUtilities.createDirectory(staleFile.getParentFile(), false); try { staleFile.createNewFile(); if (getLog().isDebugEnabled()) { getLog().debug("Created staleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]"); } } catch (IOException e) { throw new MojoExecutionException("Could not create staleFile.", e); } } else { if (!staleFile.setLastModified(System.currentTimeMillis())) { getLog().warn("Failed updating modification time of staleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]"); } } } /** * Prints out the system properties to the Maven Log at Debug level. */ protected void logSystemPropertiesAndBasedir() { if (getLog().isDebugEnabled()) { final StringBuilder builder = new StringBuilder(); builder.append("\n+=================== [System properties]\n"); builder.append("|\n"); // Sort the system properties final SortedMap props = new TreeMap(); props.put("basedir", FileSystemUtilities.getCanonicalPath(getProject().getBasedir())); for (Map.Entry current : System.getProperties().entrySet()) { props.put("" + current.getKey(), current.getValue()); } for (Map.Entry current : props.entrySet()) { builder.append("| [" + current.getKey() + "]: " + current.getValue() + "\n"); } builder.append("|\n"); builder.append("+=================== [End System properties]\n"); // All done. getLog().debug(builder.toString().replace("\n", NEWLINE)); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy