1 package org.apache.maven.plugin.announcement;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.plugin.MojoExecutionException;
23 import org.apache.maven.plugin.changes.ChangesXML;
24 import org.apache.maven.plugin.changes.IssueAdapter;
25 import org.apache.maven.plugin.changes.ProjectUtils;
26 import org.apache.maven.plugin.changes.ReleaseUtils;
27 import org.apache.maven.plugin.github.GitHubDownloader;
28 import org.apache.maven.plugin.github.GitHubIssueManagementSystem;
29 import org.apache.maven.plugin.issues.Issue;
30 import org.apache.maven.plugin.issues.IssueManagementSystem;
31 import org.apache.maven.plugin.issues.IssueUtils;
32 import org.apache.maven.plugin.jira.AbstractJiraDownloader;
33 import org.apache.maven.plugin.jira.AdaptiveJiraDownloader;
34 import org.apache.maven.plugin.jira.JIRAIssueManagmentSystem;
35 import org.apache.maven.plugin.trac.TracDownloader;
36 import org.apache.maven.plugin.trac.TracIssueManagmentSystem;
37 import org.apache.maven.plugins.annotations.Component;
38 import org.apache.maven.plugins.annotations.Mojo;
39 import org.apache.maven.plugins.annotations.Parameter;
40 import org.apache.maven.plugins.annotations.ResolutionScope;
41 import org.apache.maven.plugins.changes.model.Release;
42 import org.apache.maven.project.MavenProject;
43 import org.apache.maven.settings.Settings;
44 import org.apache.velocity.Template;
45 import org.apache.velocity.app.VelocityEngine;
46 import org.apache.velocity.context.Context;
47 import org.apache.velocity.exception.ResourceNotFoundException;
48 import org.apache.velocity.exception.VelocityException;
49 import org.apache.velocity.tools.ToolManager;
50 import org.codehaus.plexus.util.ReaderFactory;
51 import org.codehaus.plexus.util.StringUtils;
52 import org.codehaus.plexus.velocity.VelocityComponent;
53
54 import java.io.File;
55 import java.io.FileOutputStream;
56 import java.io.OutputStreamWriter;
57 import java.io.Writer;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.List;
61 import java.util.Map;
62
63
64
65
66
67
68
69
70 @Mojo( name = "announcement-generate", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
71 public class AnnouncementMojo
72 extends AbstractAnnouncementMojo
73 {
74 private static final String CHANGES_XML = "changes.xml";
75
76 private static final String JIRA = "JIRA";
77
78 private static final String TRAC = "Trac";
79
80 private static final String GIT_HUB = "GitHub";
81
82
83
84
85
86
87
88 @Parameter( property = "changes.announcementFile" )
89 private String announcementFile;
90
91
92
93
94
95
96
97 @Parameter
98 private Map announceParameters;
99
100
101
102 @Parameter( property = "project.artifactId", readonly = true )
103 private String artifactId;
104
105
106
107
108
109 @Parameter( property = "changes.developmentTeam", defaultValue = "${project.name} team", required = true )
110 private String developmentTeam;
111
112
113
114
115 @Parameter( property = "changes.finalName", defaultValue = "${project.build.finalName}", required = true )
116 private String finalName;
117
118
119
120 @Parameter( property = "project.groupId", readonly = true )
121 private String groupId;
122
123
124
125
126
127 @Parameter( defaultValue = "${project.description}" )
128 private String introduction;
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 @Parameter
145 private List<String> issueManagementSystems;
146
147
148
149
150
151
152
153
154
155
156
157 @Parameter
158 private Map<String, String> issueTypes;
159
160
161
162
163
164
165 @Parameter( defaultValue = "${project.build.directory}/announcement", required = true )
166 private File announcementDirectory;
167
168
169
170
171
172
173 @Parameter
174 private File outputDirectory;
175
176
177
178
179 @Parameter( property = "project.packaging", readonly = true )
180 private String packaging;
181
182
183
184
185 @Component
186 private MavenProject project;
187
188
189
190
191 @Parameter( property = "changes.template", defaultValue = "announcement.vm", required = true )
192 private String template;
193
194
195
196
197
198
199
200
201 @Parameter( property = "changes.templateDirectory", defaultValue = "org/apache/maven/plugin/announcement",
202 required = true )
203 private String templateDirectory;
204
205
206
207
208
209
210 @Parameter( property = "changes.templateEncoding", defaultValue = "${project.build.sourceEncoding}" )
211 private String templateEncoding;
212
213
214
215
216
217
218
219
220 @Parameter( property = "changes.useJql", defaultValue = "false" )
221 private boolean useJql;
222
223
224
225
226
227 @Parameter( property = "project.url" )
228 private String url;
229
230
231
232
233
234
235 @Parameter
236 private String urlDownload;
237
238
239
240
241 @Component( role = VelocityComponent.class, hint = "maven-changes-plugin" )
242 private VelocityComponent velocity;
243
244
245
246
247 @Parameter( property = "changes.version", defaultValue = "${project.version}", required = true )
248 private String version;
249
250
251
252
253
254
255
256 @Parameter( defaultValue = "${basedir}/src/changes/changes.xml" )
257 private File xmlPath;
258
259
260
261
262
263
264
265
266
267
268
269
270 @Parameter( defaultValue = "" )
271 private String filter;
272
273
274
275
276
277
278 @Parameter( property = "generateJiraAnnouncement", defaultValue = "false", required = true )
279 private boolean generateJiraAnnouncement;
280
281
282
283
284
285
286
287
288 @Parameter( property = "changes.jiraMerge", defaultValue = "false" )
289 private boolean jiraMerge;
290
291
292
293
294
295
296 @Parameter( property = "changes.jiraPassword", defaultValue = "" )
297 private String jiraPassword;
298
299
300
301
302
303
304 @Parameter( property = "changes.jiraUser", defaultValue = "" )
305 private String jiraUser;
306
307
308
309
310 @Parameter( defaultValue = "${project.build.directory}/jira-announcement.xml", required = true, readonly = true )
311 private File jiraXML;
312
313
314
315
316
317
318
319
320 @Parameter( property = "changes.maxEntries", defaultValue = "25", required = true )
321 private int maxEntries;
322
323
324
325
326
327
328
329
330
331 @Parameter( property = "changes.resolutionIds", defaultValue = "Fixed" )
332 private String resolutionIds;
333
334
335
336
337 @Component
338 private Settings settings;
339
340
341
342
343
344
345
346
347
348 @Parameter( property = "changes.statusIds", defaultValue = "Closed" )
349 private String statusIds;
350
351
352
353
354
355
356 @Parameter( property = "changes.webUser", defaultValue = "" )
357 private String webUser;
358
359
360
361
362
363
364 @Parameter( property = "changes.webPassword", defaultValue = "" )
365 private String webPassword;
366
367
368
369
370
371
372
373
374
375
376
377
378
379 @Parameter( property = "changes.versionPrefix", defaultValue = "" )
380 private String versionPrefix;
381
382
383
384
385
386
387
388
389
390
391
392 @Parameter( property = "changes.tracPassword", defaultValue = "" )
393 private String tracPassword;
394
395
396
397
398
399
400 @Parameter( defaultValue = "order=id" )
401 private String tracQuery;
402
403
404
405
406
407
408
409 @Parameter( property = "changes.tracUser", defaultValue = "" )
410 private String tracUser;
411
412
413
414
415
416
417
418
419
420
421 @Parameter( defaultValue = "http", property = "changes.githubAPIScheme" )
422 private String githubAPIScheme;
423
424
425
426
427
428
429 @Parameter( defaultValue = "80", property = "changes.githubAPIPort" )
430 private int githubAPIPort;
431
432 private ReleaseUtils releaseUtils = new ReleaseUtils( getLog() );
433
434 private ChangesXML xml;
435
436
437
438
439
440
441
442
443
444
445 public void execute()
446 throws MojoExecutionException
447 {
448
449 if ( outputDirectory != null )
450 {
451 throw new MojoExecutionException( "You are using the old parameter 'outputDirectory'. You must use 'announcementDirectory' instead." );
452 }
453
454
455 if ( runOnlyAtExecutionRoot && !isThisTheExecutionRoot() )
456 {
457 getLog().info( "Skipping the announcement generation in this project because it's not the Execution Root" );
458 }
459 else
460 {
461 if ( issueManagementSystems == null )
462 {
463 issueManagementSystems = new ArrayList<String>();
464 }
465
466
467 if ( issueManagementSystems.isEmpty() )
468 {
469 if ( this.jiraMerge )
470 {
471 issueManagementSystems.add( CHANGES_XML );
472 issueManagementSystems.add( JIRA );
473 }
474 else if ( generateJiraAnnouncement )
475 {
476 issueManagementSystems.add( JIRA );
477 }
478 else
479 {
480 issueManagementSystems.add( CHANGES_XML );
481 }
482 }
483
484
485 List<Release> releases = null;
486 if ( issueManagementSystems.contains( CHANGES_XML ) )
487 {
488 if ( getXmlPath().exists() )
489 {
490 ChangesXML changesXML = new ChangesXML( getXmlPath(), getLog() );
491 List<Release> changesReleases = releaseUtils.convertReleaseList( changesXML.getReleaseList() );
492 releases = releaseUtils.mergeReleases( null, changesReleases );
493 getLog().info( "Including issues from file " + getXmlPath() + " in announcement..." );
494 }
495 else
496 {
497 getLog().warn( "changes.xml file " + getXmlPath().getAbsolutePath() + " does not exist." );
498 }
499 }
500
501 if ( issueManagementSystems.contains( JIRA ) )
502 {
503 if ( ProjectUtils.validateIfIssueManagementComplete( project, JIRA, "JIRA announcement", getLog() ) )
504 {
505 List<Release> jiraReleases = getJiraReleases();
506 releases = releaseUtils.mergeReleases( releases, jiraReleases );
507 getLog().info( "Including issues from JIRA in announcement..." );
508 }
509 else
510 {
511 throw new MojoExecutionException(
512 "Something is wrong with the Issue Management section. See previous error messages." );
513 }
514 }
515
516 if ( issueManagementSystems.contains( TRAC ) )
517 {
518 if ( ProjectUtils.validateIfIssueManagementComplete( project, TRAC, "Trac announcement", getLog() ) )
519 {
520 List<Release> tracReleases = getTracReleases();
521 releases = releaseUtils.mergeReleases( releases, tracReleases );
522 getLog().info( "Including issues from Trac in announcement..." );
523 }
524 else
525 {
526 throw new MojoExecutionException(
527 "Something is wrong with the Issue Management section. See previous error messages." );
528 }
529 }
530
531 if ( issueManagementSystems.contains( GIT_HUB ) )
532 {
533 if ( ProjectUtils.validateIfIssueManagementComplete( project, GIT_HUB, "GitHub announcement", getLog() ) )
534 {
535 List<Release> gitHubReleases = getGitHubReleases();
536 releases = releaseUtils.mergeReleases( releases, gitHubReleases );
537 getLog().info( "Including issues from GitHub in announcement..." );
538 }
539 else
540 {
541 throw new MojoExecutionException(
542 "Something is wrong with the Issue Management section. See previous error messages." );
543 }
544 }
545
546
547
548
549
550
551
552
553
554
555
556 if ( releases == null || releases.isEmpty() )
557 {
558 throw new MojoExecutionException(
559 "No releases found in any of the configured issue management systems." );
560 }
561 else
562 {
563 doGenerate( releases );
564 }
565 }
566 }
567
568
569
570
571
572
573
574 public void doGenerate( List<Release> releases )
575 throws MojoExecutionException
576 {
577 String version = ( versionPrefix == null ? "" : versionPrefix ) + getVersion();
578
579 getLog().debug( "Generating announcement for version [" + version + "]. Found these releases: "
580 + ReleaseUtils.toString( releases ) );
581
582 doGenerate( releases, releaseUtils.getLatestRelease( releases, version ) );
583 }
584
585 protected void doGenerate( List<Release> releases, Release release )
586 throws MojoExecutionException
587 {
588 try
589 {
590 ToolManager toolManager = new ToolManager( true );
591 Context context = toolManager.createContext();
592
593 if ( getIntroduction() == null || getIntroduction().equals( "" ) )
594 {
595 setIntroduction( getUrl() );
596 }
597
598 context.put( "releases", releases );
599
600 context.put( "groupId", getGroupId() );
601
602 context.put( "artifactId", getArtifactId() );
603
604 context.put( "version", getVersion() );
605
606 context.put( "packaging", getPackaging() );
607
608 context.put( "url", getUrl() );
609
610 context.put( "release", release );
611
612 context.put( "introduction", getIntroduction() );
613
614 context.put( "developmentTeam", getDevelopmentTeam() );
615
616 context.put( "finalName", getFinalName() );
617
618 context.put( "urlDownload", getUrlDownload() );
619
620 context.put( "project", project );
621
622 if ( announceParameters == null )
623 {
624
625 context.put( "announceParameters", Collections.EMPTY_MAP );
626 }
627 else
628 {
629 context.put( "announceParameters", announceParameters );
630 }
631
632
633 processTemplate( context, announcementDirectory, template, announcementFile );
634 }
635 catch ( ResourceNotFoundException rnfe )
636 {
637 throw new MojoExecutionException( "Resource not found.", rnfe );
638 }
639 catch ( VelocityException ve )
640 {
641 throw new MojoExecutionException( ve.toString(), ve );
642 }
643 }
644
645
646
647
648
649
650
651
652
653
654 public void processTemplate( Context context, File outputDirectory, String template, String announcementFile )
655 throws VelocityException, MojoExecutionException
656 {
657 File f;
658
659
660 if ( StringUtils.isEmpty( announcementFile ) )
661 {
662 announcementFile = template;
663 }
664
665 try
666 {
667 f = new File( outputDirectory, announcementFile );
668
669 if ( !f.getParentFile().exists() )
670 {
671 f.getParentFile().mkdirs();
672 }
673
674 VelocityEngine engine = velocity.getEngine();
675
676 engine.setApplicationAttribute( "baseDirectory", basedir );
677
678 if ( StringUtils.isEmpty( templateEncoding ) )
679 {
680 templateEncoding = ReaderFactory.FILE_ENCODING;
681 getLog().warn(
682 "File encoding has not been set, using platform encoding " + templateEncoding
683 + ", i.e. build is platform dependent!" );
684 }
685
686 Writer writer = new OutputStreamWriter( new FileOutputStream( f ), templateEncoding );
687
688 Template velocityTemplate = engine.getTemplate( templateDirectory + "/" + template, templateEncoding );
689
690 velocityTemplate.merge( context, writer );
691
692 writer.flush();
693
694 writer.close();
695
696 getLog().info( "Created template " + f );
697 }
698
699 catch ( ResourceNotFoundException rnfe )
700 {
701 throw new ResourceNotFoundException( "Template not found. ( " + templateDirectory + "/" + template + " )" );
702 }
703 catch ( VelocityException ve )
704 {
705 throw new VelocityException( ve.toString() );
706 }
707
708 catch ( Exception e )
709 {
710 if ( e.getCause() != null )
711 {
712 getLog().warn( e.getCause() );
713 }
714 throw new MojoExecutionException( e.toString(), e.getCause() );
715 }
716 }
717
718 protected List<Release> getJiraReleases()
719 throws MojoExecutionException
720 {
721 AbstractJiraDownloader jiraDownloader = new AdaptiveJiraDownloader();
722
723 File jiraXMLFile = jiraXML;
724
725 jiraDownloader.setLog( getLog() );
726
727 jiraDownloader.setOutput( jiraXMLFile );
728
729 jiraDownloader.setStatusIds( statusIds );
730
731 jiraDownloader.setResolutionIds( resolutionIds );
732
733 jiraDownloader.setMavenProject( project );
734
735 jiraDownloader.setSettings( settings );
736
737 jiraDownloader.setNbEntries( maxEntries );
738
739 jiraDownloader.setFilter( filter );
740
741 jiraDownloader.setJiraUser( jiraUser );
742
743 jiraDownloader.setJiraPassword( jiraPassword );
744
745 jiraDownloader.setUseJql( useJql );
746
747 jiraDownloader.setWebUser( webUser );
748
749 jiraDownloader.setWebPassword( webPassword );
750
751 try
752 {
753 jiraDownloader.doExecute();
754
755 List<Issue> issueList = jiraDownloader.getIssueList();
756
757 if ( StringUtils.isNotEmpty( versionPrefix ) )
758 {
759 int originalNumberOfIssues = issueList.size();
760 issueList = IssueUtils.filterIssuesWithVersionPrefix( issueList, versionPrefix );
761 getLog().debug( "Filtered out " + issueList.size() + " issues of " + originalNumberOfIssues
762 + " that matched the versionPrefix '" + versionPrefix + "'." );
763 }
764
765 return getReleases( issueList, new JIRAIssueManagmentSystem() );
766 }
767 catch ( Exception e )
768 {
769 throw new MojoExecutionException( "Failed to extract issues from JIRA.", e );
770 }
771 }
772
773 private List<Release> getReleases( List<Issue> issues, IssueManagementSystem ims )
774 throws MojoExecutionException
775 {
776 if ( issueTypes != null )
777 {
778 ims.applyConfiguration( issueTypes );
779 }
780 if ( issues.isEmpty() )
781 {
782 return Collections.emptyList();
783 }
784 else
785 {
786 IssueAdapter adapter = new IssueAdapter( ims );
787 return adapter.getReleases( issues );
788 }
789 }
790
791 protected List<Release> getTracReleases()
792 throws MojoExecutionException
793 {
794 TracDownloader issueDownloader = new TracDownloader();
795
796 issueDownloader.setProject( project );
797
798 issueDownloader.setQuery( tracQuery );
799
800 issueDownloader.setTracPassword( tracPassword );
801
802 issueDownloader.setTracUser( tracUser );
803
804 try
805 {
806 return getReleases( issueDownloader.getIssueList(), new TracIssueManagmentSystem() );
807 }
808 catch ( Exception e )
809 {
810 throw new MojoExecutionException( "Failed to extract issues from Trac.", e );
811 }
812 }
813
814 protected List<Release> getGitHubReleases()
815 throws MojoExecutionException
816 {
817 try
818 {
819 GitHubDownloader issueDownloader =
820 new GitHubDownloader( project, githubAPIScheme, githubAPIPort, false, true );
821 return getReleases( issueDownloader.getIssueList(), new GitHubIssueManagementSystem() );
822 }
823 catch ( Exception e )
824 {
825 throw new MojoExecutionException( "Failed to extract issues from GitHub.", e );
826 }
827 }
828
829
830
831
832
833 public String getArtifactId()
834 {
835 return artifactId;
836 }
837
838 public void setArtifactId( String artifactId )
839 {
840 this.artifactId = artifactId;
841 }
842
843 public String getDevelopmentTeam()
844 {
845 return developmentTeam;
846 }
847
848 public void setDevelopmentTeam( String developmentTeam )
849 {
850 this.developmentTeam = developmentTeam;
851 }
852
853 public String getFinalName()
854 {
855 return finalName;
856 }
857
858 public void setFinalName( String finalName )
859 {
860 this.finalName = finalName;
861 }
862
863 public String getGroupId()
864 {
865 return groupId;
866 }
867
868 public void setGroupId( String groupId )
869 {
870 this.groupId = groupId;
871 }
872
873 public String getIntroduction()
874 {
875 return introduction;
876 }
877
878 public void setIntroduction( String introduction )
879 {
880 this.introduction = introduction;
881 }
882
883 public void setIssueTypes( Map<String, String> issueTypes )
884 {
885 this.issueTypes = issueTypes;
886 }
887
888 public Map<String, String> getIssueTypes()
889 {
890 return issueTypes;
891 }
892
893 public File getAnnouncementDirectory()
894 {
895 return announcementDirectory;
896 }
897
898 public void setAnnouncementDirectory( File announcementDirectory )
899 {
900 this.announcementDirectory = announcementDirectory;
901 }
902
903 public String getPackaging()
904 {
905 return packaging;
906 }
907
908 public void setPackaging( String packaging )
909 {
910 this.packaging = packaging;
911 }
912
913 public String getUrl()
914 {
915 return url;
916 }
917
918 public void setUrl( String url )
919 {
920 this.url = url;
921 }
922
923 public String getUrlDownload()
924 {
925 return urlDownload;
926 }
927
928 public void setUrlDownload( String urlDownload )
929 {
930 this.urlDownload = urlDownload;
931 }
932
933 public VelocityComponent getVelocity()
934 {
935 return velocity;
936 }
937
938 public void setVelocity( VelocityComponent velocity )
939 {
940 this.velocity = velocity;
941 }
942
943 public String getVersion()
944 {
945 return version;
946 }
947
948 public void setVersion( String version )
949 {
950 this.version = version;
951 }
952
953 public ChangesXML getXml()
954 {
955 return xml;
956 }
957
958 public void setXml( ChangesXML xml )
959 {
960 this.xml = xml;
961 }
962
963 public File getXmlPath()
964 {
965 return xmlPath;
966 }
967
968 public void setXmlPath( File xmlPath )
969 {
970 this.xmlPath = xmlPath;
971 }
972 }