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