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