Java tutorial
package org.codehaus.mojo.build; /** * The MIT License * * Copyright (c) 2015 Learning Commons, University of Calgary * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and * associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.text.MessageFormat; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.scm.ScmException; import org.apache.maven.scm.ScmFile; import org.apache.maven.scm.ScmFileSet; import org.apache.maven.scm.command.info.InfoItem; import org.apache.maven.scm.command.info.InfoScmResult; import org.apache.maven.scm.command.status.StatusScmResult; import org.apache.maven.scm.command.update.UpdateScmResult; import org.apache.maven.scm.command.update.UpdateScmResultWithRevision; import org.apache.maven.scm.log.ScmLogDispatcher; import org.apache.maven.scm.log.ScmLogger; import org.apache.maven.scm.manager.ScmManager; import org.apache.maven.scm.provider.ScmProvider; import org.apache.maven.scm.provider.git.gitexe.command.branch.GitBranchCommand; import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository; import org.apache.maven.scm.repository.ScmRepository; import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.StringUtils; /** * This mojo is designed to give you a build number. So when you might make 100 builds of version 1.0-SNAPSHOT, you can * differentiate between them all. * <p> * The build number is based on the revision number retrieved from SCM. It is known to work with Subversion, GIT, and * Mercurial. * <p> * This mojo can also check to make sure that you have checked everything into SCM, before issuing the build number. * That behaviour can be suppressed, and then the latest local build number is used. * <p> * Build numbers are not automatically reflected in your artifact's filename, but can be added to the metadata. You can * access the build number in your pom with ${buildNumber}. You can also access ${timestamp} and the SCM branch of the * build (if applicable) in ${SCMBranch} * <p> * Note that there are several <code><strong>doFoo</strong></code> parameters. These parameters (doCheck, doUpdate, etc) * are the first thing evaluated. If there is no matching expression, we get the default-value. If there is (ie * <code>-Dmaven.buildNumber.doUpdate=false</code>), we get that value. So if the XML contains * <tt><doCheck>true</doCheck></tt>, then normally that's the final value of the param in question. However, * this mojo reverses that behaviour, such that the command line parameters get the last say. * * @author <a href="mailto:woodj@ucalgary.ca">Julian Wood</a> * @version $Id$ */ @Mojo(name = "create", defaultPhase = LifecyclePhase.INITIALIZE, requiresProject = true, threadSafe = true) public class CreateMojo extends AbstractScmMojo { private static final String DEFAULT_BRANCH_NAME = "UNKNOWN_BRANCH"; /** * You can rename the buildNumber property name to another property name if desired. * * @since 1.0-beta-1 */ @Parameter(property = "maven.buildNumber.buildNumberPropertyName", defaultValue = "buildNumber") private String buildNumberPropertyName; /** * You can rename the timestamp property name to another property name if desired. * * @since 1.0-beta-1 */ @Parameter(property = "maven.buildNumber.timestampPropertyName", defaultValue = "timestamp") private String timestampPropertyName; /** * If this is made true, we check for modified files, and if there are any, we fail the build. Note that this used * to be inverted (skipCheck), but needed to be changed to allow releases to work. This corresponds to 'svn status'. * * @since 1.0-beta-1 */ @Parameter(property = "maven.buildNumber.doCheck", defaultValue = "false") private boolean doCheck; /** * If this is made true, then the revision will be updated to the latest in the repo, otherwise it will remain what * it is locally. Note that this used to be inverted (skipUpdate), but needed to be changed to allow releases to * work. This corresponds to 'svn update'. * * @since 1.0-beta-1 */ @Parameter(property = "maven.buildNumber.doUpdate", defaultValue = "false") private boolean doUpdate; /** * Specify a message as specified by java.text.MessageFormat. This triggers "items" configuration to be read * * @since 1.0-beta-1 */ @Parameter(property = "maven.buildNumber.format") private String format; /** * Properties file to be created when "format" is not null and item has "buildNumber". See Usage for details * * @since 1.0-beta-2 */ @Parameter(defaultValue = "${basedir}/buildNumber.properties") private File buildNumberPropertiesFileLocation; /** * Specify the corresponding items for the format message, as specified by java.text.MessageFormat. Special item * values are "scmVersion", "timestamp" and "buildNumber[digits]", where [digits] are optional digits added to the * end of the number to select a property. * * @since 1.0-beta-1 */ @Parameter private List<?> items; /** * The locale used for date and time formatting. The locale name should be in the format defined in * {@link Locale#toString()}. The default locale is the platform default returned by {@link Locale#getDefault()}. * * @since 1.0-beta-2 */ @Parameter(property = "maven.buildNumber.locale") private String locale; /** * whether to retrieve the revision for the last commit, or the last revision of the repository. * * @since 1.0-beta-2 */ @Parameter(property = "maven.buildNumber.useLastCommittedRevision", defaultValue = "false") private boolean useLastCommittedRevision; /** * Apply this java.text.MessageFormat to the timestamp only (as opposed to the <code>format</code> parameter). * * @since 1.0-beta-2 */ @Parameter(property = "maven.buildNumber.timestampFormat") private String timestampFormat; /** * Selects alternative SCM provider implementations. Each map key denotes the original provider type as given in the * SCM URL like "cvs" or "svn", the map value specifies the provider type of the desired implementation to use * instead. In other words, this map configures a substitition mapping for SCM providers. * * @since 1.0-beta-3 */ @Parameter private Map<String, String> providerImplementations; /** * If set to true, will get the scm revision once for all modules of a multi-module project instead of fetching once * for each module. * * @since 1.0-beta-3 */ @Parameter(property = "maven.buildNumber.getRevisionOnlyOnce", defaultValue = "false") private boolean getRevisionOnlyOnce; /** * You can rename the buildScmBranch property name to another property name if desired. * * @since 1.0-beta-4 */ @Parameter(property = "maven.buildNumber.scmBranchPropertyName", defaultValue = "scmBranch") private String scmBranchPropertyName; // ////////////////////////////////////// internal maven components /////////////////////////////////// /** * Contains the full list of projects in the reactor. * * @since 1.0-beta-3 */ @Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true) private List<MavenProject> reactorProjects; @Parameter(defaultValue = "${session}", readonly = true, required = true) private MavenSession session; // ////////////////////////////////////// internal variables /////////////////////////////////// private ScmLogDispatcher logger; private String revision; private boolean useScm; public void execute() throws MojoExecutionException, MojoFailureException { if (skip) { getLog().info("Skipping execution."); return; } if (providerImplementations != null) { for (Entry<String, String> entry : providerImplementations.entrySet()) { String providerType = entry.getKey(); String providerImplementation = entry.getValue(); getLog().info("Change the default '" + providerType + "' provider implementation to '" + providerImplementation + "'."); scmManager.setScmProviderImplementation(providerType, providerImplementation); } } Date now = Calendar.getInstance().getTime(); if (format != null) { if (items == null) { throw new MojoExecutionException( " if you set a format, you must provide at least one item, please check documentation "); } // needs to be an array // look for special values Object[] itemAry = new Object[items.size()]; for (int i = 0; i < items.size(); i++) { Object item = items.get(i); if (item instanceof String) { String s = (String) item; if (s.equals("timestamp")) { itemAry[i] = now; } else if (s.startsWith("scmVersion")) { useScm = true; itemAry[i] = getRevision(); } else if (s.startsWith("buildNumber")) { // check for properties file File propertiesFile = this.buildNumberPropertiesFileLocation; // create if not exists if (!propertiesFile.exists()) { try { if (!propertiesFile.getParentFile().exists()) { propertiesFile.getParentFile().mkdirs(); } propertiesFile.createNewFile(); } catch (IOException e) { throw new MojoExecutionException( "Couldn't create properties file: " + propertiesFile, e); } } Properties properties = new Properties(); String buildNumberString = null; FileInputStream inputStream = null; FileOutputStream outputStream = null; try { // get the number for the buildNumber specified inputStream = new FileInputStream(propertiesFile); properties.load(inputStream); buildNumberString = properties.getProperty(s); if (buildNumberString == null) { buildNumberString = "0"; } int buildNumber = Integer.valueOf(buildNumberString).intValue(); // store the increment properties.setProperty(s, String.valueOf(++buildNumber)); outputStream = new FileOutputStream(propertiesFile); properties.store(outputStream, "maven.buildNumber.plugin properties file"); // use in the message (format) itemAry[i] = new Integer(buildNumber); } catch (NumberFormatException e) { throw new MojoExecutionException( "Couldn't parse buildNumber in properties file to an Integer: " + buildNumberString); } catch (IOException e) { throw new MojoExecutionException("Couldn't load properties file: " + propertiesFile, e); } finally { IOUtil.close(inputStream); IOUtil.close(outputStream); } } else { itemAry[i] = item; } } else { itemAry[i] = item; } } revision = format(itemAry); } else { // Check if the plugin has already run. revision = project.getProperties().getProperty(this.buildNumberPropertyName); if (this.getRevisionOnlyOnce && revision != null) { getLog().debug("Revision available from previous execution"); return; } if (doCheck) { // we fail if there are local mods checkForLocalModifications(); } else { getLog().debug("Checking for local modifications: skipped."); } if (session.getSettings().isOffline()) { getLog().info("maven is executed in offline mode, Updating project files from SCM: skipped."); } else { if (doUpdate) { // we update your local repo // even after you commit, your revision stays the same until you update, thus this // action List<ScmFile> changedFiles = update(); for (ScmFile file : changedFiles) { getLog().debug("Updated: " + file); } if (changedFiles.size() == 0) { getLog().debug("No files needed updating."); } } else { getLog().debug("Updating project files from SCM: skipped."); } } revision = getRevision(); } if (project != null) { String timestamp = String.valueOf(now.getTime()); if (timestampFormat != null) { timestamp = MessageFormat.format(timestampFormat, new Object[] { now }); } getLog().info(MessageFormat.format("Storing buildNumber: {0} at timestamp: {1}", new Object[] { revision, timestamp })); if (revision != null) { project.getProperties().put(buildNumberPropertyName, revision); } project.getProperties().put(timestampPropertyName, timestamp); String scmBranch = getScmBranch(); getLog().info("Storing buildScmBranch: " + scmBranch); project.getProperties().put(scmBranchPropertyName, scmBranch); // Add the revision and timestamp properties to each project in the reactor if (getRevisionOnlyOnce && reactorProjects != null) { Iterator<MavenProject> projIter = reactorProjects.iterator(); while (projIter.hasNext()) { MavenProject nextProj = (MavenProject) projIter.next(); if (revision != null) { nextProj.getProperties().put(this.buildNumberPropertyName, revision); } nextProj.getProperties().put(this.timestampPropertyName, timestamp); nextProj.getProperties().put(this.scmBranchPropertyName, scmBranch); } } } } /** * Formats the given argument using the configured format template and locale. * * @param arguments arguments to be formatted @ @return formatted result */ private String format(Object[] arguments) { Locale l = Locale.getDefault(); if (locale != null) { String[] parts = locale.split("_", 3); if (parts.length <= 1) { l = new Locale(locale); } else if (parts.length == 2) { l = new Locale(parts[0], parts[1]); } else { l = new Locale(parts[0], parts[1], parts[2]); } } return new MessageFormat(format, l).format(arguments); } private void checkForLocalModifications() throws MojoExecutionException { getLog().debug("Verifying there are no local modifications ..."); List<ScmFile> changedFiles; try { changedFiles = getStatus(); } catch (ScmException e) { throw new MojoExecutionException("An error has occurred while checking scm status.", e); } if (!changedFiles.isEmpty()) { StringBuilder message = new StringBuilder(); String ls = System.getProperty("line.separator"); for (ScmFile file : changedFiles) { message.append(file.toString()); message.append(ls); } throw new MojoExecutionException( "Cannot create the build number because you have local modifications : \n" + message); } } public List<ScmFile> update() throws MojoExecutionException { try { ScmRepository repository = getScmRepository(); ScmProvider scmProvider = scmManager.getProviderByRepository(repository); UpdateScmResult result = scmProvider.update(repository, new ScmFileSet(scmDirectory)); if (result == null) { return Collections.emptyList(); } checkResult(result); if (result instanceof UpdateScmResultWithRevision) { String revision = ((UpdateScmResultWithRevision) result).getRevision(); getLog().info("Got a revision during update: " + revision); this.revision = revision; } return result.getUpdatedFiles(); } catch (ScmException e) { throw new MojoExecutionException("Couldn't update project. " + e.getMessage(), e); } } public List<ScmFile> getStatus() throws ScmException { ScmRepository repository = getScmRepository(); ScmProvider scmProvider = scmManager.getProviderByRepository(repository); StatusScmResult result = scmProvider.status(repository, new ScmFileSet(scmDirectory)); if (result == null) { return Collections.emptyList(); } checkResult(result); return result.getChangedFiles(); } /** * Get the branch info for this revision from the repository. For svn, it is in svn info. * * @return * @throws MojoExecutionException * @throws MojoExecutionException */ public String getScmBranch() throws MojoExecutionException { /* git branch can be obtained directly by a command */ try { ScmRepository repository = getScmRepository(); ScmProvider provider = scmManager.getProviderByRepository(repository); if (GitScmProviderRepository.PROTOCOL_GIT.equals(provider.getScmType())) { ScmFileSet fileSet = new ScmFileSet(scmDirectory); return GitBranchCommand.getCurrentBranch(getLogger(), (GitScmProviderRepository) repository.getProviderRepository(), fileSet); } } catch (ScmException e) { getLog().warn( "Cannot get the branch information from the git repository: \n" + e.getLocalizedMessage()); } return getScmBranchFromUrl(); } private String getScmBranchFromUrl() throws MojoExecutionException { String scmUrl = null; try { ScmRepository repository = getScmRepository(); InfoScmResult scmResult = info(repository, new ScmFileSet(scmDirectory)); if (scmResult == null || !scmResult.isSuccess()) { getLog().debug("Cannot get the branch information from the scm repository : " + (scmResult == null ? "" : scmResult.getCommandOutput())); return DEFAULT_BRANCH_NAME; } if (scmResult.getInfoItems().isEmpty()) { if (!StringUtils.isEmpty(revisionOnScmFailure)) { setDoCheck(false); setDoUpdate(false); return DEFAULT_BRANCH_NAME; } } if (!scmResult.getInfoItems().isEmpty()) { InfoItem info = scmResult.getInfoItems().get(0); scmUrl = info.getURL(); } } catch (ScmException e) { if (!StringUtils.isEmpty(revisionOnScmFailure)) { getLog().warn("Cannot get the branch information from the scm repository, proceeding with " + DEFAULT_BRANCH_NAME + " : \n" + e.getLocalizedMessage()); setDoCheck(false); setDoUpdate(false); return DEFAULT_BRANCH_NAME; } throw new MojoExecutionException( "Cannot get the branch information from the scm repository : \n" + e.getLocalizedMessage(), e); } return filterBranchFromScmUrl(scmUrl); } protected String filterBranchFromScmUrl(String scmUrl) { String scmBranch = "UNKNOWN"; if (StringUtils.contains(scmUrl, "/trunk")) { scmBranch = "trunk"; } else if (StringUtils.contains(scmUrl, "/branches") || StringUtils.contains(scmUrl, "/tags")) { scmBranch = scmUrl.replaceFirst(".*((branches|tags)[^/]*).*?", "$1"); } return scmBranch; } /** * Get the revision info from the repository. For svn, it is svn info * * @return * @throws MojoExecutionException */ public String getRevision() throws MojoExecutionException { if (format != null && !useScm) { return revision; } useScm = false; try { return this.getScmRevision(); } catch (ScmException e) { if (!StringUtils.isEmpty(revisionOnScmFailure)) { getLog().warn("Cannot get the revision information from the scm repository, proceeding with " + "revision of " + revisionOnScmFailure + " : \n" + e.getLocalizedMessage()); setDoCheck(false); setDoUpdate(false); return revisionOnScmFailure; } throw new MojoExecutionException( "Cannot get the revision information from the scm repository : \n" + e.getLocalizedMessage(), e); } } /** * @return * @todo normally this would be handled in AbstractScmProvider */ private ScmLogger getLogger() { if (logger == null) { logger = new ScmLogDispatcher(); } return logger; } // //////////////////////////////////////////////////////////////////////////////////////////// // setters to help with test public void setScmManager(ScmManager scmManager) { this.scmManager = scmManager; } public void setUrlScm(String urlScm) { this.scmConnectionUrl = urlScm; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setDoCheck(boolean doCheck) { String doCheckSystemProperty = System.getProperty("maven.buildNumber.doCheck"); if (doCheckSystemProperty != null) { // well, this gets the final say this.doCheck = Boolean.valueOf(doCheckSystemProperty).booleanValue(); } else { this.doCheck = doCheck; } } public void setDoUpdate(boolean doUpdate) { String doUpdateSystemProperty = System.getProperty("maven.buildNumber.doUpdate"); if (doUpdateSystemProperty != null) { // well, this gets the final say this.doUpdate = Boolean.valueOf(doUpdateSystemProperty).booleanValue(); } else { this.doUpdate = doUpdate; } } void setFormat(String format) { this.format = format; } void setLocale(String locale) { this.locale = locale; } void setItems(List<?> items) { this.items = items; } public void setBuildNumberPropertiesFileLocation(File buildNumberPropertiesFileLocation) { this.buildNumberPropertiesFileLocation = buildNumberPropertiesFileLocation; } public void setScmDirectory(File scmDirectory) { this.scmDirectory = scmDirectory; } public void setRevisionOnScmFailure(String revisionOnScmFailure) { this.revisionOnScmFailure = revisionOnScmFailure; } public void setShortRevisionLength(int shortRevision) { this.shortRevisionLength = shortRevision; } }