View Javadoc
1   /*
2    *  Licensed under the Apache License, Version 2.0 (the "License");
3    *  you may not use this file except in compliance with the License.
4    *  You may obtain a copy of the License at
5    * 
6    *       http://www.apache.org/licenses/LICENSE-2.0
7    * 
8    *  Unless required by applicable law or agreed to in writing, software
9    *  distributed under the License is distributed on an "AS IS" BASIS,
10   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   *  See the License for the specific language governing permissions and
12   *  limitations under the License.
13   */
14  
15  package org.apache.maven.plugins.linkcheck;
16  
17  import java.util.List;
18  import java.util.Locale;
19  
20  import org.apache.commons.io.FilenameUtils;
21  
22  import org.apache.maven.doxia.linkcheck.model.LinkcheckFile;
23  import org.apache.maven.doxia.linkcheck.model.LinkcheckFileResult;
24  import org.apache.maven.doxia.linkcheck.model.LinkcheckModel;
25  import org.apache.maven.doxia.sink.Sink;
26  
27  import org.codehaus.plexus.i18n.I18N;
28  import org.codehaus.plexus.util.StringUtils;
29  
30  /**
31   *
32   * @author ltheussl
33   * @since 1.1
34   */
35  public class LinkcheckReportGenerator
36  {
37      private final I18N i18n;
38  
39      private String httpMethod;
40      private boolean offline;
41      private String[] excludedLinks;
42      private Integer[] excludedHttpStatusErrors;
43      private Integer[] excludedHttpStatusWarnings;
44      private String[] excludedPages;
45      private boolean httpFollowRedirect;
46  
47      /**
48       *
49       * @param i18n not null.
50       */
51      public LinkcheckReportGenerator( I18N i18n )
52      {
53          this.i18n = i18n;
54      }
55  
56      /**
57       *
58       * @param excludedHttpStatusErrors may be null.
59       */
60      public void setExcludedHttpStatusErrors( Integer[] excludedHttpStatusErrors )
61      {
62          this.excludedHttpStatusErrors = excludedHttpStatusErrors;
63      }
64  
65      /**
66       *
67       * @param excludedHttpStatusWarnings may be null.
68       */
69      public void setExcludedHttpStatusWarnings( Integer[] excludedHttpStatusWarnings )
70      {
71          this.excludedHttpStatusWarnings = excludedHttpStatusWarnings;
72      }
73  
74      /**
75       *
76       * @param excludedLinks may be null.
77       */
78      public void setExcludedLinks( String[] excludedLinks )
79      {
80          this.excludedLinks = excludedLinks;
81      }
82  
83      /**
84       *
85       * @param excludedPages may be null.
86       */
87      public void setExcludedPages( String[] excludedPages )
88      {
89          this.excludedPages = excludedPages;
90      }
91  
92      /**
93       *
94       * @param httpFollowRedirect default is false.
95       */
96      public void setHttpFollowRedirect( boolean httpFollowRedirect )
97      {
98          this.httpFollowRedirect = httpFollowRedirect;
99      }
100 
101     /**
102      *
103      * @param httpMethod may be null.
104      */
105     public void setHttpMethod( String httpMethod )
106     {
107         this.httpMethod = httpMethod;
108     }
109 
110     /**
111      *
112      * @param offline default is false.
113      */
114     public void setOffline( boolean offline )
115     {
116         this.offline = offline;
117     }
118 
119     /**
120      * Genarate a report for the given LinkcheckModel and emit it into a Sink.
121      * <strong>Note</strong> that the Sink is flushed and closed.
122      *
123      * @param locale not null.
124      * @param linkcheckModel may be null.
125      * @param sink not null.
126      */
127     public void generateReport( Locale locale, LinkcheckModel linkcheckModel, Sink sink )
128     {
129         String name = i18n.getString( "linkcheck-report", locale, "report.linkcheck.name" );
130 
131         sink.head();
132         sink.title();
133         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.title" ) );
134         sink.title_();
135         sink.head_();
136 
137         sink.body();
138 
139         if ( linkcheckModel == null )
140         {
141             sink.section1();
142             sink.sectionTitle1();
143             sink.text( name );
144             sink.sectionTitle1_();
145 
146             sink.paragraph();
147             sink.rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.empty" ) );
148             sink.paragraph_();
149 
150             sink.section1_();
151 
152             sink.body_();
153             sink.flush();
154             sink.close();
155 
156             return;
157         }
158 
159         // Overview
160         sink.section1();
161         sink.sectionTitle1();
162         sink.text( name );
163         sink.sectionTitle1_();
164 
165         sink.paragraph();
166         sink.rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.overview" ) );
167         sink.paragraph_();
168 
169         sink.section1_();
170 
171         // Statistics
172         generateSummarySection( locale, linkcheckModel, sink );
173 
174         if ( linkcheckModel.getFiles().size() > 0 )
175         {
176             // Details
177             generateDetailsSection( locale, linkcheckModel, sink );
178         }
179 
180         sink.body_();
181         sink.flush();
182         sink.close();
183     }
184 
185     private void generateSummarySection( Locale locale, LinkcheckModel linkcheckModel, Sink sink )
186     {
187         // Calculus
188         List linkcheckFiles = linkcheckModel.getFiles();
189 
190         int totalFiles = linkcheckFiles.size();
191 
192         int totalLinks = 0;
193         int totalValidLinks = 0;
194         int totalErrorLinks = 0;
195         int totalWarningLinks = 0;
196         for (Object linkcheckFile1 : linkcheckFiles) {
197             LinkcheckFile linkcheckFile = (LinkcheckFile) linkcheckFile1;
198 
199             totalLinks += linkcheckFile.getNumberOfLinks();
200             totalValidLinks += linkcheckFile.getNumberOfLinks(LinkcheckFileResult.VALID_LEVEL);
201             totalErrorLinks += linkcheckFile.getNumberOfLinks(LinkcheckFileResult.ERROR_LEVEL);
202             totalWarningLinks += linkcheckFile.getNumberOfLinks(LinkcheckFileResult.WARNING_LEVEL);
203         }
204 
205         sink.section1();
206         sink.sectionTitle1();
207         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary" ) );
208         sink.sectionTitle1_();
209 
210         // Summary of the analysis parameters
211         sink.paragraph();
212         sink.rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.overview1" ) );
213         sink.paragraph_();
214 
215         sink.table();
216 
217         sink.tableRow();
218         sink.tableHeaderCell();
219         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.parameter" ) );
220         sink.tableHeaderCell_();
221         sink.tableHeaderCell();
222         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.value" ) );
223         sink.tableHeaderCell_();
224         sink.tableRow_();
225 
226         sink.tableRow();
227         sink.tableCell();
228         sink.rawText(
229                            i18n.getString( "linkcheck-report", locale,
230                                            "report.linkcheck.summary.table.httpFollowRedirect" ) );
231         sink.tableCell_();
232         sink.tableCell();
233         sink.text( String.valueOf( httpFollowRedirect ) );
234         sink.tableCell_();
235         sink.tableRow_();
236 
237         sink.tableRow();
238         sink.tableCell();
239         sink
240                  .rawText(
241                            i18n
242                                .getString( "linkcheck-report", locale, "report.linkcheck.summary.table.httpMethod" ) );
243         sink.tableCell_();
244         sink.tableCell();
245         if ( StringUtils.isEmpty( httpMethod ) )
246         {
247             sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.none" ) );
248         }
249         else
250         {
251             sink.text( httpMethod );
252         }
253         sink.tableCell_();
254         sink.tableRow_();
255 
256         sink.tableRow();
257         sink.tableCell();
258         sink.rawText(
259                            i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.offline" ) );
260         sink.tableCell_();
261         sink.tableCell();
262         sink.text( String.valueOf( offline ) );
263         sink.tableCell_();
264         sink.tableRow_();
265 
266         sink.tableRow();
267         sink.tableCell();
268         sink.rawText(
269                            i18n.getString( "linkcheck-report", locale,
270                                            "report.linkcheck.summary.table.excludedPages" ) );
271         sink.tableCell_();
272         sink.tableCell();
273         if ( excludedPages == null || excludedPages.length == 0 )
274         {
275             sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.none" ) );
276         }
277         else
278         {
279             sink.text( StringUtils.join( excludedPages, "," ) );
280         }
281         sink.tableCell_();
282         sink.tableRow_();
283 
284         sink.tableRow();
285         sink.tableCell();
286         sink.rawText(
287                            i18n.getString( "linkcheck-report", locale,
288                                            "report.linkcheck.summary.table.excludedLinks" ) );
289         sink.tableCell_();
290         sink.tableCell();
291         if ( excludedLinks == null || excludedLinks.length == 0 )
292         {
293             sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.none" ) );
294         }
295         else
296         {
297             sink.text( StringUtils.join( excludedLinks, "," ) );
298         }
299         sink.tableCell_();
300         sink.tableRow_();
301 
302         sink.tableRow();
303         sink.tableCell();
304         sink.rawText(
305                            i18n.getString( "linkcheck-report", locale,
306                                            "report.linkcheck.summary.table.excludedHttpStatusErrors" ) );
307         sink.tableCell_();
308         sink.tableCell();
309         if ( excludedHttpStatusErrors == null || excludedHttpStatusErrors.length == 0 )
310         {
311             sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.none" ) );
312         }
313         else
314         {
315             sink.text( toString( excludedHttpStatusErrors ) );
316         }
317         sink.tableCell_();
318         sink.tableRow_();
319 
320         sink.tableRow();
321         sink.tableCell();
322         sink.rawText(
323                            i18n.getString( "linkcheck-report", locale,
324                                            "report.linkcheck.summary.table.excludedHttpStatusWarnings" ) );
325         sink.tableCell_();
326         sink.tableCell();
327         if ( excludedHttpStatusWarnings == null || excludedHttpStatusWarnings.length == 0 )
328         {
329             sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.table.none" ) );
330         }
331         else
332         {
333             sink.text( toString( excludedHttpStatusWarnings ) );
334         }
335         sink.tableCell_();
336         sink.tableRow_();
337 
338         sink.table_();
339 
340         // Summary of the checked files
341         sink.paragraph();
342         sink.rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.summary.overview2" ) );
343         sink.paragraph_();
344 
345         sink.table();
346 
347         // Header
348         generateTableHeader( locale, false, sink );
349 
350         // Content
351         sink.tableRow();
352 
353         sink.tableCell();
354         sink.bold();
355         sink.text( totalFiles + "" );
356         sink.bold_();
357         sink.tableCell_();
358         sink.tableCell();
359         sink.bold();
360         sink.text( totalLinks + "" );
361         sink.bold_();
362         sink.tableCell_();
363         sink.tableCell();
364         sink.bold();
365         sink.text( String.valueOf( totalValidLinks ) );
366         sink.bold_();
367         sink.tableCell_();
368         sink.tableCell();
369         sink.bold();
370         sink.text( String.valueOf( totalWarningLinks ) );
371         sink.bold_();
372         sink.tableCell_();
373         sink.tableCell();
374         sink.bold();
375         sink.text( String.valueOf( totalErrorLinks ) );
376         sink.bold_();
377         sink.tableCell_();
378 
379         sink.tableRow_();
380 
381         sink.table_();
382 
383         sink.section1_();
384     }
385 
386     private void generateDetailsSection( Locale locale, LinkcheckModel linkcheckModel, Sink sink )
387     {
388         sink.section1();
389         sink.sectionTitle1();
390         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.detail" ) );
391         sink.sectionTitle1_();
392 
393         sink.paragraph();
394         sink.rawText( i18n.getString( "linkcheck-report", locale, "report.linkcheck.detail.overview" ) );
395         sink.paragraph_();
396 
397         sink.table();
398 
399         // Header
400         generateTableHeader( locale, true, sink );
401 
402         // Content
403         List linkcheckFiles = linkcheckModel.getFiles();
404         for (Object linkcheckFile1 : linkcheckFiles) {
405             LinkcheckFile linkcheckFile = (LinkcheckFile) linkcheckFile1;
406 
407             sink.tableRow();
408 
409             sink.tableCell();
410             if (linkcheckFile.getUnsuccessful() == 0) {
411                 iconValid(locale, sink);
412             } else {
413                 iconError(locale, sink);
414             }
415             sink.tableCell_();
416 
417             // tableCell( createLinkPatternedText( linkcheckFile.getRelativePath(), "./"
418             // + linkcheckFile.getRelativePath() ) );
419             sink.tableCell();
420             sink.link(linkcheckFile.getRelativePath());
421             sink.text(linkcheckFile.getRelativePath());
422             sink.link_();
423             sink.tableCell_();
424             sink.tableCell();
425             sink.text(String.valueOf(linkcheckFile.getNumberOfLinks()));
426             sink.tableCell_();
427             sink.tableCell();
428             sink.text(String.valueOf(linkcheckFile.getNumberOfLinks(LinkcheckFileResult.VALID_LEVEL)));
429             sink.tableCell_();
430             sink.tableCell();
431             sink.text(String.valueOf(linkcheckFile.getNumberOfLinks(LinkcheckFileResult.WARNING_LEVEL)));
432             sink.tableCell_();
433             sink.tableCell();
434             sink.text(String.valueOf(linkcheckFile.getNumberOfLinks(LinkcheckFileResult.ERROR_LEVEL)));
435             sink.tableCell_();
436 
437             sink.tableRow_();
438 
439             // Detail error
440             if (linkcheckFile.getUnsuccessful() != 0) {
441                 sink.tableRow();
442 
443                 sink.tableCell();
444                 sink.text("");
445                 sink.tableCell_();
446 
447                 // TODO it is due to DOXIA-78
448                 sink.rawText("<td colspan=\"5\">");
449 
450                 sink.table();
451 
452                 for (Object o : linkcheckFile.getResults()) {
453                     LinkcheckFileResult linkcheckFileResult = (LinkcheckFileResult) o;
454 
455                     if (linkcheckFileResult.getStatusLevel() == LinkcheckFileResult.VALID_LEVEL) {
456                         continue;
457                     }
458 
459                     sink.tableRow();
460 
461                     sink.tableCell();
462                     if (linkcheckFileResult.getStatusLevel() == LinkcheckFileResult.WARNING_LEVEL) {
463                         iconWarning(locale, sink);
464                     } else if (linkcheckFileResult.getStatusLevel() == LinkcheckFileResult.ERROR_LEVEL) {
465                         iconError(locale, sink);
466                     }
467                     sink.tableCell_();
468 
469                     sink.tableCell();
470                     sink.italic();
471                     if (linkcheckFileResult.getTarget().startsWith("#")) {
472                         sink.link(linkcheckFile.getRelativePath() + linkcheckFileResult.getTarget());
473                     } else if (linkcheckFileResult.getTarget().startsWith(".")) {
474                         // We need to calculate a correct absolute path here, because target is a relative path
475                         String absolutePath = FilenameUtils.getFullPath(linkcheckFile.getRelativePath())
476                                 + linkcheckFileResult.getTarget();
477                         String normalizedPath = FilenameUtils.normalize(absolutePath);
478                         if (normalizedPath == null) {
479                             normalizedPath = absolutePath;
480                         }
481                         sink.link(normalizedPath);
482                     } else {
483                         sink.link(linkcheckFileResult.getTarget());
484                     }
485                     // Show the link as it was written to make it easy for
486                     // the author to find it in the source document
487                     sink.text(linkcheckFileResult.getTarget());
488                     sink.link_();
489                     sink.text(": ");
490                     sink.text(linkcheckFileResult.getErrorMessage());
491                     sink.italic_();
492                     sink.tableCell_();
493 
494                     sink.tableRow_();
495                 }
496 
497                 sink.table_();
498 
499                 sink.tableCell_();
500 
501                 sink.tableRow_();
502             }
503         }
504 
505         sink.table_();
506 
507         sink.section1_();
508     }
509 
510     private void generateTableHeader( Locale locale, boolean detail, Sink sink )
511     {
512         sink.tableRow();
513         if ( detail )
514         {
515             sink.rawText( "<th rowspan=\"2\">" );
516             sink.text( "" );
517             sink.tableHeaderCell_();
518         }
519         sink.rawText( "<th rowspan=\"2\">" );
520         sink.text(
521                         detail ? i18n.getString( "linkcheck-report", locale,
522                                                  "report.linkcheck.detail.table.documents" )
523                                         : i18n.getString( "linkcheck-report", locale,
524                                                           "report.linkcheck.summary.table.documents" ) );
525         sink.tableHeaderCell_();
526         // TODO it is due to DOXIA-78
527         sink.rawText( "<th colspan=\"4\" align=\"center\">" );
528         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.links" ) );
529         sink.tableHeaderCell_();
530         sink.tableRow_();
531 
532         sink.tableRow();
533         sink.tableHeaderCell();
534         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.table.totalLinks" ) );
535         sink.tableHeaderCell_();
536         sink.tableHeaderCell();
537         iconValid( locale, sink );
538         sink.tableHeaderCell_();
539         sink.tableHeaderCell();
540         iconWarning( locale, sink );
541         sink.tableHeaderCell_();
542         sink.tableHeaderCell();
543         iconError( locale, sink );
544         sink.tableHeaderCell_();
545         sink.tableRow_();
546     }
547 
548     private void iconError( Locale locale, Sink sink )
549     {
550         sink.figure();
551         sink.figureCaption();
552         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.icon.error" ) );
553         sink.figureCaption_();
554         sink.figureGraphics( LinkcheckReport.ICON_ERROR );
555         sink.figure_();
556     }
557 
558     private void iconValid( Locale locale, Sink sink )
559     {
560         sink.figure();
561         sink.figureCaption();
562         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.icon.valid" ) );
563         sink.figureCaption_();
564         sink.figureGraphics( LinkcheckReport.ICON_SUCCESS );
565         sink.figure_();
566     }
567 
568     private void iconWarning( Locale locale, Sink sink )
569     {
570         sink.figure();
571         sink.figureCaption();
572         sink.text( i18n.getString( "linkcheck-report", locale, "report.linkcheck.icon.warning" ) );
573         sink.figureCaption_();
574         sink.figureGraphics( LinkcheckReport.ICON_WARNING );
575         sink.figure_();
576     }
577 
578     // ----------------------------------------------------------------------
579     // static methods
580     // ----------------------------------------------------------------------
581 
582     /**
583      * Similar to {@link Arrays#toString(int[])} in 1.5.
584      *
585      * @param a not null
586      * @return the array comma separated.
587      */
588     private static String toString( Object[] a )
589     {
590         if ( a == null || a.length == 0 )
591         {
592             return "";
593         }
594 
595         StringBuilder buf = new StringBuilder();
596         buf.append( a[0] );
597 
598         for ( int i = 1; i < a.length; i++ )
599         {
600             buf.append( ", " );
601             buf.append( a[i] );
602         }
603 
604         return buf.toString();
605     }
606 }