1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.invoker;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.text.DecimalFormat;
24 import java.text.DecimalFormatSymbols;
25 import java.text.MessageFormat;
26 import java.text.NumberFormat;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Locale;
30
31 import org.apache.maven.doxia.sink.Sink;
32 import org.apache.maven.plugins.annotations.Component;
33 import org.apache.maven.plugins.annotations.Mojo;
34 import org.apache.maven.plugins.annotations.Parameter;
35 import org.apache.maven.plugins.invoker.model.BuildJob;
36 import org.apache.maven.plugins.invoker.model.io.xpp3.BuildJobXpp3Reader;
37 import org.apache.maven.reporting.AbstractMavenReport;
38 import org.apache.maven.reporting.MavenReportException;
39 import org.codehaus.plexus.i18n.I18N;
40 import org.codehaus.plexus.util.ReaderFactory;
41 import org.codehaus.plexus.util.StringUtils;
42 import org.codehaus.plexus.util.xml.XmlStreamReader;
43 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
44
45
46
47
48
49
50
51
52
53 @Mojo(name = "report", threadSafe = true)
54 public class InvokerReport extends AbstractMavenReport {
55
56
57
58
59 @Component
60 protected I18N i18n;
61
62
63
64
65 @Parameter(defaultValue = "${project.build.directory}/invoker-reports", property = "invoker.reportsDirectory")
66 private File reportsDirectory;
67
68
69
70
71 private NumberFormat percentFormat;
72
73
74
75
76 private NumberFormat secondsFormat;
77
78
79
80
81 private MessageFormat nameAndDescriptionFormat;
82
83 protected void executeReport(Locale locale) throws MavenReportException {
84 DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
85 percentFormat = new DecimalFormat(getText(locale, "report.invoker.format.percent"), symbols);
86 secondsFormat = new DecimalFormat(getText(locale, "report.invoker.format.seconds"), symbols);
87 nameAndDescriptionFormat = new MessageFormat(getText(locale, "report.invoker.format.name_with_description"));
88
89 Sink sink = getSink();
90
91 sink.head();
92
93 sink.title();
94 sink.text(getText(locale, "report.invoker.result.title"));
95 sink.title_();
96
97 sink.head_();
98
99 sink.body();
100
101 sink.section1();
102 sink.sectionTitle1();
103 sink.text(getText(locale, "report.invoker.result.title"));
104 sink.sectionTitle1_();
105 sink.paragraph();
106 sink.text(getText(locale, "report.invoker.result.description"));
107 sink.paragraph_();
108 sink.section1_();
109
110
111
112
113 File[] reportFiles = ReportUtils.getReportFiles(reportsDirectory);
114 if (reportFiles.length <= 0) {
115 getLog().info("no invoker report files found, skip report generation");
116 return;
117 }
118
119 BuildJobXpp3Reader buildJobReader = new BuildJobXpp3Reader();
120
121 List<BuildJob> buildJobs = new ArrayList<>(reportFiles.length);
122 for (File reportFile : reportFiles) {
123 try (XmlStreamReader xmlReader = ReaderFactory.newXmlReader(reportFile)) {
124 buildJobs.add(buildJobReader.read(xmlReader));
125 } catch (XmlPullParserException e) {
126 throw new MavenReportException("Failed to parse report file: " + reportFile, e);
127 } catch (IOException e) {
128 throw new MavenReportException("Failed to read report file: " + reportFile, e);
129 }
130 }
131
132
133
134
135
136 constructSummarySection(buildJobs, locale);
137
138
139
140
141
142 sink.section2();
143 sink.sectionTitle2();
144
145 sink.text(getText(locale, "report.invoker.detail.title"));
146
147 sink.sectionTitle2_();
148
149 sink.section2_();
150
151
152 sink.table();
153 sink.tableRows(null, false);
154
155 sink.tableRow();
156
157
158
159 sinkTableHeader(sink, getText(locale, "report.invoker.detail.name"));
160 sinkTableHeader(sink, getText(locale, "report.invoker.detail.result"));
161 sinkTableHeader(sink, getText(locale, "report.invoker.detail.time"));
162 sinkTableHeader(sink, getText(locale, "report.invoker.detail.message"));
163
164 sink.tableRow_();
165
166 for (BuildJob buildJob : buildJobs) {
167 renderBuildJob(buildJob);
168 }
169
170 sink.tableRows_();
171 sink.table_();
172
173 sink.body_();
174
175 sink.flush();
176 sink.close();
177 }
178
179 private void constructSummarySection(List<? extends BuildJob> buildJobs, Locale locale) {
180 Sink sink = getSink();
181
182 sink.section2();
183 sink.sectionTitle2();
184
185 sink.text(getText(locale, "report.invoker.summary.title"));
186
187 sink.sectionTitle2_();
188 sink.section2_();
189
190
191
192
193
194
195 sink.table();
196 sink.tableRows(null, false);
197
198 sink.tableRow();
199
200 sinkTableHeader(sink, getText(locale, "report.invoker.summary.number"));
201 sinkTableHeader(sink, getText(locale, "report.invoker.summary.success"));
202 sinkTableHeader(sink, getText(locale, "report.invoker.summary.failed"));
203 sinkTableHeader(sink, getText(locale, "report.invoker.summary.skipped"));
204 sinkTableHeader(sink, getText(locale, "report.invoker.summary.success.rate"));
205 sinkTableHeader(sink, getText(locale, "report.invoker.summary.time.total"));
206 sinkTableHeader(sink, getText(locale, "report.invoker.summary.time.avg"));
207
208 int number = buildJobs.size();
209 int success = 0;
210 int failed = 0;
211 int skipped = 0;
212 double totalTime = 0;
213
214 for (BuildJob buildJob : buildJobs) {
215 if (BuildJob.Result.SUCCESS.equals(buildJob.getResult())) {
216 success++;
217 } else if (BuildJob.Result.SKIPPED.equals(buildJob.getResult())) {
218 skipped++;
219 } else {
220 failed++;
221 }
222 totalTime += buildJob.getTime();
223 }
224
225 sink.tableRow_();
226 sink.tableRow();
227
228 sinkCell(sink, Integer.toString(number));
229 sinkCell(sink, Integer.toString(success));
230 sinkCell(sink, Integer.toString(failed));
231 sinkCell(sink, Integer.toString(skipped));
232
233 if (success + failed > 0) {
234 sinkCell(sink, percentFormat.format((double) success / (success + failed)));
235 } else {
236 sinkCell(sink, "");
237 }
238
239 sinkCell(sink, secondsFormat.format(totalTime));
240
241 sinkCell(sink, secondsFormat.format(totalTime / number));
242
243 sink.tableRow_();
244
245 sink.tableRows_();
246 sink.table_();
247 }
248
249 private void renderBuildJob(BuildJob buildJob) {
250 Sink sink = getSink();
251 sink.tableRow();
252 sinkCell(sink, getBuildJobReportName(buildJob));
253
254 sinkCell(sink, buildJob.getResult());
255 sinkCell(sink, secondsFormat.format(buildJob.getTime()));
256 sinkCell(sink, buildJob.getFailureMessage());
257 sink.tableRow_();
258 }
259
260 private String getBuildJobReportName(BuildJob buildJob) {
261 String buildJobName = buildJob.getName();
262 String buildJobDescription = buildJob.getDescription();
263 boolean emptyJobName = StringUtils.isEmpty(buildJobName);
264 boolean emptyJobDescription = StringUtils.isEmpty(buildJobDescription);
265 boolean isReportJobNameComplete = !emptyJobName && !emptyJobDescription;
266 if (isReportJobNameComplete) {
267 return getFormattedName(buildJobName, buildJobDescription);
268 } else {
269 String buildJobProject = buildJob.getProject();
270 if (!emptyJobName) {
271 getLog().warn(incompleteNameWarning("description", buildJobProject));
272 } else if (!emptyJobDescription) {
273 getLog().warn(incompleteNameWarning("name", buildJobProject));
274 }
275 return buildJobProject;
276 }
277 }
278
279 private static String incompleteNameWarning(String missing, String pom) {
280 return String.format(
281 "Incomplete job name-description: %s is missing. " + "POM (%s) will be used in place of job name.",
282 missing, pom);
283 }
284
285 private String getFormattedName(String name, String description) {
286 return nameAndDescriptionFormat.format(new Object[] {name, description});
287 }
288
289 public String getDescription(Locale locale) {
290 return getText(locale, "report.invoker.result.description");
291 }
292
293 public String getName(Locale locale) {
294 return getText(locale, "report.invoker.result.name");
295 }
296
297 public String getOutputName() {
298 return "invoker-report";
299 }
300
301 public boolean canGenerateReport() {
302 return ReportUtils.getReportFiles(reportsDirectory).length > 0;
303 }
304
305 private String getText(Locale locale, String key) {
306 return i18n.getString("invoker-report", locale, key);
307 }
308
309 private void sinkTableHeader(Sink sink, String header) {
310 sink.tableHeaderCell();
311 sink.text(header);
312 sink.tableHeaderCell_();
313 }
314
315 private void sinkCell(Sink sink, String text) {
316 sink.tableCell();
317 sink.text(text);
318 sink.tableCell_();
319 }
320 }