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