View Javadoc

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  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>&#42;&#42;/dummy/&#42;</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      * &lt;httpClientParameters&gt;
234      * &nbsp;&lt;property&gt;
235      * &nbsp;&nbsp;&lt;name&gt;http.protocol.max-redirects&lt;/name&gt;
236      * &nbsp;&nbsp;&lt;value&gt;10&lt;/value&gt;
237      * &nbsp;&lt;/property&gt;
238      * &lt;/httpClientParameters&gt;
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     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         return i18n.getString( "linkcheck-report", locale, "report.linkcheck.description" );
298     }
299 
300     /** {@inheritDoc} */
301     public String getName( Locale locale )
302     {
303         return i18n.getString( "linkcheck-report", locale, "report.linkcheck.name" );
304     }
305 
306     /** {@inheritDoc} */
307     public String getOutputName()
308     {
309         return "linkcheck";
310     }
311 
312     /** {@inheritDoc} */
313     public boolean canGenerateReport()
314     {
315         if ( skip )
316         {
317             return false;
318         }
319 
320         return true;
321     }
322 
323     /** {@inheritDoc} */
324     public void execute()
325         throws MojoExecutionException
326     {
327         if ( !canGenerateReport() )
328         {
329             return;
330         }
331 
332         checkEncoding();
333 
334         try
335         {
336             result = executeLinkCheck( getBasedir() );
337         }
338         catch ( LinkCheckException e )
339         {
340             throw new MojoExecutionException( "LinkCheckException: " + e.getMessage(), e );
341         }
342     }
343 
344     // ----------------------------------------------------------------------
345     // Protected methods
346     // ----------------------------------------------------------------------
347 
348     /** {@inheritDoc} */
349     protected String getOutputDirectory()
350     {
351         return outputDirectory.getAbsolutePath();
352     }
353 
354     /** {@inheritDoc} */
355     protected MavenProject getProject()
356     {
357         return project;
358     }
359 
360     /** {@inheritDoc} */
361     protected Renderer getSiteRenderer()
362     {
363         return siteRenderer;
364     }
365 
366     /** {@inheritDoc} */
367     protected void executeReport( Locale locale )
368         throws MavenReportException
369     {
370         if ( result == null )
371         {
372             getLog().debug( "Calling execute()" );
373 
374             try
375             {
376                 this.execute();
377             }
378             catch ( MojoExecutionException e )
379             {
380                 throw new MavenReportException( "MojoExecutionException: " + e.getMessage(), e );
381             }
382         }
383 
384         if ( result != null )
385         {
386             generateReport( locale, result );
387             // free memory
388             result = null;
389         }
390     }
391 
392     // ----------------------------------------------------------------------
393     // Private methods
394     // ----------------------------------------------------------------------
395 
396     private void checkEncoding()
397     {
398         if ( StringUtils.isEmpty( encoding ) )
399         {
400             if ( getLog().isWarnEnabled() )
401             {
402                 getLog().warn( "File encoding has not been set, using platform encoding "
403                     + ReaderFactory.FILE_ENCODING + ", i.e. build is platform dependent!" );
404             }
405 
406             encoding = ReaderFactory.FILE_ENCODING;
407         }
408     }
409 
410     private File getBasedir()
411         throws MojoExecutionException
412     {
413         File basedir;
414 
415         if ( forceSite )
416         {
417             basedir = new File( linkcheckOutput.getParentFile(), "tmpsite" );
418             basedir.mkdirs();
419 
420             List documents = null;
421             try
422             {
423                 documents = FileUtils.getFiles( basedir, "**/*.html", null );
424             }
425             catch ( IOException e )
426             {
427                 getLog().error( "IOException: " + e.getMessage() );
428                 getLog().debug( e );
429             }
430 
431             // if the site was not already generated, invoke it
432             if ( documents == null || ( documents != null && documents.size() == 0 ) )
433             {
434                 getLog().info( "Invoking the maven-site-plugin to ensure that all files are generated..." );
435 
436                 try
437                 {
438                     SiteInvoker invoker = new SiteInvoker( localRepository, getLog() );
439                     invoker.invokeSite( project, basedir );
440                 }
441                 catch ( IOException e )
442                 {
443                     throw new MojoExecutionException( "IOException: " + e.getMessage(), e );
444                 }
445             }
446         }
447         else
448         {
449             getLog().warn( "The number of documents analyzed by Linkcheck could differ from the actual "
450                                    + "number of documents!" );
451 
452             basedir = outputDirectory;
453             basedir.mkdirs();
454         }
455 
456         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         linkCheck.setOnline( !offline );
470         linkCheck.setBasedir( basedir );
471         linkCheck.setBaseURL( baseURL );
472         linkCheck.setReportOutput( linkcheckOutput );
473         linkCheck.setLinkCheckCache( linkcheckCache );
474         linkCheck.setExcludedLinks( excludedLinks );
475         linkCheck.setExcludedPages( getExcludedPages() );
476         linkCheck.setExcludedHttpStatusErrors( asIntArray( excludedHttpStatusErrors ) );
477         linkCheck.setExcludedHttpStatusWarnings( asIntArray( excludedHttpStatusWarnings ) );
478         linkCheck.setEncoding( ( StringUtils.isNotEmpty( encoding ) ? encoding : ReaderFactory.UTF_8 ) );
479 
480         HttpBean bean = new HttpBean();
481         bean.setMethod( httpMethod );
482         bean.setFollowRedirects( httpFollowRedirect );
483         bean.setTimeout( timeout );
484         if ( httpClientParameters != null )
485         {
486             bean.setHttpClientParameters( httpClientParameters );
487         }
488 
489         Proxy proxy = settings.getActiveProxy();
490         if ( proxy != null )
491         {
492             bean.setProxyHost( proxy.getHost() );
493             bean.setProxyPort( proxy.getPort() );
494             bean.setProxyUser( proxy.getUsername() );
495             bean.setProxyPassword( proxy.getPassword() );
496         }
497         linkCheck.setHttp( bean );
498 
499         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         List pagesToExclude =
508             ( excludedPages != null ? new ArrayList( Arrays.asList( excludedPages ) ) : new ArrayList() );
509 
510         // Exclude this report
511         pagesToExclude.add( getOutputName() + ".html" );
512 
513         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         LinkcheckReportGenerator reportGenerator = new LinkcheckReportGenerator( i18n );
523 
524         reportGenerator.setExcludedHttpStatusErrors( excludedHttpStatusErrors );
525         reportGenerator.setExcludedHttpStatusWarnings( excludedHttpStatusWarnings );
526         reportGenerator.setExcludedLinks( excludedLinks );
527         reportGenerator.setExcludedPages( excludedPages );
528         reportGenerator.setHttpFollowRedirect( httpFollowRedirect );
529         reportGenerator.setHttpMethod( httpMethod );
530         reportGenerator.setOffline( offline );
531 
532         reportGenerator.generateReport( locale, linkcheckModel, getSink() );
533         closeReport();
534 
535         // Copy the images
536         copyStaticResources();
537     }
538 
539     private void copyStaticResources()
540     {
541         try
542         {
543             getLog().debug( "Copying static linkcheck resources." );
544             for ( int i = 0; i < resourceNames.length; i++ )
545             {
546                 URL url = this.getClass().getClassLoader().getResource( pluginResourcesBase + "/" + resourceNames[i] );
547                 FileUtils.copyURLToFile( url, new File( getReportOutputDirectory(), resourceNames[i] ) );
548             }
549         }
550         catch ( IOException e )
551         {
552             getLog().error( "Unable to copy icons for linkcheck report." );
553             getLog().debug( e );
554         }
555     }
556 
557     private static int[] asIntArray( Integer[] array )
558     {
559         if ( array == null )
560         {
561             return null;
562         }
563 
564         int[] newArray = new int[array.length];
565 
566         for ( int i = 0; i < array.length; i++ )
567         {
568             newArray[i] = array[i].intValue();
569         }
570 
571         return newArray;
572     }
573 }