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