org.nuiton.jredmine.plugin.GenerateChangesMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of maven-jredmine-plugin Show documentation
Show all versions of maven-jredmine-plugin Show documentation
JRedmine maven plugin to interacts with Redmine's server
/*
* *##%
* 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;
}
}