View Javadoc

1   package org.apache.maven.plugin.announcement;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileOutputStream;
24  import java.io.OutputStreamWriter;
25  import java.io.Writer;
26  import java.util.Collections;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.maven.plugin.AbstractMojo;
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.changes.ChangesXML;
34  import org.apache.maven.plugin.jira.JiraXML;
35  import org.apache.maven.plugins.changes.model.Action;
36  import org.apache.maven.plugins.changes.model.Release;
37  import org.apache.maven.project.MavenProject;
38  import org.apache.maven.settings.Settings;
39  import org.apache.velocity.Template;
40  import org.apache.velocity.VelocityContext;
41  import org.apache.velocity.app.VelocityEngine;
42  import org.apache.velocity.context.Context;
43  import org.apache.velocity.exception.ResourceNotFoundException;
44  import org.apache.velocity.exception.VelocityException;
45  import org.codehaus.plexus.util.ReaderFactory;
46  import org.codehaus.plexus.util.StringUtils;
47  import org.codehaus.plexus.velocity.VelocityComponent;
48  
49  /**
50   * Goal which generate the template for an announcement.
51   *
52   * @goal announcement-generate
53   * @requiresDependencyResolution test
54   * @author aramirez@exist.com
55   * @version $Id: AnnouncementMojo.html 816592 2012-05-08 12:40:21Z hboutemy $
56   */
57  public class AnnouncementMojo
58      extends AbstractMojo
59  {
60      private static final String SNAPSHOT_SUFFIX = "-SNAPSHOT";
61  
62      /**
63       * Directory where the template file will be generated.
64       *
65       * @parameter expression="${project.build.directory}/announcement"
66       * @required
67       */
68      private File outputDirectory;
69  
70      /**
71       * @parameter expression="${project.groupId}"
72       * @readonly
73       */
74      private String groupId;
75  
76      /**
77       * @parameter expression="${project.artifactId}"
78       * @readonly
79       */
80      private String artifactId;
81  
82      /**
83       * Version of the artifact.
84       *
85       * @parameter expression="${changes.version}" default-value="${project.version}"
86       * @required
87       */
88      private String version;
89  
90      /**
91       * Distribution URL of the artifact.
92       * This parameter will be passed to the template.
93       *
94       * @parameter expression="${project.url}"
95       */
96      private String url;
97  
98      /**
99       * Packaging structure for the artifact.
100      *
101      * @parameter expression="${project.packaging}"
102      * @readonly
103      */
104     private String packaging;
105 
106     /**
107      * The name of the artifact to be used in the announcement.
108      *
109      * @parameter expression="${changes.finalName}" default-value="${project.build.finalName}"
110      * @required
111      */
112     private String finalName;
113 
114     /**
115      * URL where the artifact can be downloaded. If not specified,
116      * no URL is used.
117      * This parameter will be passed to the template.
118      *
119      * @parameter
120      */
121     private String urlDownload;
122 
123     /**
124      * The path of the changes.xml file.
125      *
126      * @parameter expression="${basedir}/src/changes/changes.xml"
127      * @required
128      */
129     private File xmlPath;
130 
131     /**
132      * Name of the team that develops the artifact.
133      * This parameter will be passed to the template.
134      *
135      * @parameter default-value="${project.name} team" expression="${changes.developmentTeam}"
136      * @required
137      */
138     private String developmentTeam;
139 
140     /**
141      * Short description or introduction of the released artifact.
142      * This parameter will be passed to the template.
143      *
144      * @parameter default-value="${project.description}"
145      */
146     private String introduction;
147 
148     /**
149      * Velocity Component.
150      *
151      * @component role="org.codehaus.plexus.velocity.VelocityComponent" roleHint="maven-changes-plugin"
152      * @readonly
153      */
154     private VelocityComponent velocity;
155 
156     /**
157      * The Velocity template used to format the announcement.
158      *
159      * @parameter default-value="announcement.vm" expression="${changes.template}"
160      * @required
161      */
162     private String template;
163 
164     /**
165      * Directory that contains the template.
166      * <p>
167      * <b>Note:</b> This directory must be a subdirectory of
168      * <code>/src/main/resources/ or current project base directory</code>.
169      * </p>
170      *
171      * @parameter default-value="org/apache/maven/plugin/announcement" expression="${changes.templateDirectory}"
172      * @required
173      */
174     private String templateDirectory;
175 
176     /**
177      * The current project base directory.
178      *
179      * @parameter expression="${basedir}"
180      * @required
181      * @since 2.1
182      */
183     private String basedir;
184 
185     private ChangesXML xml;
186 
187     //=======================================//
188     //  JIRA-Announcement Needed Parameters  //
189     //=======================================//
190 
191     /**
192      * The Maven Project.
193      *
194      * @parameter expression="${project}"
195      * @required
196      * @readonly
197      */
198     private MavenProject project;
199 
200     /**
201      * Settings XML configuration.
202      *
203      * @parameter expression="${settings}"
204      * @required
205      * @readonly
206      */
207     private Settings settings;
208 
209     /**
210      * Flag to determine if the plugin will generate a JIRA announcement.
211      *
212      * @parameter expression="${generateJiraAnnouncement}" default-value="false"
213      * @required
214      */
215     private boolean generateJiraAnnouncement;
216 
217     /**
218      * Include issues from JIRA with these status ids. Multiple status ids can
219      * be specified as a comma separated list of ids.
220      * <p>
221      * <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter was
222      * called "statusId".
223      * </p>
224      *
225      * @parameter default-value="Closed" expression="${changes.statusIds}"
226      */
227     private String statusIds;
228 
229     /**
230      * Include issues from JIRA with these resolution ids. Multiple resolution
231      * ids can be specified as a comma separated list of ids.
232      * <p>
233      * <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter was
234      * called "resolutionId".
235      * </p>
236      *
237      * @parameter default-value="Fixed" expression="${changes.resolutionIds}"
238      */
239     private String resolutionIds;
240 
241     /**
242      * The path of the XML file of JIRA-announcements to be parsed.
243      *
244      * @parameter expression="${project.build.directory}/jira-announcement.xml"
245      * @required
246      * @readonly
247      */
248     private File jiraXML;
249 
250     /**
251      * The maximum number of issues to fetch from JIRA.
252      * <p>
253      * <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter was
254      * called "nbEntries".
255      * </p>
256      *
257      * @parameter default-value="25"  expression="${changes.maxEntries}"
258      * @required
259      */
260     private int maxEntries;
261 
262     /**
263      * Defines the JIRA username for authentication into a private JIRA installation.
264      *
265      * @parameter default-value="" expression="${changes.jiraUser}"
266      * @since 2.1
267      */
268     private String jiraUser;
269 
270     /**
271      * Defines the JIRA password for authentication into a private JIRA installation.
272      *
273      * @parameter default-value="" expression="${changes.jiraPassword}"
274      * @since 2.1
275      */
276     private String jiraPassword;
277 
278     /**
279      * The template encoding.
280      *
281      * @parameter expression="${changes.templateEncoding}" default-value="${project.build.sourceEncoding}"
282      * @since 2.1
283      */
284     private String templateEncoding;
285 
286     /**
287      * If releases from JIRA should be merged with the releases from a
288      * changes.xml file.
289      *
290      * @parameter expression="${changes.jiraMerge}" default-value="false"
291      * @since 2.1
292      */
293     private boolean jiraMerge;
294 
295     /**
296      * Map of custom parameters for the announcement.
297      * This Map will be passed to the template.
298      *
299      * @parameter
300      * @since 2.1
301      */
302     private Map announceParameters;
303 
304     //=======================================//
305     //    announcement-generate execution    //
306     //=======================================//
307 
308     /**
309      * Generate the template
310      *
311      * @throws MojoExecutionException
312      */
313     public void execute()
314         throws MojoExecutionException
315     {
316         if ( this.jiraMerge )
317         {
318             ChangesXML changesXML =  new ChangesXML( getXmlPath(), getLog() );
319             List changesReleases = changesXML.getReleaseList();
320             List jiraReleases = getJiraReleases();
321             List mergedReleases = mergeReleases( changesReleases, jiraReleases );
322             doGenerate( mergedReleases );
323         }
324         else
325         {
326             if ( !generateJiraAnnouncement )
327             {
328                 if ( getXmlPath().exists() )
329                 {
330                     setXml( new ChangesXML( getXmlPath(), getLog() ) );
331 
332                     getLog().info( "Creating announcement file from " + getXmlPath() + "..." );
333 
334                     doGenerate( getXml().getReleaseList() );
335                 }
336                 else
337                 {
338                     getLog().warn( "changes.xml file " + getXmlPath().getAbsolutePath() + " does not exist." );
339                 }
340             }
341             else
342             {
343                 doJiraGenerate();
344             }
345         }
346     }
347 
348     /**
349      * Add the parameters to velocity context
350      *
351      * @param releases A <code>List</code> of <code>Release</code>s
352      * @throws MojoExecutionException
353      */
354     public void doGenerate( List releases )
355         throws MojoExecutionException
356     {
357         doGenerate( releases, getLatestRelease( releases )  );
358     }
359 
360     protected void doGenerate( List releases, Release release )
361         throws MojoExecutionException
362     {
363         try
364         {
365             Context context = new VelocityContext();
366 
367             if ( getIntroduction() == null || getIntroduction().equals( "" ) )
368             {
369                 setIntroduction( getUrl() );
370             }
371 
372             context.put( "releases", releases );
373 
374             context.put( "groupId", getGroupId() );
375 
376             context.put( "artifactId", getArtifactId() );
377 
378             context.put( "version", getVersion() );
379 
380             context.put( "packaging", getPackaging() );
381 
382             context.put( "url", getUrl() );
383 
384             context.put( "release", release );
385 
386             context.put( "introduction", getIntroduction() );
387 
388             context.put( "developmentTeam", getDevelopmentTeam() );
389 
390             context.put( "finalName", getFinalName() );
391 
392             context.put( "urlDownload", getUrlDownload() );
393 
394             context.put( "project", project );
395 
396             if ( announceParameters == null )
397             {
398                 // empty Map to prevent NPE in velocity execution
399                 context.put( "announceParameters", Collections.EMPTY_MAP );
400             }
401             else
402             {
403                 context.put( "announceParameters", announceParameters );
404             }
405 
406 
407             processTemplate( context, getOutputDirectory(), template );
408         }
409         catch ( ResourceNotFoundException rnfe )
410         {
411             throw new MojoExecutionException( "Resource not found.", rnfe );
412         }
413         catch ( VelocityException ve )
414         {
415             throw new MojoExecutionException( ve.toString(), ve );
416         }
417     }
418 
419     /**
420      * Get the latest release by matching the supplied releases
421      * with the version from the pom.
422      *
423      * @param releases list of releases
424      * @return A <code>Release</code> that matches the next release of the current project
425      * @throws MojoExecutionException
426      */
427     public Release getLatestRelease( List releases )
428         throws MojoExecutionException
429     {
430         boolean isFound = false;
431 
432         Release release = null;
433 
434         // Remove "-SNAPSHOT" from the end, if it's there
435         String pomVersion = getVersion();
436         if ( pomVersion != null && pomVersion.endsWith( SNAPSHOT_SUFFIX ) )
437         {
438             pomVersion = pomVersion.substring( 0, pomVersion.length() - SNAPSHOT_SUFFIX.length() );
439         }
440         getLog().debug( "Found " + releases.size() + " releases." );
441 
442         for ( int i = 0; i < releases.size(); i++ )
443         {
444             release = (Release) releases.get( i );
445             if ( getLog().isDebugEnabled() )
446             {
447                 getLog().debug( "The release: " + release.getVersion()
448                     + " has " + release.getActions().size() + " actions." );
449             }
450 
451             if ( release.getVersion() != null && release.getVersion().equals( pomVersion ) )
452             {
453                 isFound = true;
454                 if ( getLog().isDebugEnabled() )
455                 {
456                     getLog().debug( "Found the correct release: " + release.getVersion() );
457                     logRelease( release );
458                 }
459                 return release;
460             }
461         }
462 
463         release = getRelease( releases, pomVersion );
464         isFound = ( release != null );
465 
466         if ( !isFound )
467         {
468             throw new MojoExecutionException( "Couldn't find the release '" + pomVersion
469                 + "' among the supplied releases." );
470         }
471         else
472         {
473 
474         }
475         return release;
476     }
477 
478 
479     protected Release getRelease( List releases, String version )
480     {
481         Release release = null;
482         for ( int i = 0; i < releases.size(); i++ )
483         {
484             release = (Release) releases.get( i );
485             if ( getLog().isDebugEnabled() )
486             {
487                 getLog().debug( "The release: " + release.getVersion()
488                     + " has " + release.getActions().size() + " actions." );
489             }
490 
491             if ( release.getVersion() != null && release.getVersion().equals( version ) )
492             {
493                 if ( getLog().isDebugEnabled() )
494                 {
495                     getLog().debug( "Found the correct release: " + release.getVersion() );
496                     logRelease( release );
497                 }
498                 return release;
499             }
500         }
501         return null;
502     }
503 
504     private void logRelease( Release release )
505     {
506         Action action;
507         for ( Iterator iterator = release.getActions().iterator(); iterator.hasNext(); )
508         {
509             action = (Action) iterator.next();
510             getLog().debug( "o " + action.getType() );
511             getLog().debug( "issue : " + action.getIssue() );
512             getLog().debug( "action : " + action.getAction() );
513             getLog().debug( "dueTo : " + action.getDueTo() );
514         }
515     }
516 
517     /**
518      * Create the velocity template
519      *
520      * @param context velocity context that has the parameter values
521      * @param outputDirectory directory where the file will be generated
522      * @param template velocity template which will the context be merged
523      * @throws ResourceNotFoundException, VelocityException, IOException
524      */
525     public void processTemplate( Context context, File outputDirectory, String template )
526         throws ResourceNotFoundException, VelocityException, MojoExecutionException
527     {
528         File f;
529 
530         try
531         {
532             f = new File( outputDirectory, template );
533 
534             if ( !f.getParentFile().exists() )
535             {
536                 f.getParentFile().mkdirs();
537             }
538 
539             VelocityEngine engine = velocity.getEngine();
540 
541             engine.setApplicationAttribute( "baseDirectory", basedir );
542 
543             if ( StringUtils.isEmpty( templateEncoding ) )
544             {
545                 templateEncoding =  ReaderFactory.FILE_ENCODING;
546                 getLog().warn(
547                                "File encoding has not been set, using platform encoding " + templateEncoding
548                                    + ", i.e. build is platform dependent!" );
549             }
550 
551             Writer writer = new OutputStreamWriter( new FileOutputStream( f ), templateEncoding );
552 
553             Template velocityTemplate = engine.getTemplate( templateDirectory + "/" + template, templateEncoding );
554 
555             velocityTemplate.merge( context, writer );
556 
557             writer.flush();
558 
559             writer.close();
560 
561             getLog().info( "Created template " + f );
562         }
563 
564         catch ( ResourceNotFoundException rnfe )
565         {
566             throw new ResourceNotFoundException( "Template not found. ( " + templateDirectory + "/" + template + " )" );
567         }
568         catch ( VelocityException ve )
569         {
570             throw new VelocityException( ve.toString() );
571         }
572 
573         catch ( Exception e )
574         {
575             if ( e.getCause() != null )
576             {
577                 getLog().warn( e.getCause() );
578             }
579             throw new MojoExecutionException( e.toString(), e.getCause() );
580         }
581     }
582 
583     public void doJiraGenerate()
584         throws MojoExecutionException
585     {
586         List releases = getJiraReleases();
587 
588         getLog().info( "Creating announcement file from JIRA releases..." );
589 
590         doGenerate( releases );
591     }
592 
593     protected List getJiraReleases()
594         throws MojoExecutionException
595     {
596         JiraDownloader jiraDownloader = new JiraDownloader();
597 
598         File jiraXMLFile = jiraXML;
599 
600         jiraDownloader.setLog( getLog() );
601 
602         jiraDownloader.setOutput( jiraXMLFile );
603 
604         jiraDownloader.setStatusIds( statusIds );
605 
606         jiraDownloader.setResolutionIds( resolutionIds );
607 
608         jiraDownloader.setMavenProject( project );
609 
610         jiraDownloader.setSettings( settings );
611 
612         jiraDownloader.setNbEntries( maxEntries );
613 
614         jiraDownloader.setJiraUser( jiraUser );
615 
616         jiraDownloader.setJiraPassword( jiraPassword );
617 
618         try
619         {
620             jiraDownloader.doExecute();
621 
622             if ( jiraXMLFile.exists() )
623             {
624                 JiraXML jiraParser = new JiraXML( jiraXMLFile );
625 
626                 List issues = jiraParser.getIssueList();
627 
628                 return JiraXML.getReleases( issues );
629             }
630             else
631             {
632                 getLog().warn( "jira file " + jiraXMLFile.getPath() + " doesn't exists " );
633             }
634             return Collections.EMPTY_LIST;
635         }
636         catch ( Exception e )
637         {
638             throw new MojoExecutionException( "Failed to extract JIRA issues from the downloaded file", e );
639         }
640     }
641 
642     protected List mergeReleases( List changesReleases, List jiraReleases )
643     {
644         if ( changesReleases == null && jiraReleases == null )
645         {
646             return Collections.EMPTY_LIST;
647         }
648         if ( changesReleases == null )
649         {
650             return jiraReleases;
651         }
652         if ( jiraReleases == null )
653         {
654             return changesReleases;
655         }
656 
657         for ( Iterator iterator = changesReleases.iterator(); iterator.hasNext(); )
658         {
659             Release release = (Release) iterator.next();
660             Release jiraRelease = getRelease( jiraReleases, release.getVersion() );
661             if ( jiraRelease != null )
662             {
663                 if ( jiraRelease.getActions() != null )
664                 {
665                     release.getActions().addAll( jiraRelease.getActions() );
666                 }
667             }
668         }
669         return changesReleases;
670     }
671 
672     /*
673      * accessors
674      */
675 
676     public File getXmlPath()
677     {
678         return xmlPath;
679     }
680 
681     public void setXmlPath( File xmlPath )
682     {
683         this.xmlPath = xmlPath;
684     }
685 
686     public File getOutputDirectory()
687     {
688         return outputDirectory;
689     }
690 
691     public void setOutputDirectory( File outputDirectory )
692     {
693         this.outputDirectory = outputDirectory;
694     }
695 
696     public String getGroupId()
697     {
698         return groupId;
699     }
700 
701     public void setGroupId( String groupId )
702     {
703         this.groupId = groupId;
704     }
705 
706     public String getArtifactId()
707     {
708         return artifactId;
709     }
710 
711     public void setArtifactId( String artifactId )
712     {
713         this.artifactId = artifactId;
714     }
715 
716     public String getVersion()
717     {
718         return version;
719     }
720 
721     public void setVersion( String version )
722     {
723         this.version = version;
724     }
725 
726     public String getUrl()
727     {
728         return url;
729     }
730 
731     public void setUrl( String url )
732     {
733         this.url = url;
734     }
735 
736     public ChangesXML getXml()
737     {
738         return xml;
739     }
740 
741     public void setXml( ChangesXML xml )
742     {
743         this.xml = xml;
744     }
745 
746     public String getPackaging()
747     {
748         return packaging;
749     }
750 
751     public void setPackaging( String packaging )
752     {
753         this.packaging = packaging;
754     }
755 
756     public String getDevelopmentTeam()
757     {
758         return developmentTeam;
759     }
760 
761     public void setDevelopmentTeam( String developmentTeam )
762     {
763         this.developmentTeam = developmentTeam;
764     }
765 
766     public String getIntroduction()
767     {
768         return introduction;
769     }
770 
771     public void setIntroduction( String introduction )
772     {
773         this.introduction = introduction;
774     }
775 
776     public VelocityComponent getVelocity()
777     {
778         return velocity;
779     }
780 
781     public void setVelocity( VelocityComponent velocity )
782     {
783         this.velocity = velocity;
784     }
785 
786     public String getFinalName()
787     {
788         return finalName;
789     }
790 
791     public void setFinalName( String finalName )
792     {
793         this.finalName = finalName;
794     }
795 
796     public String getUrlDownload()
797     {
798         return urlDownload;
799     }
800 
801     public void setUrlDownload( String urlDownload )
802     {
803         this.urlDownload = urlDownload;
804     }
805 }