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