Java tutorial
/** * Copyright (C) 2014-2015 Philip Helger (www.helger.com) * philip[at]helger[dot]com * * Licensed 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. */ package com.helger.maven.buildinfo; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.maven.model.Dependency; import org.apache.maven.model.Exclusion; import org.apache.maven.model.Plugin; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.joda.time.DateTime; import org.slf4j.impl.StaticLoggerBinder; import com.helger.commons.CGlobal; import com.helger.commons.SystemProperties; import com.helger.commons.collections.CollectionHelper; import com.helger.commons.io.file.FileIOError; import com.helger.commons.io.file.FileOperations; import com.helger.commons.io.file.FileUtils; import com.helger.commons.io.resource.FileSystemResource; import com.helger.commons.microdom.reader.XMLMapHandler; import com.helger.commons.regex.RegExHelper; import com.helger.commons.string.StringHelper; import com.helger.datetime.PDTFactory; import com.helger.datetime.config.PDTConfig; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * @author Philip Helger * @goal generate-buildinfo * @phase generate-resources * @description Create build information at compile time. The information will * be part of the created JAR/WAR/... file. The resulting file will * reside in the <code>META-INF</code> directory of the created * artifact. */ @SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "set via maven property") public final class GenerateBuildInfoMojo extends AbstractMojo { /** The name of the XML file */ private static final String DEFAULT_FILENAME_BUILDINFO_XML = "buildinfo.xml"; /** The name of the properties file */ private static final String DEFAULT_FILENAME_BUILDINFO_PROPERTIES = "buildinfo.properties"; /** * The Maven Project. * * @parameter property=project * @required * @readonly */ private MavenProject project; /** * @parameter property=reactorProjects * @required * @readonly */ private List<MavenProject> reactorProjects; /** * The directory where the temporary buildinfo files will be saved. * * @required * @parameter property=tempDirectory * default-value="${project.build.directory}/buildinfo-maven-plugin" */ private File tempDirectory; /** * Set the time zone to be used. Use "UTC" for the universal timezone. * Otherwise Strings like "Europe/Vienna" should be used. * * @parameter property="timeZone" */ @SuppressWarnings("unused") private String timeZone; /** * Should all system properties be emitted into the build info? If this flag * is set, the selectedSystemProperties are cleared, so either this flag or * the selectedSystemProperties should be used.<br> * All contained system properties are prefixed with * <code>systemproperty.</code> in the generated file. * * @parameter property="withAllSystemProperties" default-value="false" */ private boolean withAllSystemProperties = false; /** * A selected subset of system property names to be emitted. Each element can * be a regular expression to match more than one potential system property. * If this set is not empty, the withSystemProperties property should not need * to be enabled.<br> * All contained system properties are prefixed with * <code>systemproperty.</code> in the generated file. * * @parameter property="selectedSystemProperties" */ private HashSet<String> selectedSystemProperties; /** * A selected subset of system property names to be ignored. Each element can * be a regular expression to match more than one potential system property. * Ignored system properties take precedence over selected system properties. * They are also ignored if withAllSystemProperties is set to * <code>true</code>. * * @parameter property="ignoredSystemProperties" */ private HashSet<String> ignoredSystemProperties; /** * Should all environment variables be emitted into the build info? If this * flag is set, the selectedEnvVars are cleared, so either this flag or the * selectedEnvVars should be used.<br> * All contained environment variables are prefixed with <code>envvar.</code> * in the generated file. * * @parameter property="withAllEnvVars" default-value="false" */ private boolean withAllEnvVars = false; /** * A selected subset of environment variables names to be emitted. Each * element can be a regular expression to match more than one potential * environment variables. If this set is not empty, the withEnvVars property * does not need to be enabled.<br> * All contained environment variables are prefixed with <code>envvar.</code> * in the generated file. * * @parameter property="selectedEnvVars" */ private HashSet<String> selectedEnvVars; /** * A selected subset of environment variables names to be ignored. Each * element can be a regular expression to match more than one potential * environment variables. Ignored environment variables take precedence over * selected environment variables. They are also ignored if withAllEnvVars is * set to <code>true</code>. * * @parameter property="ignoredEnvVars" */ private HashSet<String> ignoredEnvVars; /** * Generate build info in .XML format? It is safe to generate multiple formats * in one run!<br> * The generated file has the following layout: * * <pre> * <mapping> * <map key="buildinfo.version" value="2" /> * <map key="project.groupid" value="com.helger.maven" /> * ... * </mapping> * </pre> * * @parameter property="formatXML" default-value="true" */ private boolean formatXML = true; /** * Generate build info in .properties format? It is safe to generate multiple * formats in one run! * * @parameter property="formatProperties" default-value="false" */ private boolean formatProperties = false; public void setTempDirectory(@Nonnull final File aDir) { tempDirectory = aDir; if (!tempDirectory.isAbsolute()) tempDirectory = new File(project.getBasedir(), aDir.getPath()); final FileIOError aResult = FileOperations.createDirRecursiveIfNotExisting(tempDirectory); if (aResult.isFailure()) getLog().error("Failed to create temp directory " + aResult.toString()); else getLog().info("Successfully created temp directory " + aDir.toString()); } @SuppressFBWarnings(value = "URF_UNREAD_FIELD") public void setTimeZone(final String sTimeZone) { if (PDTConfig.setDefaultDateTimeZoneID(sTimeZone).isSuccess()) timeZone = sTimeZone; else getLog().warn("Unknown time zone '" + sTimeZone + "'"); } public void setWithAllSystemProperties(final boolean bEnable) { withAllSystemProperties = bEnable; if (withAllSystemProperties) { // No selection if we have all system properties if (selectedSystemProperties != null && !selectedSystemProperties.isEmpty()) { getLog().warn( "Clearing all selected system properties, because all system properties are enabled!"); setSelectedSystemProperties(null); } } } // Important: parameter type must match member type! public void setSelectedSystemProperties(final Set<String> aCollection) { selectedSystemProperties = new HashSet<String>(); if (aCollection != null) { for (final String sName : aCollection) if (StringHelper.hasText(sName)) if (!selectedSystemProperties.add(sName)) getLog().warn("The selected system property '" + sName + "' is contained more than once"); } if (!selectedSystemProperties.isEmpty()) { // If we have a set of selected, don't use all system properties if (withAllSystemProperties) { getLog().warn("Disabling all system properties, because selected system properties are defined!"); setWithAllSystemProperties(false); } } } // Important: parameter type must match member type! public void setIgnoredSystemProperties(final Set<String> aCollection) { ignoredSystemProperties = new HashSet<String>(); if (aCollection != null) { for (final String sName : aCollection) if (StringHelper.hasText(sName)) if (!ignoredSystemProperties.add(sName)) getLog().warn("The ignored system property '" + sName + "' is contained more than once"); } } public void setWithAllEnvVars(final boolean bEnable) { withAllEnvVars = bEnable; if (withAllEnvVars) { // No selection if we have all environment variables if (selectedEnvVars != null && !selectedEnvVars.isEmpty()) { getLog().warn("Clearing all environment variables, because all environment variables are enabled!"); setSelectedEnvVars(null); } } } // Important: parameter type must match member type! public void setSelectedEnvVars(final Set<String> aCollection) { selectedEnvVars = new HashSet<String>(); if (aCollection != null) { for (final String sName : aCollection) if (StringHelper.hasText(sName)) if (!selectedEnvVars.add(sName)) getLog().warn( "The selected environment variable '" + sName + "' is contained more than once"); } if (!selectedEnvVars.isEmpty()) { // If we have a set of selected, don't use all if (withAllEnvVars) { getLog().warn( "Disabling all environment variables, because selected environment variables are defined!"); setWithAllEnvVars(false); } } } // Important: parameter type must match member type! public void setIgnoredEnvVars(final Set<String> aCollection) { ignoredEnvVars = new HashSet<String>(); if (aCollection != null) { for (final String sName : aCollection) if (StringHelper.hasText(sName)) if (!ignoredEnvVars.add(sName)) getLog().warn( "The ignored environment variable '" + sName + "' is contained more than once"); } } public void setFormatXML(final boolean bEnable) { formatXML = bEnable; } public void setFormatProperties(final boolean bEnable) { formatProperties = bEnable; } private static boolean _matches(@Nullable final Set<String> aSet, @Nonnull final String sName) { if (aSet == null) return false; // Direct match? if (aSet.contains(sName)) return true; // RegEx match? for (final String sSelected : aSet) if (RegExHelper.stringMatchesPattern(sSelected, sName)) return true; // No match! return false; } private Map<String, String> _determineBuildInfoProperties() { // Get the current time, using the time zone specified in the settings final DateTime aDT = PDTFactory.getCurrentDateTime(); // Build the default properties final Map<String, String> aProps = new LinkedHashMap<String, String>(); // Version 1: initial // Version 2: added dependency information; added per build plugin the key // property aProps.put("buildinfo.version", "2"); // Project information aProps.put("project.groupid", project.getGroupId()); aProps.put("project.artifactid", project.getArtifactId()); aProps.put("project.version", project.getVersion()); aProps.put("project.name", project.getName()); aProps.put("project.packaging", project.getPackaging()); // Parent project information (if available) final MavenProject aParentProject = project.getParent(); if (aParentProject != null) { aProps.put("parentproject.groupid", aParentProject.getGroupId()); aProps.put("parentproject.artifactid", aParentProject.getArtifactId()); aProps.put("parentproject.version", aParentProject.getVersion()); aProps.put("parentproject.name", aParentProject.getName()); } // All reactor projects (nested projects) // Don't emit this, if this is "1" as than only the current project would be // listed if (reactorProjects != null && reactorProjects.size() != 1) { final String sPrefix = "reactorproject."; // The number of reactor projects aProps.put(sPrefix + "count", Integer.toString(reactorProjects.size())); // Show details of all reactor projects, index starting at 0 int nIndex = 0; for (final MavenProject aReactorProject : reactorProjects) { aProps.put(sPrefix + nIndex + ".groupid", aReactorProject.getGroupId()); aProps.put(sPrefix + nIndex + ".artifactid", aReactorProject.getArtifactId()); aProps.put(sPrefix + nIndex + ".version", aReactorProject.getVersion()); aProps.put(sPrefix + nIndex + ".name", aReactorProject.getName()); ++nIndex; } } // Build Plugins final List<?> aBuildPlugins = project.getBuildPlugins(); if (aBuildPlugins != null) { final String sPrefix = "build.plugin."; // The number of build plugins aProps.put(sPrefix + "count", Integer.toString(aBuildPlugins.size())); // Show details of all plugins, index starting at 0 int nIndex = 0; for (final Object aObj : aBuildPlugins) { final Plugin aPlugin = (Plugin) aObj; aProps.put(sPrefix + nIndex + ".groupid", aPlugin.getGroupId()); aProps.put(sPrefix + nIndex + ".artifactid", aPlugin.getArtifactId()); aProps.put(sPrefix + nIndex + ".version", aPlugin.getVersion()); final Object aConfiguration = aPlugin.getConfiguration(); if (aConfiguration != null) { // Will emit an XML structure! aProps.put(sPrefix + nIndex + ".configuration", aConfiguration.toString()); } aProps.put(sPrefix + nIndex + ".key", aPlugin.getKey()); ++nIndex; } } // Build dependencies final List<?> aDependencies = project.getDependencies(); if (aDependencies != null) { final String sDepPrefix = "dependency."; // The number of build plugins aProps.put(sDepPrefix + "count", Integer.toString(aDependencies.size())); // Show details of all dependencies, index starting at 0 int nDepIndex = 0; for (final Object aDepObj : aDependencies) { final Dependency aDependency = (Dependency) aDepObj; aProps.put(sDepPrefix + nDepIndex + ".groupid", aDependency.getGroupId()); aProps.put(sDepPrefix + nDepIndex + ".artifactid", aDependency.getArtifactId()); aProps.put(sDepPrefix + nDepIndex + ".version", aDependency.getVersion()); aProps.put(sDepPrefix + nDepIndex + ".type", aDependency.getType()); if (aDependency.getClassifier() != null) aProps.put(sDepPrefix + nDepIndex + ".classifier", aDependency.getClassifier()); aProps.put(sDepPrefix + nDepIndex + ".scope", aDependency.getScope()); if (aDependency.getSystemPath() != null) aProps.put(sDepPrefix + nDepIndex + ".systempath", aDependency.getSystemPath()); aProps.put(sDepPrefix + nDepIndex + ".optional", Boolean.toString(aDependency.isOptional())); aProps.put(sDepPrefix + nDepIndex + ".managementkey", aDependency.getManagementKey()); // Add all exclusions of the current dependency final List<?> aExclusions = aDependency.getExclusions(); if (aExclusions != null) { final String sExclusionPrefix = sDepPrefix + nDepIndex + ".exclusion."; // The number of build plugins aProps.put(sExclusionPrefix + "count", Integer.toString(aExclusions.size())); // Show details of all dependencies, index starting at 0 int nExclusionIndex = 0; for (final Object aExclusionObj : aExclusions) { final Exclusion aExclusion = (Exclusion) aExclusionObj; aProps.put(sExclusionPrefix + nExclusionIndex + ".groupid", aExclusion.getGroupId()); aProps.put(sExclusionPrefix + nExclusionIndex + ".artifactid", aExclusion.getArtifactId()); ++nExclusionIndex; } } ++nDepIndex; } } // Build date and time aProps.put("build.datetime", aDT.toString()); aProps.put("build.datetime.millis", Long.toString(aDT.getMillis())); aProps.put("build.datetime.date", aDT.toLocalDate().toString()); aProps.put("build.datetime.time", aDT.toLocalTime().toString()); aProps.put("build.datetime.timezone", aDT.getZone().getID()); final int nOfsMilliSecs = aDT.getZone().getOffset(aDT); aProps.put("build.datetime.timezone.offsethours", Long.toString(nOfsMilliSecs / CGlobal.MILLISECONDS_PER_HOUR)); aProps.put("build.datetime.timezone.offsetmins", Long.toString(nOfsMilliSecs / CGlobal.MILLISECONDS_PER_MINUTE)); aProps.put("build.datetime.timezone.offsetsecs", Long.toString(nOfsMilliSecs / CGlobal.MILLISECONDS_PER_SECOND)); aProps.put("build.datetime.timezone.offsetmillisecs", Integer.toString(nOfsMilliSecs)); // Emit system properties? if (withAllSystemProperties || CollectionHelper.isNotEmpty(selectedSystemProperties)) for (final Map.Entry<String, String> aEntry : CollectionHelper .getSortedByKey(SystemProperties.getAllProperties()).entrySet()) { final String sName = aEntry.getKey(); if (withAllSystemProperties || _matches(selectedSystemProperties, sName)) if (!_matches(ignoredSystemProperties, sName)) aProps.put("systemproperty." + sName, aEntry.getValue()); } // Emit environment variable? if (withAllEnvVars || CollectionHelper.isNotEmpty(selectedEnvVars)) for (final Map.Entry<String, String> aEntry : CollectionHelper.getSortedByKey(System.getenv()) .entrySet()) { final String sName = aEntry.getKey(); if (withAllEnvVars || _matches(selectedEnvVars, sName)) if (!_matches(ignoredEnvVars, sName)) aProps.put("envvar." + sName, aEntry.getValue()); } return aProps; } private void _writeBuildinfoXML(final Map<String, String> aProps) throws MojoExecutionException { // Write the XML in the format that it can easily be read by the // com.helger.common.microdom.reader.XMLMapHandler class final File aFile = new File(tempDirectory, DEFAULT_FILENAME_BUILDINFO_XML); if (XMLMapHandler.writeMap(aProps, new FileSystemResource(aFile)).isFailure()) throw new MojoExecutionException("Failed to write XML file to " + aFile); getLog().debug("Wrote buildinfo XML file to " + aFile); } private void _writeBuildinfoProperties(final Map<String, String> aProps) throws MojoExecutionException { // Write properties file final File aFile = new File(tempDirectory, DEFAULT_FILENAME_BUILDINFO_PROPERTIES); final Properties p = new Properties(); p.putAll(aProps); try { p.store(FileUtils.getOutputStream(aFile), "Generated - do not edit!"); } catch (final IOException ex) { throw new MojoExecutionException("Failed to write properties file to " + aFile, ex); } getLog().debug("Wrote buildinfo properties file to " + aFile); } public void execute() throws MojoExecutionException { StaticLoggerBinder.getSingleton().setMavenLog(getLog()); if (tempDirectory == null) throw new MojoExecutionException("No buildinfo temp directory specified!"); if (tempDirectory.exists() && !tempDirectory.isDirectory()) throw new MojoExecutionException( "The specified buildinfo temp directory " + tempDirectory + " is not a directory!"); if (!tempDirectory.exists()) { // Ensure that the directory exists if (!tempDirectory.mkdirs()) throw new MojoExecutionException("Failed to create buildinfo temp directory " + tempDirectory); getLog().info("Created buildinfo temp directory " + tempDirectory); } if (!formatProperties && !formatXML) throw new MojoExecutionException( "No buildinfo output format was specified. Nothing will be generated!"); final Map<String, String> aProps = _determineBuildInfoProperties(); if (formatXML) _writeBuildinfoXML(aProps); if (formatProperties) _writeBuildinfoProperties(aProps); // Add output directory as a resource-directory final Resource aResource = new Resource(); aResource.setDirectory(tempDirectory.getAbsolutePath()); aResource.addInclude("**/*"); aResource.setFiltering(false); aResource.setTargetPath("META-INF"); project.addResource(aResource); } }