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