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