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

org.apache.maven.plugin.announcement.AnnouncementMojo Maven / Gradle / Ivy

Go to download

Creates a release history for inclusion into the site and assists in generating an announcement mail.

There is a newer version: 2.12.1
Show newest version
package org.apache.maven.plugin.announcement;

/*
 * 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.plugin.MojoExecutionException;
import org.apache.maven.plugin.changes.ChangesXML;
import org.apache.maven.plugin.changes.IssueAdapter;
import org.apache.maven.plugin.changes.ProjectUtils;
import org.apache.maven.plugin.changes.ReleaseUtils;
import org.apache.maven.plugin.github.GitHubDownloader;
import org.apache.maven.plugin.github.GitHubIssueManagementSystem;
import org.apache.maven.plugin.issues.Issue;
import org.apache.maven.plugin.issues.IssueManagementSystem;
import org.apache.maven.plugin.issues.IssueUtils;
import org.apache.maven.plugin.jira.AbstractJiraDownloader;
import org.apache.maven.plugin.jira.AdaptiveJiraDownloader;
import org.apache.maven.plugin.jira.JIRAIssueManagmentSystem;
import org.apache.maven.plugin.trac.TracDownloader;
import org.apache.maven.plugin.trac.TracIssueManagmentSystem;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.changes.model.Release;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.tools.ToolManager;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.velocity.VelocityComponent;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Goal which generate an announcement from the announcement template.
 *
 * @author [email protected]
 * @version $Id: AnnouncementMojo.java 1579078 2014-03-18 22:44:41Z dennisl $
 * @since 2.0-beta-2
 */
@Mojo( name = "announcement-generate", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
public class AnnouncementMojo
    extends AbstractAnnouncementMojo
{
    private static final String CHANGES_XML = "changes.xml";

    private static final String JIRA = "JIRA";

    private static final String TRAC = "Trac";

    private static final String GIT_HUB = "GitHub";

    /**
     * The name of the file which will contain the generated announcement. If
     * no value is specified the plugin will use the name of the template.
     *
     * @since 2.4
     */
    @Parameter( property = "changes.announcementFile" )
    private String announcementFile;

    /**
     * Map of custom parameters for the announcement.
     * This Map will be passed to the template.
     *
     * @since 2.1
     */
    @Parameter
    private Map announceParameters;

    /**
     */
    @Parameter( property = "project.artifactId", readonly = true )
    private String artifactId;

    /**
     * Name of the team that develops the artifact.
     * This parameter will be passed to the template.
     */
    @Parameter( property = "changes.developmentTeam", defaultValue = "${project.name} team", required = true )
    private String developmentTeam;

    /**
     * The name of the artifact to be used in the announcement.
     */
    @Parameter( property = "changes.finalName", defaultValue = "${project.build.finalName}", required = true )
    private String finalName;

    /**
     */
    @Parameter( property = "project.groupId", readonly = true )
    private String groupId;

    /**
     * Short description or introduction of the released artifact.
     * This parameter will be passed to the template.
     */
    @Parameter( defaultValue = "${project.description}" )
    private String introduction;

    /**
     * A list of issue management systems to fetch releases from. This parameter
     * replaces the parameters generateJiraAnnouncement and
     * jiraMerge.
     * 

* Valid values are: changes.xml and JIRA. *

* Note: Only one issue management system that is * configured in <project>/<issueManagement> can be used. This * currently means that you can combine a changes.xml file with one other * issue management system. * * @since 2.4 */ @Parameter private List issueManagementSystems; /** * Maps issues types to action types for grouping issues in announcements. * If issue types are not defined for a action type then the default issue type * will be applied. *

* Valid action types: add, fix and update. *

* * @since 2.6 */ @Parameter private Map issueTypes; /** * Directory where the announcement file will be generated. * * @since 2.10 */ @Parameter( defaultValue = "${project.build.directory}/announcement", required = true ) private File announcementDirectory; /** * Directory where the announcement file will be generated. * * @deprecated Starting with version 2.10 this parameter is no longer used. You must use {@link #announcementDirectory} instead. */ @Parameter private File outputDirectory; /** * Packaging structure for the artifact. */ @Parameter( property = "project.packaging", readonly = true ) private String packaging; /** * The Maven Project. */ @Component private MavenProject project; /** * The Velocity template used to format the announcement. */ @Parameter( property = "changes.template", defaultValue = "announcement.vm", required = true ) private String template; /** * Directory that contains the template. *

* Note: This directory must be a subdirectory of * /src/main/resources/ or current project base directory. *

*/ @Parameter( property = "changes.templateDirectory", defaultValue = "org/apache/maven/plugin/announcement", required = true ) private String templateDirectory; /** * The template encoding. * * @since 2.1 */ @Parameter( property = "changes.templateEncoding", defaultValue = "${project.build.sourceEncoding}" ) private String templateEncoding; /** * Use the JIRA query language instead of the JIRA query based on HTTP parameters. * From JIRA 5.1 and up only JQL is supported. JIRA 4.4 supports both JQL and URL parameter based queries. * From 5.1.1 this is obsolete, since REST queries only use JQL. * * @since 2.10 */ @Parameter( property = "changes.useJql", defaultValue = "false" ) private boolean useJql; /** * Distribution URL of the artifact. * This parameter will be passed to the template. */ @Parameter( property = "project.url" ) private String url; /** * URL where the artifact can be downloaded. If not specified, * no URL is used. * This parameter will be passed to the template. */ @Parameter private String urlDownload; /** * Velocity Component. */ @Component( role = VelocityComponent.class, hint = "maven-changes-plugin" ) private VelocityComponent velocity; /** * Version of the artifact. */ @Parameter( property = "changes.version", defaultValue = "${project.version}", required = true ) private String version; /** * The path of the changes.xml file. * * @parameter expression="${basedir}/src/changes/changes.xml" * @required */ @Parameter( defaultValue = "${basedir}/src/changes/changes.xml" ) private File xmlPath; //=======================================// // JIRA-Announcement Needed Parameters // //=======================================// /** * Defines the filter parameters to restrict which issues are retrieved * from JIRA. The filter parameter uses the same format of url * parameters that is used in a JIRA search. * * @since 2.4 */ @Parameter( defaultValue = "" ) private String filter; /** * Flag to determine if the plugin will generate a JIRA announcement. * * @deprecated Since version 2.4 this parameter has been deprecated. Please use the issueManagementSystems parameter instead. */ @Parameter( property = "generateJiraAnnouncement", defaultValue = "false", required = true ) private boolean generateJiraAnnouncement; /** * If releases from JIRA should be merged with the releases from a * changes.xml file. * * @since 2.1 * @deprecated Since version 2.4 this parameter has been deprecated. Please use the issueManagementSystems parameter instead. */ @Parameter( property = "changes.jiraMerge", defaultValue = "false" ) private boolean jiraMerge; /** * Defines the JIRA password for authentication into a private JIRA installation. * * @since 2.1 */ @Parameter( property = "changes.jiraPassword", defaultValue = "" ) private String jiraPassword; /** * Defines the JIRA username for authentication into a private JIRA installation. * * @since 2.1 */ @Parameter( property = "changes.jiraUser", defaultValue = "" ) private String jiraUser; /** * Path to the JIRA XML file, which will be parsed. */ @Parameter( defaultValue = "${project.build.directory}/jira-announcement.xml", required = true, readonly = true ) private File jiraXML; /** * The maximum number of issues to fetch from JIRA. *

* Note: In versions 2.0-beta-3 and earlier this parameter was * called "nbEntries". *

*/ @Parameter( property = "changes.maxEntries", defaultValue = "25", required = true ) private int maxEntries; /** * Include issues from JIRA with these resolution ids. Multiple resolution * ids can be specified as a comma separated list of ids. *

* Note: In versions 2.0-beta-3 and earlier this parameter was * called "resolutionId". *

*/ @Parameter( property = "changes.resolutionIds", defaultValue = "Fixed" ) private String resolutionIds; /** * Settings XML configuration. */ @Component private Settings settings; /** * Include issues from JIRA with these status ids. Multiple status ids can * be specified as a comma separated list of ids. *

* Note: In versions 2.0-beta-3 and earlier this parameter was * called "statusId". *

*/ @Parameter( property = "changes.statusIds", defaultValue = "Closed" ) private String statusIds; /** * Defines the http user for basic authentication into the JIRA webserver. * * @since 2.4 */ @Parameter( property = "changes.webUser", defaultValue = "" ) private String webUser; /** * Defines the http password for basic authentication into the JIRA webserver. * * @since 2.4 */ @Parameter( property = "changes.webPassword", defaultValue = "" ) private String webPassword; /** * The prefix used when naming versions in JIRA. *

* If you have a project in JIRA with several components that have different * release cycles, it is an often used pattern to prefix the version with * the name of the component, e.g. maven-filtering-1.0 etc. To fetch issues * from JIRA for a release of the "maven-filtering" component you would need * to set this parameter to "maven-filtering-". *

* * @since 2.5 */ @Parameter( property = "changes.versionPrefix", defaultValue = "" ) private String versionPrefix; //=======================================// // Trac Parameters // //=======================================// /** * Defines the Trac password for authentication into a private Trac * installation. * * @since 2.4 */ @Parameter( property = "changes.tracPassword", defaultValue = "" ) private String tracPassword; /** * Defines the Trac query for searching for tickets. * * @since 2.4 */ @Parameter( defaultValue = "order=id" ) private String tracQuery; /** * Defines the Trac username for authentication into a private Trac * installation. * * @since 2.4 */ @Parameter( property = "changes.tracUser", defaultValue = "" ) private String tracUser; //=======================================// // Github Parameters // //=======================================// /** * The scheme of your github api domain. Only use if using github enterprise. * * @since 2.9 */ @Parameter( defaultValue = "http", property = "changes.githubAPIScheme" ) private String githubAPIScheme; /** * The port of your github api domain. Only use if using github enterprise. * * @since 2.9 */ @Parameter( defaultValue = "80", property = "changes.githubAPIPort" ) private int githubAPIPort; private ReleaseUtils releaseUtils = new ReleaseUtils( getLog() ); private ChangesXML xml; //=======================================// // announcement-generate execution // //=======================================// /** * Generate the template * * @throws MojoExecutionException */ public void execute() throws MojoExecutionException { // Fail build fast if it is using deprecated parameters if ( outputDirectory != null ) { throw new MojoExecutionException( "You are using the old parameter 'outputDirectory'. You must use 'announcementDirectory' instead." ); } // Run only at the execution root if ( runOnlyAtExecutionRoot && !isThisTheExecutionRoot() ) { getLog().info( "Skipping the announcement generation in this project because it's not the Execution Root" ); } else { if ( issueManagementSystems == null ) { issueManagementSystems = new ArrayList(); } // Handle deprecated parameters, in a backward compatible way if ( issueManagementSystems.isEmpty() ) { if ( this.jiraMerge ) { issueManagementSystems.add( CHANGES_XML ); issueManagementSystems.add( JIRA ); } else if ( generateJiraAnnouncement ) { issueManagementSystems.add( JIRA ); } else { issueManagementSystems.add( CHANGES_XML ); } } // Fetch releases from the configured issue management systems List releases = null; if ( issueManagementSystems.contains( CHANGES_XML ) ) { if ( getXmlPath().exists() ) { ChangesXML changesXML = new ChangesXML( getXmlPath(), getLog() ); List changesReleases = releaseUtils.convertReleaseList( changesXML.getReleaseList() ); releases = releaseUtils.mergeReleases( null, changesReleases ); getLog().info( "Including issues from file " + getXmlPath() + " in announcement..." ); } else { getLog().warn( "changes.xml file " + getXmlPath().getAbsolutePath() + " does not exist." ); } } if ( issueManagementSystems.contains( JIRA ) ) { if ( ProjectUtils.validateIfIssueManagementComplete( project, JIRA, "JIRA announcement", getLog() ) ) { List jiraReleases = getJiraReleases(); releases = releaseUtils.mergeReleases( releases, jiraReleases ); getLog().info( "Including issues from JIRA in announcement..." ); } else { throw new MojoExecutionException( "Something is wrong with the Issue Management section. See previous error messages." ); } } if ( issueManagementSystems.contains( TRAC ) ) { if ( ProjectUtils.validateIfIssueManagementComplete( project, TRAC, "Trac announcement", getLog() ) ) { List tracReleases = getTracReleases(); releases = releaseUtils.mergeReleases( releases, tracReleases ); getLog().info( "Including issues from Trac in announcement..." ); } else { throw new MojoExecutionException( "Something is wrong with the Issue Management section. See previous error messages." ); } } if ( issueManagementSystems.contains( GIT_HUB ) ) { if ( ProjectUtils.validateIfIssueManagementComplete( project, GIT_HUB, "GitHub announcement", getLog() ) ) { List gitHubReleases = getGitHubReleases(); releases = releaseUtils.mergeReleases( releases, gitHubReleases ); getLog().info( "Including issues from GitHub in announcement..." ); } else { throw new MojoExecutionException( "Something is wrong with the Issue Management section. See previous error messages." ); } } // @todo Add more issue management systems here. // Follow these steps: // 1. Add a constant for the name of the issue management system // 2. Add the @parameters needed to configure the issue management system // 3. Add a protected List getReleases() method that retrieves a list of releases // 4. Merge those releases into the "releases" variable // For help with these steps, you can have a look at how this has been done for JIRA or Trac // Generate the report if ( releases == null || releases.isEmpty() ) { throw new MojoExecutionException( "No releases found in any of the configured issue management systems." ); } else { doGenerate( releases ); } } } /** * Add the parameters to velocity context * * @param releases A List of Releases * @throws MojoExecutionException */ public void doGenerate( List releases ) throws MojoExecutionException { String version = ( versionPrefix == null ? "" : versionPrefix ) + getVersion(); getLog().debug( "Generating announcement for version [" + version + "]. Found these releases: " + ReleaseUtils.toString( releases ) ); doGenerate( releases, releaseUtils.getLatestRelease( releases, version ) ); } protected void doGenerate( List releases, Release release ) throws MojoExecutionException { try { ToolManager toolManager = new ToolManager( true ); Context context = toolManager.createContext(); if ( getIntroduction() == null || getIntroduction().equals( "" ) ) { setIntroduction( getUrl() ); } context.put( "releases", releases ); context.put( "groupId", getGroupId() ); context.put( "artifactId", getArtifactId() ); context.put( "version", getVersion() ); context.put( "packaging", getPackaging() ); context.put( "url", getUrl() ); context.put( "release", release ); context.put( "introduction", getIntroduction() ); context.put( "developmentTeam", getDevelopmentTeam() ); context.put( "finalName", getFinalName() ); context.put( "urlDownload", getUrlDownload() ); context.put( "project", project ); if ( announceParameters == null ) { // empty Map to prevent NPE in velocity execution context.put( "announceParameters", Collections.EMPTY_MAP ); } else { context.put( "announceParameters", announceParameters ); } processTemplate( context, announcementDirectory, template, announcementFile ); } catch ( ResourceNotFoundException rnfe ) { throw new MojoExecutionException( "Resource not found.", rnfe ); } catch ( VelocityException ve ) { throw new MojoExecutionException( ve.toString(), ve ); } } /** * Create the velocity template * * @param context velocity context that has the parameter values * @param outputDirectory directory where the file will be generated * @param template velocity template which will the context be merged * @param announcementFile The file name of the generated announcement * @throws ResourceNotFoundException, VelocityException, IOException */ public void processTemplate( Context context, File outputDirectory, String template, String announcementFile ) throws VelocityException, MojoExecutionException { File f; // Use the name of the template as a default value if ( StringUtils.isEmpty( announcementFile ) ) { announcementFile = template; } try { f = new File( outputDirectory, announcementFile ); if ( !f.getParentFile().exists() ) { f.getParentFile().mkdirs(); } VelocityEngine engine = velocity.getEngine(); engine.setApplicationAttribute( "baseDirectory", basedir ); if ( StringUtils.isEmpty( templateEncoding ) ) { templateEncoding = ReaderFactory.FILE_ENCODING; getLog().warn( "File encoding has not been set, using platform encoding " + templateEncoding + ", i.e. build is platform dependent!" ); } Writer writer = new OutputStreamWriter( new FileOutputStream( f ), templateEncoding ); Template velocityTemplate = engine.getTemplate( templateDirectory + "/" + template, templateEncoding ); velocityTemplate.merge( context, writer ); writer.flush(); writer.close(); getLog().info( "Created template " + f ); } catch ( ResourceNotFoundException rnfe ) { throw new ResourceNotFoundException( "Template not found. ( " + templateDirectory + "/" + template + " )" ); } catch ( VelocityException ve ) { throw new VelocityException( ve.toString() ); } catch ( Exception e ) { if ( e.getCause() != null ) { getLog().warn( e.getCause() ); } throw new MojoExecutionException( e.toString(), e.getCause() ); } } protected List getJiraReleases() throws MojoExecutionException { AbstractJiraDownloader jiraDownloader = new AdaptiveJiraDownloader(); File jiraXMLFile = jiraXML; jiraDownloader.setLog( getLog() ); jiraDownloader.setOutput( jiraXMLFile ); jiraDownloader.setStatusIds( statusIds ); jiraDownloader.setResolutionIds( resolutionIds ); jiraDownloader.setMavenProject( project ); jiraDownloader.setSettings( settings ); jiraDownloader.setNbEntries( maxEntries ); jiraDownloader.setFilter( filter ); jiraDownloader.setJiraUser( jiraUser ); jiraDownloader.setJiraPassword( jiraPassword ); jiraDownloader.setUseJql( useJql ); jiraDownloader.setWebUser( webUser ); jiraDownloader.setWebPassword( webPassword ); try { jiraDownloader.doExecute(); List issueList = jiraDownloader.getIssueList(); if ( StringUtils.isNotEmpty( versionPrefix ) ) { int originalNumberOfIssues = issueList.size(); issueList = IssueUtils.filterIssuesWithVersionPrefix( issueList, versionPrefix ); getLog().debug( "Filtered out " + issueList.size() + " issues of " + originalNumberOfIssues + " that matched the versionPrefix '" + versionPrefix + "'." ); } return getReleases( issueList, new JIRAIssueManagmentSystem() ); } catch ( Exception e ) { throw new MojoExecutionException( "Failed to extract issues from JIRA.", e ); } } private List getReleases( List issues, IssueManagementSystem ims ) throws MojoExecutionException { if ( issueTypes != null ) { ims.applyConfiguration( issueTypes ); } if ( issues.isEmpty() ) { return Collections.emptyList(); } else { IssueAdapter adapter = new IssueAdapter( ims ); return adapter.getReleases( issues ); } } protected List getTracReleases() throws MojoExecutionException { TracDownloader issueDownloader = new TracDownloader(); issueDownloader.setProject( project ); issueDownloader.setQuery( tracQuery ); issueDownloader.setTracPassword( tracPassword ); issueDownloader.setTracUser( tracUser ); try { return getReleases( issueDownloader.getIssueList(), new TracIssueManagmentSystem() ); } catch ( Exception e ) { throw new MojoExecutionException( "Failed to extract issues from Trac.", e ); } } protected List getGitHubReleases() throws MojoExecutionException { try { GitHubDownloader issueDownloader = new GitHubDownloader( project, githubAPIScheme, githubAPIPort, false, true ); return getReleases( issueDownloader.getIssueList(), new GitHubIssueManagementSystem() ); } catch ( Exception e ) { throw new MojoExecutionException( "Failed to extract issues from GitHub.", e ); } } /* * accessors */ public String getArtifactId() { return artifactId; } public void setArtifactId( String artifactId ) { this.artifactId = artifactId; } public String getDevelopmentTeam() { return developmentTeam; } public void setDevelopmentTeam( String developmentTeam ) { this.developmentTeam = developmentTeam; } public String getFinalName() { return finalName; } public void setFinalName( String finalName ) { this.finalName = finalName; } public String getGroupId() { return groupId; } public void setGroupId( String groupId ) { this.groupId = groupId; } public String getIntroduction() { return introduction; } public void setIntroduction( String introduction ) { this.introduction = introduction; } public void setIssueTypes( Map issueTypes ) { this.issueTypes = issueTypes; } public Map getIssueTypes() { return issueTypes; } public File getAnnouncementDirectory() { return announcementDirectory; } public void setAnnouncementDirectory( File announcementDirectory ) { this.announcementDirectory = announcementDirectory; } public String getPackaging() { return packaging; } public void setPackaging( String packaging ) { this.packaging = packaging; } public String getUrl() { return url; } public void setUrl( String url ) { this.url = url; } public String getUrlDownload() { return urlDownload; } public void setUrlDownload( String urlDownload ) { this.urlDownload = urlDownload; } public VelocityComponent getVelocity() { return velocity; } public void setVelocity( VelocityComponent velocity ) { this.velocity = velocity; } public String getVersion() { return version; } public void setVersion( String version ) { this.version = version; } public ChangesXML getXml() { return xml; } public void setXml( ChangesXML xml ) { this.xml = xml; } public File getXmlPath() { return xmlPath; } public void setXmlPath( File xmlPath ) { this.xmlPath = xmlPath; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy