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