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 @Parameter( defaultValue = "${project}", readonly = true, required = true )
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 @Parameter( defaultValue = "${settings}", readonly = true, required = true )
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 @Parameter( property = "changes.jiraConnectionTimeout", defaultValue = "36000" )
391 private int jiraConnectionTimeout;
392
393
394
395
396
397
398
399
400
401 @Parameter( property = "changes.jiraReceiveTimout", defaultValue = "32000" )
402 private int jiraReceiveTimout;
403
404
405
406
407
408
409
410
411
412
413
414 @Parameter( property = "changes.tracPassword", defaultValue = "" )
415 private String tracPassword;
416
417
418
419
420
421
422 @Parameter( defaultValue = "order=id" )
423 private String tracQuery;
424
425
426
427
428
429
430
431 @Parameter( property = "changes.tracUser", defaultValue = "" )
432 private String tracUser;
433
434
435
436
437
438
439
440
441
442
443 @Parameter( defaultValue = "http", property = "changes.githubAPIScheme" )
444 private String githubAPIScheme;
445
446
447
448
449
450
451 @Parameter( defaultValue = "80", property = "changes.githubAPIPort" )
452 private int githubAPIPort;
453
454 private ReleaseUtils releaseUtils = new ReleaseUtils( getLog() );
455
456 private ChangesXML xml;
457
458
459
460
461
462
463
464
465
466
467 public void execute()
468 throws MojoExecutionException
469 {
470
471 if ( outputDirectory != null )
472 {
473 throw new MojoExecutionException( "You are using the old parameter 'outputDirectory'. You must use 'announcementDirectory' instead." );
474 }
475
476
477 if ( runOnlyAtExecutionRoot && !isThisTheExecutionRoot() )
478 {
479 getLog().info( "Skipping the announcement generation in this project because it's not the Execution Root" );
480 }
481 else
482 {
483 if ( issueManagementSystems == null )
484 {
485 issueManagementSystems = new ArrayList<String>();
486 }
487
488
489 if ( issueManagementSystems.isEmpty() )
490 {
491 if ( this.jiraMerge )
492 {
493 issueManagementSystems.add( CHANGES_XML );
494 issueManagementSystems.add( JIRA );
495 }
496 else if ( generateJiraAnnouncement )
497 {
498 issueManagementSystems.add( JIRA );
499 }
500 else
501 {
502 issueManagementSystems.add( CHANGES_XML );
503 }
504 }
505
506
507 List<Release> releases = null;
508 if ( issueManagementSystems.contains( CHANGES_XML ) )
509 {
510 if ( getXmlPath().exists() )
511 {
512 ChangesXML changesXML = new ChangesXML( getXmlPath(), getLog() );
513 List<Release> changesReleases = releaseUtils.convertReleaseList( changesXML.getReleaseList() );
514 releases = releaseUtils.mergeReleases( null, changesReleases );
515 getLog().info( "Including issues from file " + getXmlPath() + " in announcement..." );
516 }
517 else
518 {
519 getLog().warn( "changes.xml file " + getXmlPath().getAbsolutePath() + " does not exist." );
520 }
521 }
522
523 if ( issueManagementSystems.contains( JIRA ) )
524 {
525 if ( ProjectUtils.validateIfIssueManagementComplete( project, JIRA, "JIRA announcement", getLog() ) )
526 {
527 List<Release> jiraReleases = getJiraReleases();
528 releases = releaseUtils.mergeReleases( releases, jiraReleases );
529 getLog().info( "Including issues from JIRA in announcement..." );
530 }
531 else
532 {
533 throw new MojoExecutionException(
534 "Something is wrong with the Issue Management section. See previous error messages." );
535 }
536 }
537
538 if ( issueManagementSystems.contains( TRAC ) )
539 {
540 if ( ProjectUtils.validateIfIssueManagementComplete( project, TRAC, "Trac announcement", getLog() ) )
541 {
542 List<Release> tracReleases = getTracReleases();
543 releases = releaseUtils.mergeReleases( releases, tracReleases );
544 getLog().info( "Including issues from Trac in announcement..." );
545 }
546 else
547 {
548 throw new MojoExecutionException(
549 "Something is wrong with the Issue Management section. See previous error messages." );
550 }
551 }
552
553 if ( issueManagementSystems.contains( GIT_HUB ) )
554 {
555 if ( ProjectUtils.validateIfIssueManagementComplete( project, GIT_HUB, "GitHub announcement", getLog() ) )
556 {
557 List<Release> gitHubReleases = getGitHubReleases();
558 releases = releaseUtils.mergeReleases( releases, gitHubReleases );
559 getLog().info( "Including issues from GitHub in announcement..." );
560 }
561 else
562 {
563 throw new MojoExecutionException(
564 "Something is wrong with the Issue Management section. See previous error messages." );
565 }
566 }
567
568
569
570
571
572
573
574
575
576
577
578 if ( releases == null || releases.isEmpty() )
579 {
580 throw new MojoExecutionException(
581 "No releases found in any of the configured issue management systems." );
582 }
583 else
584 {
585 doGenerate( releases );
586 }
587 }
588 }
589
590
591
592
593
594
595
596 public void doGenerate( List<Release> releases )
597 throws MojoExecutionException
598 {
599 String version = ( versionPrefix == null ? "" : versionPrefix ) + getVersion();
600
601 getLog().debug( "Generating announcement for version [" + version + "]. Found these releases: "
602 + ReleaseUtils.toString( releases ) );
603
604 doGenerate( releases, releaseUtils.getLatestRelease( releases, version ) );
605 }
606
607 protected void doGenerate( List<Release> releases, Release release )
608 throws MojoExecutionException
609 {
610 try
611 {
612 ToolManager toolManager = new ToolManager( true );
613 Context context = toolManager.createContext();
614
615 if ( getIntroduction() == null || getIntroduction().equals( "" ) )
616 {
617 setIntroduction( getUrl() );
618 }
619
620 context.put( "releases", releases );
621
622 context.put( "groupId", getGroupId() );
623
624 context.put( "artifactId", getArtifactId() );
625
626 context.put( "version", getVersion() );
627
628 context.put( "packaging", getPackaging() );
629
630 context.put( "url", getUrl() );
631
632 context.put( "release", release );
633
634 context.put( "introduction", getIntroduction() );
635
636 context.put( "developmentTeam", getDevelopmentTeam() );
637
638 context.put( "finalName", getFinalName() );
639
640 context.put( "urlDownload", getUrlDownload() );
641
642 context.put( "project", project );
643
644 if ( announceParameters == null )
645 {
646
647 context.put( "announceParameters", Collections.EMPTY_MAP );
648 }
649 else
650 {
651 context.put( "announceParameters", announceParameters );
652 }
653
654
655 processTemplate( context, announcementDirectory, template, announcementFile );
656 }
657 catch ( ResourceNotFoundException rnfe )
658 {
659 throw new MojoExecutionException( "Resource not found.", rnfe );
660 }
661 catch ( VelocityException ve )
662 {
663 throw new MojoExecutionException( ve.toString(), ve );
664 }
665 }
666
667
668
669
670
671
672
673
674
675
676 public void processTemplate( Context context, File outputDirectory, String template, String announcementFile )
677 throws VelocityException, MojoExecutionException
678 {
679 File f;
680
681
682 if ( StringUtils.isEmpty( announcementFile ) )
683 {
684 announcementFile = template;
685 }
686
687 try
688 {
689 f = new File( outputDirectory, announcementFile );
690
691 if ( !f.getParentFile().exists() )
692 {
693 f.getParentFile().mkdirs();
694 }
695
696 VelocityEngine engine = velocity.getEngine();
697
698 engine.setApplicationAttribute( "baseDirectory", basedir );
699
700 if ( StringUtils.isEmpty( templateEncoding ) )
701 {
702 templateEncoding = ReaderFactory.FILE_ENCODING;
703 getLog().warn(
704 "File encoding has not been set, using platform encoding " + templateEncoding
705 + ", i.e. build is platform dependent!" );
706 }
707
708 Writer writer = new OutputStreamWriter( new FileOutputStream( f ), templateEncoding );
709
710 Template velocityTemplate = engine.getTemplate( templateDirectory + "/" + template, templateEncoding );
711
712 velocityTemplate.merge( context, writer );
713
714 writer.flush();
715
716 writer.close();
717
718 getLog().info( "Created template " + f );
719 }
720
721 catch ( ResourceNotFoundException rnfe )
722 {
723 throw new ResourceNotFoundException( "Template not found. ( " + templateDirectory + "/" + template + " )" );
724 }
725 catch ( VelocityException ve )
726 {
727 throw new VelocityException( ve.toString() );
728 }
729
730 catch ( Exception e )
731 {
732 if ( e.getCause() != null )
733 {
734 getLog().warn( e.getCause() );
735 }
736 throw new MojoExecutionException( e.toString(), e.getCause() );
737 }
738 }
739
740 protected List<Release> getJiraReleases()
741 throws MojoExecutionException
742 {
743 AbstractJiraDownloader jiraDownloader = new AdaptiveJiraDownloader();
744
745 File jiraXMLFile = jiraXML;
746
747 jiraDownloader.setLog( getLog() );
748
749 jiraDownloader.setOutput( jiraXMLFile );
750
751 jiraDownloader.setStatusIds( statusIds );
752
753 jiraDownloader.setResolutionIds( resolutionIds );
754
755 jiraDownloader.setMavenProject( project );
756
757 jiraDownloader.setSettings( settings );
758
759 jiraDownloader.setNbEntries( maxEntries );
760
761 jiraDownloader.setFilter( filter );
762
763 jiraDownloader.setJiraUser( jiraUser );
764
765 jiraDownloader.setJiraPassword( jiraPassword );
766
767 jiraDownloader.setUseJql( useJql );
768
769 jiraDownloader.setWebUser( webUser );
770
771 jiraDownloader.setWebPassword( webPassword );
772
773 jiraDownloader.setConnectionTimeout( jiraConnectionTimeout );
774
775 jiraDownloader.setReceiveTimout( jiraReceiveTimout );
776
777 try
778 {
779 jiraDownloader.doExecute();
780
781 List<Issue> issueList = jiraDownloader.getIssueList();
782
783 if ( StringUtils.isNotEmpty( versionPrefix ) )
784 {
785 int originalNumberOfIssues = issueList.size();
786 issueList = IssueUtils.filterIssuesWithVersionPrefix( issueList, versionPrefix );
787 getLog().debug( "Filtered out " + issueList.size() + " issues of " + originalNumberOfIssues
788 + " that matched the versionPrefix '" + versionPrefix + "'." );
789 }
790
791 return getReleases( issueList, new JIRAIssueManagmentSystem() );
792 }
793 catch ( Exception e )
794 {
795 throw new MojoExecutionException( "Failed to extract issues from JIRA.", e );
796 }
797 }
798
799 private List<Release> getReleases( List<Issue> issues, IssueManagementSystem ims )
800 throws MojoExecutionException
801 {
802 if ( issueTypes != null )
803 {
804 ims.applyConfiguration( issueTypes );
805 }
806 if ( issues.isEmpty() )
807 {
808 return Collections.emptyList();
809 }
810 else
811 {
812 IssueAdapter adapter = new IssueAdapter( ims );
813 return adapter.getReleases( issues );
814 }
815 }
816
817 protected List<Release> getTracReleases()
818 throws MojoExecutionException
819 {
820 TracDownloader issueDownloader = new TracDownloader();
821
822 issueDownloader.setProject( project );
823
824 issueDownloader.setQuery( tracQuery );
825
826 issueDownloader.setTracPassword( tracPassword );
827
828 issueDownloader.setTracUser( tracUser );
829
830 try
831 {
832 return getReleases( issueDownloader.getIssueList(), new TracIssueManagmentSystem() );
833 }
834 catch ( Exception e )
835 {
836 throw new MojoExecutionException( "Failed to extract issues from Trac.", e );
837 }
838 }
839
840 protected List<Release> getGitHubReleases()
841 throws MojoExecutionException
842 {
843 try
844 {
845 GitHubDownloader issueDownloader =
846 new GitHubDownloader( project, githubAPIScheme, githubAPIPort, false, true );
847 return getReleases( issueDownloader.getIssueList(), new GitHubIssueManagementSystem() );
848 }
849 catch ( Exception e )
850 {
851 throw new MojoExecutionException( "Failed to extract issues from GitHub.", e );
852 }
853 }
854
855
856
857
858
859 public String getArtifactId()
860 {
861 return artifactId;
862 }
863
864 public void setArtifactId( String artifactId )
865 {
866 this.artifactId = artifactId;
867 }
868
869 public String getDevelopmentTeam()
870 {
871 return developmentTeam;
872 }
873
874 public void setDevelopmentTeam( String developmentTeam )
875 {
876 this.developmentTeam = developmentTeam;
877 }
878
879 public String getFinalName()
880 {
881 return finalName;
882 }
883
884 public void setFinalName( String finalName )
885 {
886 this.finalName = finalName;
887 }
888
889 public String getGroupId()
890 {
891 return groupId;
892 }
893
894 public void setGroupId( String groupId )
895 {
896 this.groupId = groupId;
897 }
898
899 public String getIntroduction()
900 {
901 return introduction;
902 }
903
904 public void setIntroduction( String introduction )
905 {
906 this.introduction = introduction;
907 }
908
909 public void setIssueTypes( Map<String, String> issueTypes )
910 {
911 this.issueTypes = issueTypes;
912 }
913
914 public Map<String, String> getIssueTypes()
915 {
916 return issueTypes;
917 }
918
919 public File getAnnouncementDirectory()
920 {
921 return announcementDirectory;
922 }
923
924 public void setAnnouncementDirectory( File announcementDirectory )
925 {
926 this.announcementDirectory = announcementDirectory;
927 }
928
929 public String getPackaging()
930 {
931 return packaging;
932 }
933
934 public void setPackaging( String packaging )
935 {
936 this.packaging = packaging;
937 }
938
939 public String getUrl()
940 {
941 return url;
942 }
943
944 public void setUrl( String url )
945 {
946 this.url = url;
947 }
948
949 public String getUrlDownload()
950 {
951 return urlDownload;
952 }
953
954 public void setUrlDownload( String urlDownload )
955 {
956 this.urlDownload = urlDownload;
957 }
958
959 public VelocityComponent getVelocity()
960 {
961 return velocity;
962 }
963
964 public void setVelocity( VelocityComponent velocity )
965 {
966 this.velocity = velocity;
967 }
968
969 public String getVersion()
970 {
971 return version;
972 }
973
974 public void setVersion( String version )
975 {
976 this.version = version;
977 }
978
979 public ChangesXML getXml()
980 {
981 return xml;
982 }
983
984 public void setXml( ChangesXML xml )
985 {
986 this.xml = xml;
987 }
988
989 public File getXmlPath()
990 {
991 return xmlPath;
992 }
993
994 public void setXmlPath( File xmlPath )
995 {
996 this.xmlPath = xmlPath;
997 }
998 }