1 package org.apache.maven.plugins.pdf;
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.io.InputStream;
25 import java.io.Reader;
26 import java.io.StringReader;
27 import java.io.StringWriter;
28 import java.io.Writer;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Properties;
35
36 import org.apache.commons.io.input.XmlStreamReader;
37 import org.apache.maven.artifact.Artifact;
38 import org.apache.maven.artifact.repository.ArtifactRepository;
39 import org.apache.maven.artifact.versioning.ArtifactVersion;
40 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
41 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
42 import org.apache.maven.artifact.versioning.VersionRange;
43 import org.apache.maven.doxia.Doxia;
44 import org.apache.maven.doxia.docrenderer.AbstractDocumentRenderer;
45 import org.apache.maven.doxia.docrenderer.DocumentRenderer;
46 import org.apache.maven.doxia.docrenderer.DocumentRendererContext;
47 import org.apache.maven.doxia.docrenderer.DocumentRendererException;
48 import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer;
49 import org.apache.maven.doxia.document.DocumentMeta;
50 import org.apache.maven.doxia.document.DocumentModel;
51 import org.apache.maven.doxia.document.DocumentTOCItem;
52 import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Writer;
53 import org.apache.maven.doxia.index.IndexEntry;
54 import org.apache.maven.doxia.index.IndexingSink;
55 import org.apache.maven.doxia.module.xdoc.XdocSink;
56 import org.apache.maven.doxia.parser.ParseException;
57 import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
58 import org.apache.maven.doxia.sink.Sink;
59 import org.apache.maven.doxia.sink.impl.SinkAdapter;
60 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
61 import org.apache.maven.doxia.site.decoration.DecorationModel;
62 import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader;
63 import org.apache.maven.doxia.siterenderer.Renderer;
64 import org.apache.maven.doxia.siterenderer.RendererException;
65 import org.apache.maven.doxia.siterenderer.SiteRenderingContext;
66 import org.apache.maven.doxia.tools.SiteTool;
67 import org.apache.maven.doxia.tools.SiteToolException;
68 import org.apache.maven.execution.MavenSession;
69 import org.apache.maven.plugin.AbstractMojo;
70 import org.apache.maven.plugin.MojoExecutionException;
71 import org.apache.maven.plugin.MojoFailureException;
72 import org.apache.maven.plugin.PluginManager;
73 import org.apache.maven.plugins.annotations.Component;
74 import org.apache.maven.plugins.annotations.Mojo;
75 import org.apache.maven.plugins.annotations.Parameter;
76 import org.apache.maven.project.MavenProject;
77 import org.apache.maven.project.MavenProjectBuilder;
78 import org.apache.maven.reporting.AbstractMavenReportRenderer;
79 import org.apache.maven.reporting.MavenReport;
80 import org.apache.maven.reporting.MavenReportException;
81 import org.apache.maven.reporting.exec.MavenReportExecution;
82 import org.apache.maven.reporting.exec.MavenReportExecutor;
83 import org.apache.maven.reporting.exec.MavenReportExecutorRequest;
84 import org.apache.maven.settings.Settings;
85 import org.codehaus.plexus.PlexusConstants;
86 import org.codehaus.plexus.PlexusContainer;
87 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
88 import org.codehaus.plexus.context.Context;
89 import org.codehaus.plexus.context.ContextException;
90 import org.codehaus.plexus.i18n.I18N;
91 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
92 import org.codehaus.plexus.util.FileUtils;
93 import org.codehaus.plexus.util.IOUtil;
94 import org.codehaus.plexus.util.PathTool;
95 import org.codehaus.plexus.util.ReaderFactory;
96 import org.codehaus.plexus.util.StringUtils;
97 import org.codehaus.plexus.util.WriterFactory;
98 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
99
100
101
102
103
104
105
106 @Mojo( name = "pdf", threadSafe = true )
107 public class PdfMojo
108 extends AbstractMojo implements Contextualizable
109 {
110
111
112
113
114 private static final String EOL = System.getProperty( "line.separator" );
115
116
117
118
119
120
121
122
123 @Component( hint = "fo" )
124 private PdfRenderer foRenderer;
125
126
127
128
129 @Component
130 private I18N i18n;
131
132
133
134
135 @Component( hint = "itext" )
136 private PdfRenderer itextRenderer;
137
138
139
140
141
142 @Parameter( property = "locales" )
143 private String locales;
144
145
146
147
148 @Component
149 private Renderer siteRenderer;
150
151
152
153
154 @Component
155 private SiteTool siteTool;
156
157
158
159
160
161
162 @Component( role = PluginManager.class )
163 private PluginManager pluginManager;
164
165
166
167
168
169
170 @Component
171 private Doxia doxia;
172
173
174
175
176
177
178 @Component
179 private MavenProjectBuilder mavenProjectBuilder;
180
181
182
183
184
185
186
187
188 @Parameter( defaultValue = "${project}", readonly = true, required = true )
189 private MavenProject project;
190
191
192
193
194
195
196 @Parameter( defaultValue = "${settings}", readonly = true, required = true )
197 private Settings settings;
198
199
200
201
202
203
204 @Parameter( defaultValue = "${session}", readonly = true, required = true )
205 private MavenSession session;
206
207
208
209
210 @Parameter( defaultValue = "${basedir}/src/site", required = true )
211 private File siteDirectory;
212
213
214
215
216
217
218 @Parameter( defaultValue = "${project.build.directory}/generated-site", required = true )
219 private File generatedSiteDirectory;
220
221
222
223
224 @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
225 private File outputDirectory;
226
227
228
229
230 @Parameter( defaultValue = "${project.build.directory}/pdf", required = true )
231 private File workingDirectory;
232
233
234
235
236 @Parameter( defaultValue = "src/site/pdf.xml" )
237 private File docDescriptor;
238
239
240
241
242 @Parameter( property = "implementation", defaultValue = "fo", required = true )
243 private String implementation;
244
245
246
247
248 @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
249 private ArtifactRepository localRepository;
250
251
252
253
254
255
256 @Parameter( defaultValue = "${project.remoteArtifactRepositories}" )
257 private List<ArtifactRepository> remoteRepositories;
258
259
260
261
262
263 @Parameter( property = "aggregate", defaultValue = "true" )
264 private boolean aggregate;
265
266
267
268
269 @Parameter( defaultValue = "${plugin.version}", readonly = true )
270 private String pluginVersion;
271
272
273
274
275
276
277
278
279 @Parameter( property = "includeReports", defaultValue = "true" )
280 private boolean includeReports;
281
282
283
284
285
286
287
288
289 @Parameter( property = "generateTOC", defaultValue = "start" )
290 private String generateTOC;
291
292
293
294
295
296
297
298
299
300 @Parameter( property = "validate", defaultValue = "false" )
301 private boolean validate;
302
303
304
305
306
307
308 @Parameter( defaultValue = "${reports}", required = true, readonly = true )
309 private MavenReport[] reports;
310
311
312
313
314
315
316 @Parameter( defaultValue = "${project.reporting.plugins}", readonly = true )
317 private org.apache.maven.model.ReportPlugin[] reportingPlugins;
318
319
320
321
322
323
324
325
326
327 private DocumentRenderer docRenderer;
328
329
330
331
332 private Locale defaultLocale;
333
334
335
336
337 private List<Locale> localesList;
338
339
340
341
342 private DecorationModel defaultDecorationModel;
343
344
345
346
347
348
349 private File siteDirectoryTmp;
350
351
352
353
354
355
356 private File generatedSiteDirectoryTmp;
357
358
359
360
361
362
363 private Map<Locale, List<MavenReport>> generatedMavenReports;
364
365
366
367
368 private PlexusContainer container;
369
370
371
372
373
374
375 public void execute()
376 throws MojoExecutionException, MojoFailureException
377 {
378 init();
379
380 try
381 {
382 generatePdf();
383 }
384 catch ( IOException e )
385 {
386 debugLogGeneratedModel( getDocumentModel( Locale.ENGLISH ) );
387
388 throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e );
389 }
390
391 try
392 {
393 copyGeneratedPdf();
394 }
395 catch ( IOException e )
396 {
397 throw new MojoExecutionException( "Error copying generated PDF: " + e.getMessage(), e );
398 }
399 }
400
401
402 public void contextualize( Context context )
403 throws ContextException
404 {
405 container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
406 }
407
408
409
410
411
412
413
414
415 private void init()
416 {
417 if ( "fo".equalsIgnoreCase( implementation ) )
418 {
419 this.docRenderer = foRenderer;
420 }
421 else if ( "itext".equalsIgnoreCase( implementation ) )
422 {
423 this.docRenderer = itextRenderer;
424 }
425 else
426 {
427 getLog().warn( "Invalid 'implementation' parameter: '" + implementation
428 + "', using 'fo' as default." );
429
430 this.docRenderer = foRenderer;
431 }
432
433 if ( !( "none".equalsIgnoreCase( generateTOC )
434 || "start".equalsIgnoreCase( generateTOC ) || "end".equalsIgnoreCase( generateTOC ) ) )
435 {
436 getLog().warn( "Invalid 'generateTOC' parameter: '" + generateTOC
437 + "', using 'start' as default." );
438
439 this.generateTOC = "start";
440 }
441 }
442
443
444
445
446
447
448
449
450 private void copyGeneratedPdf()
451 throws MojoExecutionException, IOException
452 {
453 boolean requireCopy = !outputDirectory.getCanonicalPath().equals( workingDirectory.getCanonicalPath() );
454
455 String outputName = getDocumentModel( getDefaultLocale() ).getOutputName().trim();
456 if ( !outputName.endsWith( ".pdf" ) )
457 {
458 outputName = outputName.concat( ".pdf" );
459 }
460
461 for ( final Locale locale : getAvailableLocales() )
462 {
463 File generatedPdfSource = new File( getLocaleDirectory( workingDirectory, locale ), outputName );
464
465 if ( !generatedPdfSource.exists() )
466 {
467 getLog().warn( "Unable to find the generated pdf: " + generatedPdfSource.getAbsolutePath() );
468 continue;
469 }
470
471 File generatedPdfDest = new File( getLocaleDirectory( outputDirectory, locale ), outputName );
472
473 if ( requireCopy )
474 {
475 FileUtils.copyFile( generatedPdfSource, generatedPdfDest );
476 generatedPdfSource.delete();
477 }
478
479 getLog().info( "pdf generated: " + generatedPdfDest );
480 }
481 }
482
483
484
485
486
487
488
489
490 private void generatePdf()
491 throws MojoExecutionException, IOException
492 {
493 Locale.setDefault( getDefaultLocale() );
494
495 for ( final Locale locale : getAvailableLocales() )
496 {
497 final File workingDir = getLocaleDirectory( workingDirectory, locale );
498
499 File siteDirectoryFile = getLocaleDirectory( getSiteDirectoryTmp(), locale );
500
501 copyResources( locale );
502
503 generateMavenReports( locale );
504
505 DocumentRendererContext context = new DocumentRendererContext();
506 context.put( "project", project );
507 context.put( "settings", settings );
508 context.put( "PathTool", new PathTool() );
509 context.put( "FileUtils", new FileUtils() );
510 context.put( "StringUtils", new StringUtils() );
511 context.put( "i18n", i18n );
512 context.put( "generateTOC", generateTOC );
513 context.put( "validate", validate );
514
515
516 for ( Map.Entry<Object, Object> entry : project.getProperties().entrySet() )
517 {
518 context.put( (String) entry.getKey(), entry.getValue() );
519 }
520
521 final DocumentModel model = aggregate ? getDocumentModel( locale ) : null;
522
523 try
524 {
525
526 ( (AbstractDocumentRenderer) docRenderer ).render( siteDirectoryFile, workingDir, model, context );
527 }
528 catch ( DocumentRendererException e )
529 {
530 throw new MojoExecutionException( "Error during document generation: " + e.getMessage(), e );
531 }
532 }
533 }
534
535
536
537
538
539
540 private File getSiteDirectoryTmp()
541 throws IOException
542 {
543 if ( this.siteDirectoryTmp == null )
544 {
545 final File tmpSiteDir = new File( workingDirectory, "site.tmp" );
546 prepareTempSiteDirectory( tmpSiteDir );
547
548 this.siteDirectoryTmp = tmpSiteDir;
549 }
550
551 return this.siteDirectoryTmp;
552 }
553
554
555
556
557
558 private File getGeneratedSiteDirectoryTmp()
559 {
560 if ( this.generatedSiteDirectoryTmp == null )
561 {
562 this.generatedSiteDirectoryTmp = new File( workingDirectory, "generated-site.tmp" );
563 }
564
565 return this.generatedSiteDirectoryTmp;
566 }
567
568
569
570
571
572
573
574
575
576
577
578 private void prepareTempSiteDirectory( final File tmpSiteDir )
579 throws IOException
580 {
581
582 tmpSiteDir.mkdirs();
583
584
585 if ( siteDirectory.exists() )
586 {
587 FileUtils.copyDirectoryStructure( siteDirectory, tmpSiteDir );
588 }
589
590
591 List<String> files =
592 FileUtils.getFileAndDirectoryNames( tmpSiteDir, FileUtils.getDefaultExcludesAsString(), null, true,
593 true, true, true );
594 for ( final String fileName : files )
595 {
596 final File file = new File( fileName );
597
598 if ( file.isDirectory() )
599 {
600 FileUtils.deleteDirectory( file );
601 }
602 else
603 {
604 file.delete();
605 }
606 }
607
608 copySiteDir( generatedSiteDirectory, tmpSiteDir );
609 }
610
611
612
613
614
615
616
617
618
619 private void copySiteDir( final File from, final File to )
620 throws IOException
621 {
622 if ( from == null || !from.exists() )
623 {
624 return;
625 }
626
627
628 for ( final Locale locale : getAvailableLocales() )
629 {
630 String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
631 List<String> siteFiles = FileUtils.getFileNames( siteDirectory, "**/*", excludes, false );
632 File siteDirectoryLocale = new File( siteDirectory, locale.getLanguage() );
633 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && siteDirectoryLocale.exists() )
634 {
635 siteFiles = FileUtils.getFileNames( siteDirectoryLocale, "**/*", excludes, false );
636 }
637
638 List<String> generatedSiteFiles = FileUtils.getFileNames( from, "**/*", excludes, false );
639 File fromLocale = new File( from, locale.getLanguage() );
640 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) && fromLocale.exists() )
641 {
642 generatedSiteFiles = FileUtils.getFileNames( fromLocale, "**/*", excludes, false );
643 }
644
645 for ( final String generatedSiteFile : generatedSiteFiles )
646 {
647 if ( siteFiles.contains( generatedSiteFile ) )
648 {
649 getLog().warn( "Generated-site already contains a file in site: " + generatedSiteFile
650 + ". Ignoring copying it!" );
651 continue;
652 }
653
654 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
655 {
656 if ( fromLocale.exists() )
657 {
658 File in = new File( fromLocale, generatedSiteFile );
659 File out = new File( new File( to, locale.getLanguage() ), generatedSiteFile );
660 out.getParentFile().mkdirs();
661 FileUtils.copyFile( in, out );
662 }
663 }
664 else
665 {
666 File in = new File( from, generatedSiteFile );
667 File out = new File( to, generatedSiteFile );
668 out.getParentFile().mkdirs();
669 FileUtils.copyFile( in, out );
670 }
671 }
672 }
673 }
674
675
676
677
678
679
680
681
682
683
684 private DocumentModel getDocumentModel( Locale locale )
685 throws MojoExecutionException
686 {
687 if ( docDescriptor.exists() )
688 {
689 DocumentModel doc = getDocumentModelFromDescriptor( locale );
690
691
692 appendGeneratedReports( doc, locale );
693
694 return doc;
695 }
696
697 DocumentModel model = new DocumentModelBuilder( project, getDefaultDecorationModel() ).getDocumentModel();
698
699 model.getMeta().setGenerator( getDefaultGenerator() );
700 model.getMeta().setLanguage( locale.getLanguage() );
701 model.getCover().setCoverType( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.type" ) );
702 model.getToc().setName( i18n.getString( "pdf-plugin", getDefaultLocale(), "toc.title" ) );
703
704 appendGeneratedReports( model, locale );
705
706 debugLogGeneratedModel( model );
707
708 return model;
709 }
710
711
712
713
714
715
716
717
718 private DocumentModel getDocumentModelFromDescriptor( Locale locale )
719 throws MojoExecutionException
720 {
721 DocumentModel model;
722
723 try
724 {
725 model = new DocumentDescriptorReader( project, getLog(),
726 locale ).readAndFilterDocumentDescriptor( docDescriptor );
727 }
728 catch ( XmlPullParserException ex )
729 {
730 throw new MojoExecutionException( "Error reading DocumentDescriptor!", ex );
731 }
732 catch ( IOException io )
733 {
734 throw new MojoExecutionException( "Error opening DocumentDescriptor!", io );
735 }
736
737 if ( model.getMeta() == null )
738 {
739 model.setMeta( new DocumentMeta() );
740 }
741
742 if ( StringUtils.isEmpty( model.getMeta().getLanguage() ) )
743 {
744 model.getMeta().setLanguage( locale.getLanguage() );
745 }
746
747 if ( StringUtils.isEmpty( model.getMeta().getGenerator() ) )
748 {
749 model.getMeta().setGenerator( getDefaultGenerator() );
750 }
751
752 return model;
753 }
754
755
756
757
758
759
760
761
762 private File getLocaleDirectory( File basedir, Locale locale )
763 {
764 if ( locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
765 {
766 return basedir;
767 }
768
769 return new File( basedir, locale.getLanguage() );
770 }
771
772
773
774
775
776 private Locale getDefaultLocale()
777 {
778 if ( this.defaultLocale == null )
779 {
780 this.defaultLocale = getAvailableLocales().get( 0 );
781 }
782
783 return this.defaultLocale;
784 }
785
786
787
788
789 private List<Locale> getAvailableLocales()
790 {
791 if ( this.localesList == null )
792 {
793 this.localesList = siteTool.getSiteLocales( locales );
794 }
795
796 return this.localesList;
797 }
798
799
800
801
802
803 private DecorationModel getDefaultDecorationModel()
804 throws MojoExecutionException
805 {
806 if ( this.defaultDecorationModel == null )
807 {
808 final Locale locale = getDefaultLocale();
809
810 final File descriptorFile = siteTool.getSiteDescriptor( siteDirectory, locale );
811 DecorationModel decoration = null;
812
813 if ( descriptorFile.exists() )
814 {
815 XmlStreamReader reader = null;
816 try
817 {
818 reader = new XmlStreamReader( descriptorFile );
819
820 String siteDescriptorContent = IOUtil.toString( reader );
821
822 reader.close();
823 reader = null;
824
825 siteDescriptorContent =
826 siteTool.getInterpolatedSiteDescriptorContent( new HashMap<String, String>( 2 ), project,
827 siteDescriptorContent );
828
829 decoration = new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) );
830 }
831 catch ( XmlPullParserException e )
832 {
833 throw new MojoExecutionException( "Error parsing site descriptor", e );
834 }
835 catch ( IOException e )
836 {
837 throw new MojoExecutionException( "Error reading site descriptor", e );
838 }
839 catch ( SiteToolException e )
840 {
841 throw new MojoExecutionException( "Error when interpoling site descriptor", e );
842 }
843 finally
844 {
845 IOUtil.close( reader );
846 }
847 }
848
849 this.defaultDecorationModel = decoration;
850 }
851
852 return this.defaultDecorationModel;
853 }
854
855
856
857
858
859
860
861
862 private void copyResources( Locale locale )
863 throws MojoExecutionException
864 {
865 final DecorationModel decorationModel = getDefaultDecorationModel();
866 if ( decorationModel == null )
867 {
868 return;
869 }
870
871 Artifact skinArtifact;
872 try
873 {
874 skinArtifact =
875 siteTool.getSkinArtifactFromRepository( localRepository, project.getRemoteArtifactRepositories(),
876 decorationModel );
877 }
878 catch ( SiteToolException e )
879 {
880 throw new MojoExecutionException( "SiteToolException: " + e.getMessage(), e );
881 }
882
883 if ( skinArtifact == null )
884 {
885 return;
886 }
887
888 if ( getLog().isDebugEnabled() )
889 {
890 getLog().debug( "Copy resources from skin artifact: '" + skinArtifact.getId() + "'..." );
891 }
892
893 try
894 {
895 final SiteRenderingContext context =
896 siteRenderer.createContextForSkin( skinArtifact, new HashMap<String, Object>( 2 ), decorationModel,
897 project.getName(), locale );
898 context.addSiteDirectory( new File( siteDirectory, locale.getLanguage() ) );
899
900 siteRenderer.copyResources( context, workingDirectory );
901 }
902 catch ( IOException e )
903 {
904 throw new MojoExecutionException( "IOException: " + e.getMessage(), e );
905 }
906 catch ( RendererException e )
907 {
908 throw new MojoExecutionException( "RendererException: " + e.getMessage(), e );
909 }
910 }
911
912
913
914
915
916
917 private String getDefaultGenerator()
918 {
919 return "Maven PDF Plugin v. " + pluginVersion + ", '" + implementation + "' implementation.";
920 }
921
922
923
924
925
926
927 private void debugLogGeneratedModel( final DocumentModel docModel )
928 {
929 if ( getLog().isDebugEnabled() && project != null )
930 {
931 final File outputDir = new File( project.getBuild().getDirectory(), "pdf" );
932
933 if ( !outputDir.exists() )
934 {
935 outputDir.mkdirs();
936 }
937
938 final File doc = FileUtils.createTempFile( "pdf", ".xml", outputDir );
939 final DocumentXpp3Writer xpp3 = new DocumentXpp3Writer();
940
941 Writer w = null;
942 try
943 {
944 w = WriterFactory.newXmlWriter( doc );
945 xpp3.write( w, docModel );
946 w.close();
947 w = null;
948 getLog().debug( "Generated a default document model: " + doc.getAbsolutePath() );
949 }
950 catch ( IOException e )
951 {
952 getLog().error( "Failed to write document model: " + e.getMessage() );
953 getLog().debug( e );
954 }
955 finally
956 {
957 IOUtil.close( w );
958 }
959 }
960 }
961
962
963
964
965
966
967
968
969
970
971 private void generateMavenReports( Locale locale )
972 throws MojoExecutionException, IOException
973 {
974 if ( !includeReports )
975 {
976 getLog().info( "Skipped report generation." );
977 return;
978 }
979
980 if ( project.getReporting() == null )
981 {
982 getLog().info( "No report was specified." );
983 return;
984 }
985
986 List<MavenReportExecution> reportExecutions = getReports();
987 for ( MavenReportExecution reportExecution : reportExecutions )
988 {
989 generateMavenReport( reportExecution, locale );
990 }
991
992
993 copySiteDir( getGeneratedSiteDirectoryTmp(), getSiteDirectoryTmp() );
994 copySiteDir( generatedSiteDirectory, getSiteDirectoryTmp() );
995 }
996
997
998
999
1000
1001
1002
1003
1004
1005
1006 private void generateMavenReport( MavenReportExecution reportExecution, Locale locale )
1007 throws IOException, MojoExecutionException
1008 {
1009 MavenReport report = reportExecution.getMavenReport();
1010
1011 String localReportName = report.getName( locale );
1012
1013 if ( !reportExecution.canGenerateReport() )
1014 {
1015 getLog().info( "Skipped \"" + localReportName + "\" report." );
1016 getLog().debug( "canGenerateReport() was false." );
1017
1018 return;
1019 }
1020
1021 if ( report.isExternalReport() )
1022 {
1023 getLog().info( "Skipped external \"" + localReportName + "\" report (not supported by pdf plugin)." );
1024 getLog().debug( "isExternalReport() was false." );
1025
1026 return;
1027 }
1028
1029 for ( final MavenReport generatedReport : getGeneratedMavenReports( locale ) )
1030 {
1031 if ( report.getName( locale ).equals( generatedReport.getName( locale ) ) )
1032 {
1033 if ( getLog().isDebugEnabled() )
1034 {
1035 getLog().debug( report.getName( locale ) + " was already generated." );
1036 }
1037 return;
1038 }
1039 }
1040
1041 File outDir = new File( getGeneratedSiteDirectoryTmp(), "xdoc" );
1042 if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1043 {
1044 outDir = new File( new File( getGeneratedSiteDirectoryTmp(), locale.getLanguage() ), "xdoc" );
1045 }
1046 outDir.mkdirs();
1047
1048 File generatedReport = new File( outDir, report.getOutputName() + ".xml" );
1049
1050 String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
1051 List<String> files =
1052 FileUtils.getFileNames( siteDirectory, "*/" + report.getOutputName() + ".*", excludes, false );
1053 if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1054 {
1055 files =
1056 FileUtils.getFileNames( new File( siteDirectory, locale.getLanguage() ), "*/"
1057 + report.getOutputName() + ".*", excludes, false );
1058 }
1059
1060 if ( files.size() != 0 )
1061 {
1062 String displayLanguage = locale.getDisplayLanguage( Locale.ENGLISH );
1063
1064 if ( getLog().isInfoEnabled() )
1065 {
1066 getLog().info(
1067 "Skipped \"" + report.getName( locale ) + "\" report, file \""
1068 + report.getOutputName() + "\" already exists for the " + displayLanguage
1069 + " version." );
1070 }
1071
1072 return;
1073 }
1074
1075 if ( getLog().isInfoEnabled() )
1076 {
1077 getLog().info( "Generating \"" + localReportName + "\" report." );
1078 }
1079
1080 StringWriter sw = new StringWriter();
1081
1082 PdfXdocSink sink = null;
1083 try
1084 {
1085 sink = new PdfXdocSink( sw );
1086 renderReportToSink( reportExecution, locale, sink );
1087 }
1088 catch ( MavenReportException e )
1089 {
1090 String goal = reportExecution.getPlugin().getArtifactId() + ':' + reportExecution.getPlugin().getVersion()
1091 + ':' + reportExecution.getGoal();
1092 throw new MojoExecutionException( "Error generating " + goal + " report", e );
1093 }
1094 finally
1095 {
1096 if ( sink != null )
1097 {
1098 sink.close();
1099 }
1100 }
1101
1102 if ( getLog().isDebugEnabled() )
1103 {
1104 getLog().debug( "Writing generated xdoc to " + generatedReport );
1105 }
1106 writeGeneratedReport( sw.toString(), generatedReport );
1107
1108
1109 if ( isValidGeneratedReportXdoc( reportExecution.getPlugin().getId() + ':' + reportExecution.getGoal(),
1110 generatedReport, localReportName ) )
1111 {
1112 getGeneratedMavenReports( locale ).add( report );
1113 }
1114 }
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124 private void renderReportToSink( MavenReportExecution reportExec, Locale locale, PdfXdocSink sink )
1125 throws MavenReportException
1126 {
1127 ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
1128 try
1129 {
1130 if ( reportExec.getClassLoader() != null )
1131 {
1132 Thread.currentThread().setContextClassLoader( reportExec.getClassLoader() );
1133 }
1134
1135 MavenReport report = reportExec.getMavenReport();
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149 report.generate( sink, locale );
1150
1151 }
1152 finally
1153 {
1154 if ( reportExec.getClassLoader() != null )
1155 {
1156 Thread.currentThread().setContextClassLoader( originalClassLoader );
1157 }
1158 }
1159 }
1160
1161
1162
1163
1164
1165
1166 private List<MavenReport> getGeneratedMavenReports( Locale locale )
1167 {
1168 if ( this.generatedMavenReports == null )
1169 {
1170 this.generatedMavenReports = new HashMap<Locale, List<MavenReport>>( 2 );
1171 }
1172
1173 if ( this.generatedMavenReports.get( locale ) == null )
1174 {
1175 this.generatedMavenReports.put( locale, new ArrayList<MavenReport>( 2 ) );
1176 }
1177
1178 return this.generatedMavenReports.get( locale );
1179 }
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197 private void appendGeneratedReports( DocumentModel model, Locale locale )
1198 {
1199 if ( !includeReports )
1200 {
1201 return;
1202 }
1203 if ( getGeneratedMavenReports( locale ).isEmpty() )
1204 {
1205 return;
1206 }
1207
1208 final DocumentTOCItem documentTOCItem = new DocumentTOCItem();
1209 documentTOCItem.setName( i18n.getString( "pdf-plugin", locale, "toc.project-info.item" ) );
1210 documentTOCItem.setRef( "/project-info" );
1211
1212 List<String> addedRef = new ArrayList<String>( 4 );
1213
1214 List<DocumentTOCItem> items = new ArrayList<DocumentTOCItem>( 4 );
1215
1216
1217 for ( final MavenReport report : getGeneratedMavenReports( locale ) )
1218 {
1219 final DocumentTOCItem reportItem = new DocumentTOCItem();
1220 reportItem.setName( report.getName( locale ) );
1221 reportItem.setRef( "/" + report.getOutputName() );
1222
1223 items.add( reportItem );
1224
1225 addedRef.add( report.getOutputName() );
1226 }
1227
1228
1229 try
1230 {
1231 if ( generatedSiteDirectory.exists() )
1232 {
1233 String excludes = getDefaultExcludesWithLocales( getAvailableLocales(), getDefaultLocale() );
1234 List<String> generatedDirs = FileUtils.getDirectoryNames( generatedSiteDirectory, "*", excludes,
1235 true );
1236 if ( !locale.getLanguage().equals( getDefaultLocale().getLanguage() ) )
1237 {
1238 generatedDirs =
1239 FileUtils.getFileNames( new File( generatedSiteDirectory, locale.getLanguage() ), "*",
1240 excludes, true );
1241 }
1242
1243 for ( final String generatedDir : generatedDirs )
1244 {
1245 List<String> generatedFiles =
1246 FileUtils.getFileNames( new File( generatedDir ), "**.*", excludes, false );
1247
1248 for ( final String generatedFile : generatedFiles )
1249 {
1250 final String ref = generatedFile.substring( 0, generatedFile.lastIndexOf( '.' ) );
1251
1252 if ( !addedRef.contains( ref ) )
1253 {
1254 final String title =
1255 getGeneratedDocumentTitle( new File( generatedDir, generatedFile ) );
1256
1257 if ( title != null )
1258 {
1259 final DocumentTOCItem reportItem = new DocumentTOCItem();
1260 reportItem.setName( title );
1261 reportItem.setRef( "/" + ref );
1262
1263 items.add( reportItem );
1264 }
1265 }
1266 }
1267 }
1268 }
1269 }
1270 catch ( IOException e )
1271 {
1272 getLog().error( "IOException: " + e.getMessage() );
1273 getLog().debug( e );
1274 }
1275
1276
1277 documentTOCItem.setItems( items );
1278 model.getToc().addItem( documentTOCItem );
1279 }
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289 private String getGeneratedDocumentTitle( final File f )
1290 throws IOException
1291 {
1292 final IndexEntry entry = new IndexEntry( "index" );
1293 final IndexingSink titleSink = new IndexingSink( entry );
1294
1295 Reader reader = null;
1296 try
1297 {
1298 reader = ReaderFactory.newXmlReader( f );
1299
1300 doxia.parse( reader, f.getParentFile().getName(), titleSink );
1301
1302 reader.close();
1303 reader = null;
1304 }
1305 catch ( ParseException e )
1306 {
1307 getLog().error( "ParseException: " + e.getMessage() );
1308 getLog().debug( e );
1309 return null;
1310 }
1311 catch ( ParserNotFoundException e )
1312 {
1313 getLog().error( "ParserNotFoundException: " + e.getMessage() );
1314 getLog().debug( e );
1315 return null;
1316 }
1317 finally
1318 {
1319 IOUtil.close( reader );
1320 }
1321
1322 return titleSink.getTitle();
1323 }
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334 private boolean isValidGeneratedReportXdoc( String fullGoal, File generatedReport, String localReportName )
1335 {
1336 SinkAdapter sinkAdapter = new SinkAdapter();
1337 Reader reader = null;
1338 try
1339 {
1340 reader = ReaderFactory.newXmlReader( generatedReport );
1341
1342 doxia.parse( reader, generatedReport.getParentFile().getName(), sinkAdapter );
1343
1344 reader.close();
1345 reader = null;
1346 }
1347 catch ( ParseException e )
1348 {
1349 StringBuilder sb = new StringBuilder( 1024 );
1350
1351 sb.append( EOL );
1352 sb.append( "Error when parsing the generated report xdoc file: " );
1353 sb.append( generatedReport.getAbsolutePath() );
1354 sb.append( EOL );
1355 sb.append( e.getMessage() );
1356 sb.append( EOL );
1357
1358 sb.append( "You could:" ).append( EOL );
1359 sb.append( " * exclude all reports using -DincludeReports=false" ).append( EOL );
1360 sb.append( " * remove the " );
1361 sb.append( fullGoal );
1362 sb.append( " from the <reporting/> part. To not affect the site generation, " );
1363 sb.append( "you could create a PDF profile." );
1364
1365 sb.append( EOL ).append( "Ignoring the \"" ).append( localReportName )
1366 .append( "\" report in the PDF." ).append( EOL );
1367
1368 getLog().error( sb.toString() );
1369 getLog().debug( e );
1370
1371 return false;
1372 }
1373 catch ( ParserNotFoundException e )
1374 {
1375 getLog().error( "ParserNotFoundException: " + e.getMessage() );
1376 getLog().debug( e );
1377
1378 return false;
1379 }
1380 catch ( IOException e )
1381 {
1382 getLog().error( "IOException: " + e.getMessage() );
1383 getLog().debug( e );
1384
1385 return false;
1386 }
1387 finally
1388 {
1389 IOUtil.close( reader );
1390 }
1391
1392 return true;
1393 }
1394
1395 protected List<MavenReportExecution> getReports()
1396 throws MojoExecutionException
1397 {
1398 if ( !isMaven3OrMore() )
1399 {
1400 getLog().error( "Report generation is not supported with Maven <= 2.x" );
1401 }
1402
1403 MavenReportExecutorRequest mavenReportExecutorRequest = new MavenReportExecutorRequest();
1404 mavenReportExecutorRequest.setLocalRepository( localRepository );
1405 mavenReportExecutorRequest.setMavenSession( session );
1406 mavenReportExecutorRequest.setProject( project );
1407 mavenReportExecutorRequest.setReportPlugins( reportingPlugins );
1408
1409 MavenReportExecutor mavenReportExecutor;
1410 try
1411 {
1412 mavenReportExecutor = (MavenReportExecutor) container.lookup( MavenReportExecutor.class.getName() );
1413 }
1414 catch ( ComponentLookupException e )
1415 {
1416 throw new MojoExecutionException( "could not get MavenReportExecutor component", e );
1417 }
1418 return mavenReportExecutor.buildMavenReports( mavenReportExecutorRequest );
1419 }
1420
1421
1422
1423
1424 protected static boolean isMaven3OrMore()
1425 {
1426 try
1427 {
1428 ArtifactVersion mavenVersion = new DefaultArtifactVersion( getMavenVersion() );
1429 return VersionRange.createFromVersionSpec( "[3.0,)" ).containsVersion( mavenVersion );
1430 }
1431 catch ( InvalidVersionSpecificationException e )
1432 {
1433 return false;
1434 }
1435
1436 }
1437
1438 protected static String getMavenVersion()
1439 {
1440
1441
1442
1443
1444 final Properties properties = new Properties();
1445
1446 InputStream in = null;
1447 try
1448 {
1449 in = MavenProject.class.getClassLoader().getResourceAsStream( "META-INF/maven/org.apache.maven/maven-core/"
1450 + "pom.properties" );
1451
1452 properties.load( in );
1453 in.close();
1454 in = null;
1455 }
1456 catch ( IOException ioe )
1457 {
1458 return "";
1459 }
1460 finally
1461 {
1462 IOUtil.close( in );
1463 }
1464
1465 return properties.getProperty( "version" ).trim();
1466 }
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483 private static void writeGeneratedReport( String content, File toFile )
1484 throws IOException
1485 {
1486 if ( StringUtils.isEmpty( content ) )
1487 {
1488 return;
1489 }
1490
1491 Writer writer = null;
1492 try
1493 {
1494 writer = WriterFactory.newXmlWriter( toFile );
1495
1496 writer.write( StringUtils.replace( content, "<table><table", "<table" ) );
1497
1498 writer.close();
1499 writer = null;
1500 }
1501 finally
1502 {
1503 IOUtil.close( writer );
1504 }
1505 }
1506
1507
1508
1509
1510
1511
1512
1513
1514 private static String getDefaultExcludesWithLocales( List<Locale> locales, Locale defaultLocale )
1515 {
1516 String excludesLocales = FileUtils.getDefaultExcludesAsString();
1517 for ( final Locale locale : locales )
1518 {
1519 if ( !locale.getLanguage().equals( defaultLocale.getLanguage() ) )
1520 {
1521 excludesLocales = excludesLocales + ",**/" + locale.getLanguage() + "/*";
1522 }
1523 }
1524
1525 return excludesLocales;
1526 }
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537 private static class PdfXdocSink
1538 extends XdocSink
1539 implements org.codehaus.doxia.sink.Sink
1540 {
1541 protected PdfXdocSink( Writer writer )
1542 {
1543 super( writer );
1544 }
1545
1546
1547 public void text( String text )
1548 {
1549
1550 super.text( StringUtils.replace( text, "\u0092", "'" ) );
1551 }
1552
1553 public void tableRow()
1554 {
1555
1556 if ( !this.tableRows )
1557 {
1558 tableRows( null, false );
1559 }
1560 super.tableRow( null );
1561 }
1562 }
1563
1564
1565
1566
1567
1568
1569 private static class ProjectInfoRenderer
1570 extends AbstractMavenReportRenderer
1571 {
1572 private final List<MavenReport> generatedReports;
1573
1574 private final I18N i18n;
1575
1576 private final Locale locale;
1577
1578 ProjectInfoRenderer( Sink sink, List<MavenReport> generatedReports, I18N i18n, Locale locale )
1579 {
1580 super( sink );
1581
1582 this.generatedReports = generatedReports;
1583 this.i18n = i18n;
1584 this.locale = locale;
1585 }
1586
1587
1588 public String getTitle()
1589 {
1590 return i18n.getString( "pdf-plugin", locale, "report.project-info.title" );
1591 }
1592
1593
1594 public void renderBody()
1595 {
1596 sink.section1();
1597 sink.sectionTitle1();
1598 sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.title" ) );
1599 sink.sectionTitle1_();
1600
1601 sink.paragraph();
1602 sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.description1" ) + " " );
1603 sink.link( "http://maven.apache.org" );
1604 sink.text( "Maven" );
1605 sink.link_();
1606 sink.text( " " + i18n.getString( "pdf-plugin", locale, "report.project-info.description2" ) );
1607 sink.paragraph_();
1608
1609 sink.section2();
1610 sink.sectionTitle2();
1611 sink.text( i18n.getString( "pdf-plugin", locale, "report.project-info.sectionTitle" ) );
1612 sink.sectionTitle2_();
1613
1614 sink.table();
1615
1616 sink.tableRows( new int[] { Sink.JUSTIFY_LEFT, Sink.JUSTIFY_LEFT }, false );
1617
1618 String name = i18n.getString( "pdf-plugin", locale, "report.project-info.column.document" );
1619 String description = i18n.getString( "pdf-plugin", locale, "report.project-info.column.description" );
1620
1621 sink.tableRow();
1622
1623 sink.tableHeaderCell( SinkEventAttributeSet.CENTER );
1624
1625 sink.text( name );
1626
1627 sink.tableHeaderCell_();
1628
1629 sink.tableHeaderCell( SinkEventAttributeSet.CENTER );
1630
1631 sink.text( description );
1632
1633 sink.tableHeaderCell_();
1634
1635 sink.tableRow_();
1636
1637 if ( generatedReports != null )
1638 {
1639 for ( final MavenReport report : generatedReports )
1640 {
1641 sink.tableRow();
1642 sink.tableCell();
1643 sink.link( report.getOutputName() + ".html" );
1644 sink.text( report.getName( locale ) );
1645 sink.link_();
1646 sink.tableCell_();
1647 sink.tableCell();
1648 sink.text( report.getDescription( locale ) );
1649 sink.tableCell_();
1650 sink.tableRow_();
1651 }
1652 }
1653
1654 sink.tableRows_();
1655
1656 sink.table_();
1657
1658 sink.section2_();
1659
1660 sink.section1_();
1661 }
1662 }
1663 }