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.2.2
Show newest version
/*
 * *##% 
 * JRedmine maven plugin
 * Copyright (C) 2009 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
 * .
 * ##%*
 */
package org.nuiton.jredmine.plugin;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.changes.model.*;
import org.apache.maven.plugins.changes.model.Properties;
import org.apache.maven.plugins.changes.model.io.xpp3.ChangesXpp3Writer;
import org.codehaus.plexus.util.xml.XmlStreamWriter;
import org.nuiton.jredmine.RedmineServiceException;
import org.nuiton.jredmine.model.*;
import org.nuiton.plugin.PluginHelper;

import java.io.File;
import java.util.*;
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
 * @goal generate-changes
 * @since 1.0.0
 */
public class GenerateChangesMojo extends AbstractRedmineMojo implements IssueCollectorConfiguration {

    /**
     * The actions understood by the changes.xml format.
     */
    enum Actions {

        add,
        fix,
        update,
        remove
    }

    /**
     * The path of the changes.xml file that will be converted into an HTML report.
     *
     * @parameter expression="${redmine.xmlPath}" default-value="${basedir}/src/changes/changes.xml"
     * @required
     * @since 1.0.0
     */
    protected File xmlPath;
    /**
     * The description of the release.
     * 

* Note : if not sets - will use the redmine version description (if exists). * * @parameter expression="${releaseDescription}" * @since 1.0.0 */ protected String releaseDescription; /** * The changes file title. * * @parameter expression="${changesTitle}" default-value="${project.description}" * @required * @since 1.0.0 */ 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. * * @parameter expression="${redmine.onlyCurrentVersion}" default-value="false" * @since 1.0.0 */ 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
     * 
* * @parameter expression="${redmine.actionMapping}" * @required * @since 1.0.0 */ 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. *

* * @parameter expression="${redmine.statusIds}" * @since 1.0.0 */ 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. *

* * @parameter expression="${redmine.categoryIds}" * @since 1.0.0 */ protected String categoryIds; /** * A flag to skip the goal. * * @parameter expression="${redmine.skipGenerateChanges}" default-value="false" * @since 1.0.0 */ 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 * * @parameter expression="${redmine.generateOnce}" default-value="true" * @since 1.0.0 */ protected boolean generateOnce; /** * 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; public GenerateChangesMojo() { super(true, false, true); } @Override protected boolean isGoalSkip() { return skipGenerateChanges; } @Override protected boolean isRunOnce() { return generateOnce; } @Override protected boolean checkRunOnceDone() { if (!isRunOnce()) { // will generate each time return false; } if (cacheChangesFile == null || !cacheChangesFile.exists()) { // the changes.xml does not exists, must generate it return false; } // nothing to generate (so no init) // will just copy the already generated changes.xml file return true; } @Override protected boolean init() throws Exception { if (xmlPath == null || xmlPath.getAbsolutePath().trim().isEmpty()) { throw new MojoExecutionException("required a xmlPath parameter"); } if (versionId == null || versionId.trim().isEmpty()) { throw new MojoExecutionException("required a versionId parameter"); } versionId = PluginHelper.removeSnapshotSuffix(versionId); runOnceDone = false; if (isRunOnce()) { runOnceDone = checkRunOnceDone(); if (runOnceDone) { return true; } } boolean result = super.init(); if (!result) { return false; } try { // 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); } // get versions try { versions = service.getVersions(projectId); } catch (RedmineServiceException e) { throw new MojoExecutionException("could not obtain versions 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 = ModelHelper.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 (statusIds != null && !statusIds.trim().isEmpty()) { for (String s : statusIds.split(",")) { Integer id = Integer.valueOf(s.trim()); IssueStatus t = ModelHelper.byId(id, statuses); if (t == null) { throw new MojoExecutionException("could not obtain the status with id " + id); } } } filters.put("status", statusIds); if (categoryIds != null && !categoryIds.trim().isEmpty()) { // create category filters for (String s : categoryIds.split(",")) { Integer id = Integer.valueOf(s.trim()); IssueCategory t = ModelHelper.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 = ModelHelper.byVersionName(versionId, versions); 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 : versions) { 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)); } } catch (Exception e) { if (verbose) { getLog().warn("could not init the plugin for reason " + e.getMessage(), e); } else { getLog().warn("could not init the plugin for reason " + e.getMessage()); } result = false; } return result; } @Override protected void doAction() throws Exception { File xmlParent = xmlPath.getParentFile(); if (!xmlParent.exists()) { xmlParent.mkdirs(); } if (isRunOnceDone()) { if (!xmlPath.exists()) { // just copy the already generated changes.xml fil getLog().info("Use already generated " + xmlPath.getName() + " (" + cacheChangesFile + ")"); copyFile(cacheChangesFile, xmlPath); } else { getLog().info("skip goal, work already done."); } return; } if (releaseVersion.getEffectiveDate() == null) { getLog().warn("The version " + versionId + " is not effective on redmine, should update effective-date property to today"); //TODO call the reset service rest/update_version 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 obtains 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(releaseProject, 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.getParentFile().getAbsolutePath()); // cache result cacheChangesFile = xmlPath; } protected ChangesDocument buildChangesDocument(Project project, 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 = ModelHelper.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 = ModelHelper.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 = ModelHelper.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 = ModelHelper.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; } /////////////////////////////////////////////////////////////////////////// /// IssueCollectionConfiguration /////////////////////////////////////////////////////////////////////////// @Override public String getProjectId() { return projectId; } @Override public String getVersionId() { return versionId; } @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 void setVersionId(String versionId) { this.versionId = versionId; } @Override public String getCategoryIds() { return getFilters().get("category"); } @Override public String getVersionNames() { return getFilters().get("version"); } @Override public String getStatusIds() { return getFilters().get("status"); } @Override public String getTrackerIds() { return getFilters().get("tracker"); } protected Map getFilters() { return filters; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy