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