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.JIRAIssueManagementSystem;
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().isEmpty()) {
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 JIRAIssueManagementSystem());
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 }