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

org.nuiton.jredmine.plugin.GenerateChangesMojo Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
/*
 * #%L
 * JRedmine :: Maven plugin
 * 
 * $Id: GenerateChangesMojo.java 411 2013-08-08 12:31:31Z tchemit $
 * $HeadURL: http://svn.nuiton.org/svn/jredmine/tags/jredmine-1.6/jredmine-maven-plugin/src/main/java/org/nuiton/jredmine/plugin/GenerateChangesMojo.java $
 * %%
 * Copyright (C) 2009 - 2012 Tony Chemit, CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */
package org.nuiton.jredmine.plugin;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.changes.model.Action;
import org.apache.maven.plugins.changes.model.Author;
import org.apache.maven.plugins.changes.model.Body;
import org.apache.maven.plugins.changes.model.ChangesDocument;
import org.apache.maven.plugins.changes.model.Properties;
import org.apache.maven.plugins.changes.model.Release;
import org.apache.maven.plugins.changes.model.io.xpp3.ChangesXpp3Writer;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.XmlStreamWriter;
import org.nuiton.jredmine.model.IdAbles;
import org.nuiton.jredmine.model.Issue;
import org.nuiton.jredmine.model.IssueCategory;
import org.nuiton.jredmine.model.IssueStatus;
import org.nuiton.jredmine.model.Tracker;
import org.nuiton.jredmine.model.User;
import org.nuiton.jredmine.model.Version;
import org.nuiton.jredmine.service.RedmineServiceException;
import org.nuiton.plugin.PluginHelper;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Generates the changes.xml file from the Redmine's server to be used
 * by the maven-changes-plugin to generates the release report and send
 * the annoncement mail at a release time.
 *
 * @author tchemit 
 * @since 1.0.0
 */
@Mojo(name = "generate-changes", requiresOnline = true, requiresProject = true)
public class GenerateChangesMojo extends AbstractRedmineMojoWithProjectAndVersion implements IssueCollectorConfiguration, SkipOrRunOnlyOnceAware {

    /** The actions understood by the changes.xml format. */
    enum Actions {
        add,
        fix,
        update,
        remove
    }

    ///////////////////////////////////////////////////////////////////////////
    /// Mojo parameters
    ///////////////////////////////////////////////////////////////////////////

    /**
     * Flag to know if anonymous connexion to redmine server is required.
     * 

* For this goal, the default value is {@code true} *

* Note: If set to {@code false}, you should fill {@link #username} * and {@link #password} properties. * * @since 1.1.3 */ @Parameter(property = "redmine.anonymous", defaultValue = "true") protected boolean anonymous; /** * The path of the changes.xml file that will be converted into an HTML report. * * @since 1.0.0 */ @Parameter(property = "redmine.xmlPath", defaultValue = "${basedir}/src/changes/changes.xml", required = true) protected File xmlPath; /** * The description of the release. *

* Note : if not sets - will use the redmine version description (if exists). * * @since 1.0.0 */ @Parameter(property = "releaseDescription") protected String releaseDescription; /** * The changes file title. * * @since 1.0.0 */ @Parameter(property = "changesTitle", defaultValue = "${project.description}", required = true) protected String changesTitle; /** * If you only want to show issues for the current version in the report. * The current version being used is ${project.version} minus * any "-SNAPSHOT" suffix. * * @since 1.0.0 */ @Parameter(property = "redmine.onlyCurrentVersion", defaultValue = "false") protected boolean onlyCurrentVersion; /** * The action mapping to the redmine tracker ids. *

* Possible actions are {@code add}, {@code fix}, {@code update}, {@code remove} *

* The syntax of mapping is {@code action:id [,action:id]*} *

* Example : *

*

     *  fix:1
     *  fix:1, add:1
     * 
* * @since 1.0.0 */ @Parameter(property = "redmine.actionMapping", required = true) protected String actionMapping; /** * The comma separated list of statuses ids to include in the changes.xml *

* Note : If a value is set to empty - that means to include all status. *

* * @since 1.0.0 */ @Parameter(property = "redmine.statusIds") protected String statusIds; /** * The comma separated list of category ids to include in the changes.xml *

* Note : If a value is set to empty - that means to include all categories. *

* * @since 1.0.0 */ @Parameter(property = "redmine.categoryIds") protected String categoryIds; /** * A flag to skip the goal. * * @since 1.0.0 */ @Parameter(property = "redmine.skipGenerateChanges", defaultValue = "false") protected boolean skipGenerateChanges; /** * A flag to generate only once in a multi-module project. The changes.xml * file will be generated only once in the pom module and then copy in the * modules. *

* The default behaviour is to generate once to reduce calls to redmine * * @since 1.0.0 */ @Parameter(property = "redmine.generateOnce", defaultValue = "true") protected boolean generateOnce; /** * A flag to restrict only to run on root module. * * @since 1.6 */ @Parameter(property = "redmine.runOnlyOnRoot", defaultValue = "true") protected boolean runOnlyOnRoot; /////////////////////////////////////////////////////////////////////////// /// Mojo internal attributes /////////////////////////////////////////////////////////////////////////// /** le fichier deja genere */ private static File cacheChangesFile; /** le mapping entre l'id d'un tracker et le type d'action */ protected Map trackerToAction; protected Map filters; private Version[] versions; /** flag to mark if a runOnce goal was done */ protected boolean runOnceDone; public GenerateChangesMojo() { super(true, true); } /////////////////////////////////////////////////////////////////////////// /// RedmineClientConfiguration /////////////////////////////////////////////////////////////////////////// @Override public boolean isAnonymous() { return anonymous; } @Override public void setAnonymous(boolean anonymous) { this.anonymous = anonymous; } /////////////////////////////////////////////////////////////////////////// /// IssueCollectionConfiguration /////////////////////////////////////////////////////////////////////////// @Override public boolean isOnlyCurrentVersion() { return onlyCurrentVersion; } @Override public int getMaxEntries() { // no filter on priority for building the changes.xml file return 0; } @Override public String getPriorityIds() { // no filter on priority for building the changes.xml file return null; } @Override public String getCategoryIds() { return filters.get("category"); } @Override public String getVersionNames() { return filters.get("version"); } @Override public String getStatusIds() { return filters.get("status"); } @Override public String getTrackerIds() { return filters.get("tracker"); } /////////////////////////////////////////////////////////////////////////// /// SkipOrRunOnlyOnceAware /////////////////////////////////////////////////////////////////////////// @Override public String getSkipProperty() { return "skipGenerateChanges"; } @Override public boolean isGoalSkip() { return skipGenerateChanges; } @Override public boolean isRunOnce() { return generateOnce; } @Override public boolean isRunOnlyOnRoot() { return runOnlyOnRoot; } @Override public boolean isRunOnceDone() { return runOnceDone; } @Override public boolean checkRunOnceDone() { Date buildStartTime = session == null ? null : session.getStartTime(); Date newStartTime = cacheChangesFile != null && cacheChangesFile.exists() ? new Date(cacheChangesFile.lastModified()) : null; boolean needInvoke = needInvoke(isRunOnce(), runOnlyOnRoot, buildStartTime, newStartTime ); return !needInvoke; } /////////////////////////////////////////////////////////////////////////// /// AbstractRedmineMojo /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// /// AbstractPlugin /////////////////////////////////////////////////////////////////////////// @Override protected void init() throws Exception { if (isGoalSkip()) { return; } if (xmlPath == null || xmlPath.getAbsolutePath().trim().isEmpty()) { throw new MojoExecutionException("required a xmlPath parameter"); } if (StringUtils.isBlank(versionId)) { throw new MojoExecutionException("required a versionId parameter"); } versionId = PluginHelper.removeSnapshotSuffix(versionId); if (runOnceDone = isRunOnce() && checkRunOnceDone()) { return; } super.init(); if (!safe && !initOk) { // we are in none safe mode but init is not ok... return; } // get trackers Tracker[] releaseTrackers; try { releaseTrackers = service.getTrackers(projectId); } catch (RedmineServiceException e) { throw new MojoExecutionException("could not obtain trackers for reason " + e.getMessage(), e); } // get statuses IssueStatus[] statuses; try { statuses = service.getIssueStatuses(); } catch (RedmineServiceException e) { throw new MojoExecutionException("could not obtain statuses for reason " + e.getMessage(), e); } // get categories IssueCategory[] categories; try { categories = service.getIssueCategories(projectId); } catch (RedmineServiceException e) { throw new MojoExecutionException("could not obtain categories for reason " + e.getMessage(), e); } filters = new HashMap(); // build trackerToAction and tracker filter trackerToAction = new HashMap(); String[] entries = actionMapping.split(","); StringBuilder buffer = new StringBuilder(); for (String entry : entries) { String[] parts = entry.split(":"); if (parts.length != 2) { // error in syntax throw new MojoExecutionException("the trackerMapping entry " + Arrays.toString(parts) + " is not well formed"); } String action = parts[0].trim().toLowerCase(); try { Actions.valueOf(action); } catch (Exception e) { throw new MojoExecutionException("the action " + action + " is unknown... authorized : " + Arrays.toString(Actions.values())); } Integer id = Integer.valueOf(parts[1].trim()); Tracker t = IdAbles.byId(id, releaseTrackers); if (t == null) { throw new MojoExecutionException("could not obtain the tracker with id " + id); } trackerToAction.put(id, action); buffer.append(",").append(id); } filters.put("tracker", buffer.substring(1)); // create status filters if (StringUtils.isNotBlank(statusIds)) { for (String s : statusIds.split(",")) { Integer id = Integer.valueOf(s.trim()); IssueStatus t = IdAbles.byId(id, statuses); if (t == null) { throw new MojoExecutionException("could not obtain the status with id " + id); } } } filters.put("status", statusIds); // create category filters if (StringUtils.isNotBlank(categoryIds)) { for (String s : categoryIds.split(",")) { Integer id = Integer.valueOf(s.trim()); IssueCategory t = IdAbles.byId(id, categories); if (t == null) { throw new MojoExecutionException("could not obtain the category with id " + id); } } filters.put("category", categoryIds); } // create version filters List versionList = new ArrayList(); boolean versionExist = true; releaseVersion = getProjectVersion(versionId); if (releaseVersion == null) { versionExist = false; // la version n'existe pas encore sur redmine getLog().warn("The version " + versionId + " does not exist on redmine"); releaseVersion = new Version(); releaseVersion.setName(versionId); releaseVersion.setProjectId(releaseProject.getId()); } if (onlyCurrentVersion) { // just release version versionList.add(releaseVersion); } else { // obtain all released versions (just a effective-date) buffer = new StringBuilder(); for (Version v : getProjectVersions()) { String versionName = v.getName(); boolean keep = true; if (!versionId.equals(versionName)) { //TODO TC-20090914 make this better, since this is a // very soft test to test only the effective date if (v.getEffectiveDate() == null) { // skip this unclosed version keep = false; } } if (keep) { buffer.append(",").append(v.getName()); versionList.add(v); } } if (!versionExist) { versionList.add(releaseVersion); } } versions = versionList.toArray(new Version[versionList.size()]); if (!onlyCurrentVersion) { filters.put("version", buffer.substring(1)); } } @Override protected boolean checkSkip() { if (isRunOnceDone()) { if (!xmlPath.exists()) { getLog().info("Use already generated " + xmlPath.getName() + " (" + cacheChangesFile + ")"); try { createDirectoryIfNecessary(xmlPath.getParentFile()); copyFile(cacheChangesFile, xmlPath); } catch (IOException e) { throw new IllegalStateException("could not copy already generated file " + xmlPath + " from " + cacheChangesFile); } } } boolean b = super.checkSkip(); return b; } @Override protected void doAction() throws Exception { File xmlParent = xmlPath.getParentFile(); createDirectoryIfNecessary(xmlParent); if (releaseVersion.getEffectiveDate() == null) { getLog().debug("The version " + versionId + " is not effective on redmine, should update effective-date property to today"); releaseVersion.setEffectiveDate(new Date()); } getLog().info("release project " + releaseProject.getName()); getLog().info("release version " + releaseVersion.getName()); getLog().info("release date " + releaseVersion.getEffectiveDate()); getLog().info("release user " + releaseUser.getFirstname() + " " + releaseUser.getLastname()); // init issues collector IssuesCollector collector = new IssuesCollector(getLog(), verbose); // collects issues try { collector.collect(service, this); } catch (RedmineServiceException ex) { collector.clearFilters(); throw new MojoExecutionException("could not obtain issues for reason " + ex.getMessage(), ex); } //TODO make some logic checks : version must be // build the maven changes.xml as memory model ChangesDocument doc = buildChangesDocument(releaseVersion, releaseUser, collector); // store the generated file ChangesXpp3Writer xppWriter = new ChangesXpp3Writer(); XmlStreamWriter writer = new XmlStreamWriter(xmlPath); xppWriter.write(writer, doc); getLog().info("File saved in " + xmlPath); // cache result cacheChangesFile = xmlPath; } /////////////////////////////////////////////////////////////////////////// /// Others /////////////////////////////////////////////////////////////////////////// protected ChangesDocument buildChangesDocument(Version version, User user, IssuesCollector collector) throws RedmineServiceException { ChangesDocument doc = new ChangesDocument(); Properties properties = new Properties(); Author author = new Author(); author.setAuthorEmail(user.getMail()); author.setName(user.getFirstname() + " " + user.getLastname()); properties.setAuthor(author); properties.setTitle(changesTitle); doc.setProperties(properties); Body body = new Body(); doc.setBody(body); //TC-20091124 : was removed since maven-changes-plugin 2.2 ? // body.setModelEncoding(encoding); Issue[] issues = collector.getIssues(); // iterate on versions for (Version v : versions) { boolean treateReleaseVersion = v.getId() == version.getId(); Issue[] issuesForVersion = Issue.byVersionId(v.getId(), issues); Release release = new Release(); body.addRelease(release); release.setVersion(v.getName()); if (v.getEffectiveDate() != null) { release.setDateRelease(dateFormat.format(v.getEffectiveDate())); } if (v.getDescription() != null) { release.setDescription(v.getDescription()); } if (treateReleaseVersion) { if (releaseDescription != null) { // override the release description // this is the main version to release release.setDescription(releaseDescription); } if (release.getDateRelease() == null) { release.setDateRelease(dateFormat.format(new Date())); } } // iterate on actions ? or order it for (Entry entry : trackerToAction.entrySet()) { String type = entry.getValue(); // get issues for the tracker Issue[] issuesForTracker = Issue.byTrackerId(entry.getKey(), issuesForVersion); for (Issue issue : issuesForTracker) { // new action Action action = new Action(); action.setSystem("redmine"); action.setAction(issue.getSubject()); action.setIssue(issue.getId() + ""); action.setType(type); if (issue.getDueDate() != null) { action.setDate(dateFormat.format(issue.getDueDate())); } else { if (verbose) { getLog().warn("issue " + issue.getSubject() + " has no dueDate..."); } } User a; int id; // created by id = issue.getAuthorId(); if (id == 0) { getLog().warn("issue " + issue.getSubject() + " is not created to any user, this is not normal..."); id = issue.getAuthorId(); } a = IdAbles.byId(id, users); if (a != null) { action.setDueTo(a.getFirstname() + " " + a.getLastname()); action.setDueToEmail(a.getMail()); } // resolved by id = issue.getAssignedToId(); if (id == 0) { getLog().warn("issue " + issue.getSubject() + " is not assigned to any user, this is not normal..."); id = issue.getAuthorId(); } a = IdAbles.byId(id, users); if (a != null) { //TODO should check this is a developper name on pom.xml action.setDev(a.getLogin()); } release.addAction(action); } } } return doc; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy