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