Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
LinkcheckReport |
|
| 2.5;2,5 |
1 | package org.apache.maven.plugins.linkcheck; | |
2 | ||
3 | /* | |
4 | * Licensed to the Apache Software Foundation (ASF) under one | |
5 | * or more contributor license agreements. See the NOTICE file | |
6 | * distributed with this work for additional information | |
7 | * regarding copyright ownership. The ASF licenses this file | |
8 | * to you under the Apache License, Version 2.0 (the | |
9 | * "License"); you may not use this file except in compliance | |
10 | * with the License. You may obtain a copy of the License at | |
11 | * | |
12 | * http://www.apache.org/licenses/LICENSE-2.0 | |
13 | * | |
14 | * Unless required by applicable law or agreed to in writing, | |
15 | * software distributed under the License is distributed on an | |
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
17 | * KIND, either express or implied. See the License for the | |
18 | * specific language governing permissions and limitations | |
19 | * under the License. | |
20 | */ | |
21 | ||
22 | import java.io.File; | |
23 | import java.io.IOException; | |
24 | import java.net.URL; | |
25 | import java.util.ArrayList; | |
26 | import java.util.Arrays; | |
27 | import java.util.List; | |
28 | import java.util.Locale; | |
29 | import java.util.Properties; | |
30 | ||
31 | import org.apache.maven.artifact.repository.ArtifactRepository; | |
32 | import org.apache.maven.doxia.linkcheck.HttpBean; | |
33 | import org.apache.maven.doxia.linkcheck.LinkCheck; | |
34 | import org.apache.maven.doxia.linkcheck.LinkCheckException; | |
35 | import org.apache.maven.doxia.linkcheck.model.LinkcheckModel; | |
36 | import org.apache.maven.doxia.siterenderer.Renderer; | |
37 | import org.apache.maven.plugin.MojoExecutionException; | |
38 | import org.apache.maven.project.MavenProject; | |
39 | import org.apache.maven.reporting.AbstractMavenReport; | |
40 | import org.apache.maven.reporting.MavenReportException; | |
41 | import org.apache.maven.settings.Proxy; | |
42 | import org.apache.maven.settings.Settings; | |
43 | import org.codehaus.plexus.i18n.I18N; | |
44 | import org.codehaus.plexus.util.FileUtils; | |
45 | import org.codehaus.plexus.util.ReaderFactory; | |
46 | import org.codehaus.plexus.util.StringUtils; | |
47 | ||
48 | /** | |
49 | * Generates a <code>Linkcheck</code> report. | |
50 | * | |
51 | * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> | |
52 | * @version $Id: LinkcheckReport.java 1021298 2010-10-11 10:18:19Z ltheussl $ | |
53 | * @since 1.0 | |
54 | * @goal linkcheck | |
55 | */ | |
56 | 0 | public class LinkcheckReport |
57 | extends AbstractMavenReport | |
58 | { | |
59 | // ---------------------------------------------------------------------- | |
60 | // Report Components | |
61 | // ---------------------------------------------------------------------- | |
62 | ||
63 | /** | |
64 | * Internationalization. | |
65 | * | |
66 | * @component | |
67 | */ | |
68 | private I18N i18n; | |
69 | ||
70 | /** | |
71 | * Doxia Site Renderer. | |
72 | * | |
73 | * @component | |
74 | */ | |
75 | private Renderer siteRenderer; | |
76 | ||
77 | /** | |
78 | * LinkCheck component. | |
79 | * | |
80 | * @component | |
81 | */ | |
82 | private LinkCheck linkCheck; | |
83 | ||
84 | // ---------------------------------------------------------------------- | |
85 | // Report Parameters | |
86 | // ---------------------------------------------------------------------- | |
87 | ||
88 | /** | |
89 | * The Maven Project. | |
90 | * | |
91 | * @parameter expression="${project}" | |
92 | * @required | |
93 | * @readonly | |
94 | */ | |
95 | private MavenProject project; | |
96 | ||
97 | /** | |
98 | * Local Repository. | |
99 | * | |
100 | * @parameter expression="${localRepository}" | |
101 | * @required | |
102 | * @readonly | |
103 | */ | |
104 | private ArtifactRepository localRepository; | |
105 | ||
106 | /** | |
107 | * Report output directory. | |
108 | * | |
109 | * @parameter expression="${project.reporting.outputDirectory}" | |
110 | * @required | |
111 | */ | |
112 | private File outputDirectory; | |
113 | ||
114 | /** | |
115 | * The Maven Settings. | |
116 | * | |
117 | * @parameter default-value="${settings}" | |
118 | * @required | |
119 | * @readonly | |
120 | */ | |
121 | private Settings settings; | |
122 | ||
123 | // ---------------------------------------------------------------------- | |
124 | // Linkcheck parameters | |
125 | // ---------------------------------------------------------------------- | |
126 | ||
127 | /** | |
128 | * Whether we are offline or not. | |
129 | * | |
130 | * @parameter default-value="${settings.offline}" expression="${linkcheck.offline}" | |
131 | * @required | |
132 | */ | |
133 | private boolean offline; | |
134 | ||
135 | /** | |
136 | * If online, the HTTP method should automatically follow HTTP redirects, | |
137 | * <tt>false</tt> otherwise. | |
138 | * | |
139 | * @parameter default-value="true" | |
140 | */ | |
141 | private boolean httpFollowRedirect; | |
142 | ||
143 | /** | |
144 | * The location of the Linkcheck cache file. | |
145 | * | |
146 | * @parameter default-value="${project.build.directory}/linkcheck/linkcheck.cache" | |
147 | * @required | |
148 | */ | |
149 | protected File linkcheckCache; | |
150 | ||
151 | /** | |
152 | * The location of the Linkcheck report file. | |
153 | * | |
154 | * @parameter default-value="${project.build.directory}/linkcheck/linkcheck.xml" | |
155 | * @required | |
156 | */ | |
157 | protected File linkcheckOutput; | |
158 | ||
159 | /** | |
160 | * The HTTP method to use. Currently supported are "GET" and "HEAD". | |
161 | * <dl> | |
162 | * <dt>HTTP GET</dt> | |
163 | * <dd> | |
164 | * The HTTP GET method is defined in section 9.3 of | |
165 | * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: | |
166 | * The GET method means retrieve whatever information (in the form of an | |
167 | * entity) is identified by the Request-URI. | |
168 | * </dd> | |
169 | * <dt>HTTP HEAD</dt> | |
170 | * <dd> | |
171 | * The HTTP HEAD method is defined in section 9.4 of | |
172 | * <a href="http://www.ietf.org/rfc/rfc2616.txt">RFC2616</a>: | |
173 | * The HEAD method is identical to GET except that the server MUST NOT | |
174 | * return a message-body in the response. | |
175 | * </dd> | |
176 | * </dl> | |
177 | * | |
178 | * @parameter default-value="head" | |
179 | * @required | |
180 | */ | |
181 | private String httpMethod; | |
182 | ||
183 | /** | |
184 | * The list of HTTP errors to ignored, like <code>404</code>. | |
185 | * | |
186 | * @parameter | |
187 | * @see {@link org.apache.commons.httpclient.HttpStatus} for all defined values. | |
188 | */ | |
189 | private Integer[] excludedHttpStatusErrors; | |
190 | ||
191 | /** | |
192 | * The list of HTTP warnings to ignored, like <code>301</code>. | |
193 | * | |
194 | * @parameter | |
195 | * @see {@link org.apache.commons.httpclient.HttpStatus} for all defined values. | |
196 | */ | |
197 | private Integer[] excludedHttpStatusWarnings; | |
198 | ||
199 | /** | |
200 | * A list of pages to exclude. | |
201 | * <br/> | |
202 | * <b>Note</b>: | |
203 | * <br/> | |
204 | * <ul> | |
205 | * <li>This report, i.e. <code>linkcheck.html</code>, is always excluded.</li> | |
206 | * <li>May contain Ant-style wildcards and double wildcards, e.g. <code>apidocs/**</code>, etc.</li> | |
207 | * </ul> | |
208 | * | |
209 | * @parameter | |
210 | */ | |
211 | private String[] excludedPages; | |
212 | ||
213 | /** | |
214 | * The list of links to exclude. | |
215 | * <br/> | |
216 | * <b>Note</b>: Patterns like <code>**/dummy/*</code> are allowed for excludedLink. | |
217 | * | |
218 | * @parameter | |
219 | */ | |
220 | private String[] excludedLinks; | |
221 | ||
222 | /** | |
223 | * The file encoding to use when Linkcheck reads the source files. If the property | |
224 | * <code>project.build.sourceEncoding</code> is not set, the platform default encoding is used. | |
225 | * | |
226 | * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}" | |
227 | */ | |
228 | private String encoding; | |
229 | ||
230 | /** | |
231 | * The extra HttpClient parameters to be used when fetching links. For instance: | |
232 | * <pre> | |
233 | * <httpClientParameters> | |
234 | * <property> | |
235 | * <name>http.protocol.max-redirects</name> | |
236 | * <value>10</value> | |
237 | * </property> | |
238 | * </httpClientParameters> | |
239 | * </pre> | |
240 | * See <a href="http://hc.apache.org/httpclient-3.x/preference-api.html">HttpClient preference page</a> | |
241 | * | |
242 | * @parameter expression="${httpClientParameters}" | |
243 | */ | |
244 | private Properties httpClientParameters; | |
245 | ||
246 | /** | |
247 | * Set the timeout to be used when fetching links. A value of zero means the timeout is not used. | |
248 | * | |
249 | * @parameter expression="${timeout}" default-value="2000" | |
250 | */ | |
251 | private int timeout; | |
252 | ||
253 | /** | |
254 | * <code>true</code> to skip the report execution, <code>false</code> otherwise. | |
255 | * The purpose is to prevent infinite call when {@link #forceSite} is enable. | |
256 | * | |
257 | * @parameter expression="${linkcheck.skip}" default-value="false" | |
258 | */ | |
259 | private boolean skip; | |
260 | ||
261 | /** | |
262 | * <code>true</code> to force the site generation, <code>false</code> otherwise. | |
263 | * Using this parameter ensures that all documents have been correctly generated. | |
264 | * | |
265 | * @parameter expression="${linkcheck.forceSite}" default-value="true" | |
266 | */ | |
267 | private boolean forceSite; | |
268 | ||
269 | /** | |
270 | * The base URL to use for absolute links (eg <code>/index.html</code>) in the site. | |
271 | * | |
272 | * @parameter expression="${linkcheck.baseURL}" default-value="${project.url}" | |
273 | */ | |
274 | private String baseURL; | |
275 | ||
276 | // ---------------------------------------------------------------------- | |
277 | // Instance fields | |
278 | // ---------------------------------------------------------------------- | |
279 | ||
280 | /** Result of the linkcheck in {@link #execute()} */ | |
281 | private LinkcheckModel result; | |
282 | ||
283 | protected static final String ICON_SUCCESS = "images/icon_success_sml.gif"; | |
284 | protected static final String ICON_WARNING = "images/icon_warning_sml.gif"; | |
285 | protected static final String ICON_INFO = "images/icon_info_sml.gif"; | |
286 | protected static final String ICON_ERROR = "images/icon_error_sml.gif"; | |
287 | private static final String pluginResourcesBase = "org/apache/maven/plugin/linkcheck"; | |
288 | 0 | private static final String resourceNames[] = { ICON_SUCCESS, ICON_WARNING, ICON_INFO, ICON_ERROR }; |
289 | ||
290 | // ---------------------------------------------------------------------- | |
291 | // Public methods | |
292 | // ---------------------------------------------------------------------- | |
293 | ||
294 | /** {@inheritDoc} */ | |
295 | public String getDescription( Locale locale ) | |
296 | { | |
297 | 0 | return i18n.getString( "linkcheck-report", locale, "report.linkcheck.description" ); |
298 | } | |
299 | ||
300 | /** {@inheritDoc} */ | |
301 | public String getName( Locale locale ) | |
302 | { | |
303 | 0 | return i18n.getString( "linkcheck-report", locale, "report.linkcheck.name" ); |
304 | } | |
305 | ||
306 | /** {@inheritDoc} */ | |
307 | public String getOutputName() | |
308 | { | |
309 | 0 | return "linkcheck"; |
310 | } | |
311 | ||
312 | /** {@inheritDoc} */ | |
313 | public boolean canGenerateReport() | |
314 | { | |
315 | 0 | if ( skip ) |
316 | { | |
317 | 0 | return false; |
318 | } | |
319 | ||
320 | 0 | return true; |
321 | } | |
322 | ||
323 | /** {@inheritDoc} */ | |
324 | public void execute() | |
325 | throws MojoExecutionException | |
326 | { | |
327 | 0 | if ( !canGenerateReport() ) |
328 | { | |
329 | 0 | return; |
330 | } | |
331 | ||
332 | 0 | checkEncoding(); |
333 | ||
334 | try | |
335 | { | |
336 | 0 | result = executeLinkCheck( getBasedir() ); |
337 | } | |
338 | 0 | catch ( LinkCheckException e ) |
339 | { | |
340 | 0 | throw new MojoExecutionException( "LinkCheckException: " + e.getMessage(), e ); |
341 | 0 | } |
342 | 0 | } |
343 | ||
344 | // ---------------------------------------------------------------------- | |
345 | // Protected methods | |
346 | // ---------------------------------------------------------------------- | |
347 | ||
348 | /** {@inheritDoc} */ | |
349 | protected String getOutputDirectory() | |
350 | { | |
351 | 0 | return outputDirectory.getAbsolutePath(); |
352 | } | |
353 | ||
354 | /** {@inheritDoc} */ | |
355 | protected MavenProject getProject() | |
356 | { | |
357 | 0 | return project; |
358 | } | |
359 | ||
360 | /** {@inheritDoc} */ | |
361 | protected Renderer getSiteRenderer() | |
362 | { | |
363 | 0 | return siteRenderer; |
364 | } | |
365 | ||
366 | /** {@inheritDoc} */ | |
367 | protected void executeReport( Locale locale ) | |
368 | throws MavenReportException | |
369 | { | |
370 | 0 | if ( result == null ) |
371 | { | |
372 | 0 | getLog().debug( "Calling execute()" ); |
373 | ||
374 | try | |
375 | { | |
376 | 0 | this.execute(); |
377 | } | |
378 | 0 | catch ( MojoExecutionException e ) |
379 | { | |
380 | 0 | throw new MavenReportException( "MojoExecutionException: " + e.getMessage(), e ); |
381 | 0 | } |
382 | } | |
383 | ||
384 | 0 | if ( result != null ) |
385 | { | |
386 | 0 | generateReport( locale, result ); |
387 | // free memory | |
388 | 0 | result = null; |
389 | } | |
390 | 0 | } |
391 | ||
392 | // ---------------------------------------------------------------------- | |
393 | // Private methods | |
394 | // ---------------------------------------------------------------------- | |
395 | ||
396 | private void checkEncoding() | |
397 | { | |
398 | 0 | if ( StringUtils.isEmpty( encoding ) ) |
399 | { | |
400 | 0 | if ( getLog().isWarnEnabled() ) |
401 | { | |
402 | 0 | getLog().warn( "File encoding has not been set, using platform encoding " |
403 | + ReaderFactory.FILE_ENCODING + ", i.e. build is platform dependent!" ); | |
404 | } | |
405 | ||
406 | 0 | encoding = ReaderFactory.FILE_ENCODING; |
407 | } | |
408 | 0 | } |
409 | ||
410 | private File getBasedir() | |
411 | throws MojoExecutionException | |
412 | { | |
413 | File basedir; | |
414 | ||
415 | 0 | if ( forceSite ) |
416 | { | |
417 | 0 | basedir = new File( linkcheckOutput.getParentFile(), "tmpsite" ); |
418 | 0 | basedir.mkdirs(); |
419 | ||
420 | 0 | List documents = null; |
421 | try | |
422 | { | |
423 | 0 | documents = FileUtils.getFiles( basedir, "**/*.html", null ); |
424 | } | |
425 | 0 | catch ( IOException e ) |
426 | { | |
427 | 0 | getLog().error( "IOException: " + e.getMessage() ); |
428 | 0 | getLog().debug( e ); |
429 | 0 | } |
430 | ||
431 | // if the site was not already generated, invoke it | |
432 | 0 | if ( documents == null || ( documents != null && documents.size() == 0 ) ) |
433 | { | |
434 | 0 | getLog().info( "Invoking the maven-site-plugin to ensure that all files are generated..." ); |
435 | ||
436 | try | |
437 | { | |
438 | 0 | SiteInvoker invoker = new SiteInvoker( localRepository, getLog() ); |
439 | 0 | invoker.invokeSite( project, basedir ); |
440 | } | |
441 | 0 | catch ( IOException e ) |
442 | { | |
443 | 0 | throw new MojoExecutionException( "IOException: " + e.getMessage(), e ); |
444 | 0 | } |
445 | } | |
446 | 0 | } |
447 | else | |
448 | { | |
449 | 0 | getLog().warn( "The number of documents analyzed by Linkcheck could differ from the actual " |
450 | + "number of documents!" ); | |
451 | ||
452 | 0 | basedir = outputDirectory; |
453 | 0 | basedir.mkdirs(); |
454 | } | |
455 | ||
456 | 0 | return basedir; |
457 | } | |
458 | ||
459 | /** | |
460 | * Execute the <code>Linkcheck</code> tool. | |
461 | * | |
462 | * @param basedir not null | |
463 | * @throws LinkCheckException if any | |
464 | */ | |
465 | private LinkcheckModel executeLinkCheck( File basedir ) | |
466 | throws LinkCheckException | |
467 | { | |
468 | // Wrap linkcheck | |
469 | 0 | linkCheck.setOnline( !offline ); |
470 | 0 | linkCheck.setBasedir( basedir ); |
471 | 0 | linkCheck.setBaseURL( baseURL ); |
472 | 0 | linkCheck.setReportOutput( linkcheckOutput ); |
473 | 0 | linkCheck.setLinkCheckCache( linkcheckCache ); |
474 | 0 | linkCheck.setExcludedLinks( excludedLinks ); |
475 | 0 | linkCheck.setExcludedPages( getExcludedPages() ); |
476 | 0 | linkCheck.setExcludedHttpStatusErrors( asIntArray( excludedHttpStatusErrors ) ); |
477 | 0 | linkCheck.setExcludedHttpStatusWarnings( asIntArray( excludedHttpStatusWarnings ) ); |
478 | 0 | linkCheck.setEncoding( ( StringUtils.isNotEmpty( encoding ) ? encoding : ReaderFactory.UTF_8 ) ); |
479 | ||
480 | 0 | HttpBean bean = new HttpBean(); |
481 | 0 | bean.setMethod( httpMethod ); |
482 | 0 | bean.setFollowRedirects( httpFollowRedirect ); |
483 | 0 | bean.setTimeout( timeout ); |
484 | 0 | if ( httpClientParameters != null ) |
485 | { | |
486 | 0 | bean.setHttpClientParameters( httpClientParameters ); |
487 | } | |
488 | ||
489 | 0 | Proxy proxy = settings.getActiveProxy(); |
490 | 0 | if ( proxy != null ) |
491 | { | |
492 | 0 | bean.setProxyHost( proxy.getHost() ); |
493 | 0 | bean.setProxyPort( proxy.getPort() ); |
494 | 0 | bean.setProxyUser( proxy.getUsername() ); |
495 | 0 | bean.setProxyPassword( proxy.getPassword() ); |
496 | } | |
497 | 0 | linkCheck.setHttp( bean ); |
498 | ||
499 | 0 | return linkCheck.execute(); |
500 | } | |
501 | ||
502 | /** | |
503 | * @return the excludedPages defined by the user and also this report. | |
504 | */ | |
505 | private String[] getExcludedPages() | |
506 | { | |
507 | 0 | List pagesToExclude = |
508 | ( excludedPages != null ? new ArrayList( Arrays.asList( excludedPages ) ) : new ArrayList() ); | |
509 | ||
510 | // Exclude this report | |
511 | 0 | pagesToExclude.add( getOutputName() + ".html" ); |
512 | ||
513 | 0 | return (String[]) pagesToExclude.toArray( new String[0] ); |
514 | } | |
515 | ||
516 | // ---------------------------------------------------------------------- | |
517 | // Linkcheck report | |
518 | // ---------------------------------------------------------------------- | |
519 | ||
520 | private void generateReport( Locale locale, LinkcheckModel linkcheckModel ) | |
521 | { | |
522 | 0 | LinkcheckReportGenerator reportGenerator = new LinkcheckReportGenerator( i18n ); |
523 | ||
524 | 0 | reportGenerator.setExcludedHttpStatusErrors( excludedHttpStatusErrors ); |
525 | 0 | reportGenerator.setExcludedHttpStatusWarnings( excludedHttpStatusWarnings ); |
526 | 0 | reportGenerator.setExcludedLinks( excludedLinks ); |
527 | 0 | reportGenerator.setExcludedPages( excludedPages ); |
528 | 0 | reportGenerator.setHttpFollowRedirect( httpFollowRedirect ); |
529 | 0 | reportGenerator.setHttpMethod( httpMethod ); |
530 | 0 | reportGenerator.setOffline( offline ); |
531 | ||
532 | 0 | reportGenerator.generateReport( locale, linkcheckModel, getSink() ); |
533 | 0 | closeReport(); |
534 | ||
535 | // Copy the images | |
536 | 0 | copyStaticResources(); |
537 | 0 | } |
538 | ||
539 | private void copyStaticResources() | |
540 | { | |
541 | try | |
542 | { | |
543 | 0 | getLog().debug( "Copying static linkcheck resources." ); |
544 | 0 | for ( int i = 0; i < resourceNames.length; i++ ) |
545 | { | |
546 | 0 | URL url = this.getClass().getClassLoader().getResource( pluginResourcesBase + "/" + resourceNames[i] ); |
547 | 0 | FileUtils.copyURLToFile( url, new File( getReportOutputDirectory(), resourceNames[i] ) ); |
548 | } | |
549 | } | |
550 | 0 | catch ( IOException e ) |
551 | { | |
552 | 0 | getLog().error( "Unable to copy icons for linkcheck report." ); |
553 | 0 | getLog().debug( e ); |
554 | 0 | } |
555 | 0 | } |
556 | ||
557 | private static int[] asIntArray( Integer[] array ) | |
558 | { | |
559 | 0 | if ( array == null ) |
560 | { | |
561 | 0 | return null; |
562 | } | |
563 | ||
564 | 0 | int[] newArray = new int[array.length]; |
565 | ||
566 | 0 | for ( int i = 0; i < array.length; i++ ) |
567 | { | |
568 | 0 | newArray[i] = array[i].intValue(); |
569 | } | |
570 | ||
571 | 0 | return newArray; |
572 | } | |
573 | } |