1 package org.apache.maven.doxia.docrenderer.pdf.itext;
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.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.text.SimpleDateFormat;
31 import java.util.Collection;
32 import java.util.Date;
33 import java.util.Iterator;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.parsers.ParserConfigurationException;
42 import javax.xml.transform.OutputKeys;
43 import javax.xml.transform.Transformer;
44 import javax.xml.transform.TransformerConfigurationException;
45 import javax.xml.transform.TransformerException;
46 import javax.xml.transform.TransformerFactory;
47 import javax.xml.transform.dom.DOMSource;
48 import javax.xml.transform.stream.StreamResult;
49 import javax.xml.transform.stream.StreamSource;
50
51 import org.apache.maven.doxia.docrenderer.DocumentRendererContext;
52 import org.apache.maven.doxia.docrenderer.DocumentRendererException;
53 import org.apache.maven.doxia.docrenderer.pdf.AbstractPdfRenderer;
54 import org.apache.maven.doxia.docrenderer.pdf.PdfRenderer;
55 import org.apache.maven.doxia.document.DocumentCover;
56 import org.apache.maven.doxia.document.DocumentMeta;
57 import org.apache.maven.doxia.document.DocumentModel;
58 import org.apache.maven.doxia.document.DocumentTOCItem;
59 import org.apache.maven.doxia.module.itext.ITextSink;
60 import org.apache.maven.doxia.module.itext.ITextSinkFactory;
61 import org.apache.maven.doxia.module.itext.ITextUtil;
62 import org.apache.maven.doxia.module.site.SiteModule;
63 import org.apache.xml.utils.DefaultErrorHandler;
64 import org.codehaus.plexus.component.annotations.Component;
65 import org.codehaus.plexus.util.IOUtil;
66 import org.codehaus.plexus.util.StringUtils;
67 import org.codehaus.plexus.util.WriterFactory;
68 import org.w3c.dom.DOMException;
69 import org.w3c.dom.Document;
70 import org.w3c.dom.Node;
71 import org.xml.sax.SAXException;
72
73 import com.lowagie.text.ElementTags;
74
75
76
77
78
79
80
81
82
83 @Component( role = PdfRenderer.class, hint = "itext" )
84 public class ITextPdfRenderer
85 extends AbstractPdfRenderer
86 {
87
88 private static final String XSLT_RESOURCE = "TOC.xslt";
89
90
91 private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
92
93
94 private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
95
96
97 private static final DocumentBuilder DOCUMENT_BUILDER;
98
99 static
100 {
101 TRANSFORMER_FACTORY.setErrorListener( new DefaultErrorHandler() );
102
103 try
104 {
105 DOCUMENT_BUILDER = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
106 }
107 catch ( ParserConfigurationException e )
108 {
109 throw new RuntimeException( "Error building document :" + e.getMessage() );
110 }
111 }
112
113
114 public void generatePdf( File inputFile, File pdfFile )
115 throws DocumentRendererException
116 {
117 if ( getLogger().isDebugEnabled() )
118 {
119 getLogger().debug( "Generating : " + pdfFile );
120 }
121
122 try
123 {
124 ITextUtil.writePdf( new FileInputStream( inputFile ), new FileOutputStream( pdfFile ) );
125 }
126 catch ( IOException e )
127 {
128 throw new DocumentRendererException( "Cannot create PDF from " + inputFile + ": " + e.getMessage(), e );
129 }
130 catch ( RuntimeException e )
131 {
132 throw new DocumentRendererException( "Error creating PDF from " + inputFile + ": " + e.getMessage(), e );
133 }
134 }
135
136
137 @Override
138 public void render( Map<String, SiteModule> filesToProcess, File outputDirectory, DocumentModel documentModel )
139 throws DocumentRendererException, IOException
140 {
141 render( filesToProcess, outputDirectory, documentModel, null );
142 }
143
144
145 @Override
146 public void render( Map<String, SiteModule> filesToProcess, File outputDirectory, DocumentModel documentModel,
147 DocumentRendererContext context )
148 throws DocumentRendererException, IOException
149 {
150
151 copyResources( outputDirectory );
152
153 if ( documentModel == null )
154 {
155 getLogger().debug( "No document model, generating all documents individually." );
156
157 renderIndividual( filesToProcess, outputDirectory, context );
158 return;
159 }
160
161 String outputName = getOutputName( documentModel );
162
163 File outputITextFile = new File( outputDirectory, outputName + ".xml" );
164 if ( !outputITextFile.getParentFile().exists() )
165 {
166 outputITextFile.getParentFile().mkdirs();
167 }
168
169 File pdfOutputFile = new File( outputDirectory, outputName + ".pdf" );
170 if ( !pdfOutputFile.getParentFile().exists() )
171 {
172 pdfOutputFile.getParentFile().mkdirs();
173 }
174
175 List<File> iTextFiles;
176 if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) )
177 {
178 getLogger().info( "No TOC is defined in the document descriptor. Merging all documents." );
179
180 iTextFiles = parseAllFiles( filesToProcess, outputDirectory, context );
181 }
182 else
183 {
184 getLogger().debug( "Using TOC defined in the document descriptor." );
185
186 iTextFiles = parseTOCFiles( outputDirectory, documentModel, context );
187 }
188
189 String generateTOC =
190 ( context != null && context.get( "generateTOC" ) != null ? context.get( "generateTOC" ).toString()
191 : "start" );
192
193 File iTextFile = new File( outputDirectory, outputName + ".xml" );
194 File iTextOutput = new File( outputDirectory, outputName + "." + getOutputExtension() );
195 Document document = generateDocument( iTextFiles );
196 transform( documentModel, document, iTextFile, generateTOC );
197 generatePdf( iTextFile, iTextOutput );
198 }
199
200
201 @Override
202 public void renderIndividual( Map<String, SiteModule> filesToProcess, File outputDirectory )
203 throws DocumentRendererException, IOException
204 {
205 renderIndividual( filesToProcess, outputDirectory, null );
206 }
207
208
209 @Override
210 public void renderIndividual( Map<String, SiteModule> filesToProcess, File outputDirectory,
211 DocumentRendererContext context )
212 throws DocumentRendererException, IOException
213 {
214 for ( Map.Entry<String, SiteModule> entry : filesToProcess.entrySet() )
215 {
216 String key = entry.getKey();
217 SiteModule module = entry.getValue();
218 File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key );
219
220 String output = key;
221 String lowerCaseExtension = module.getExtension().toLowerCase( Locale.ENGLISH );
222 if ( output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) != -1 )
223 {
224 output =
225 output.substring( 0, output.toLowerCase( Locale.ENGLISH ).indexOf( "." + lowerCaseExtension ) );
226 }
227
228 File outputITextFile = new File( outputDirectory, output + ".xml" );
229 if ( !outputITextFile.getParentFile().exists() )
230 {
231 outputITextFile.getParentFile().mkdirs();
232 }
233
234 File pdfOutputFile = new File( outputDirectory, output + ".pdf" );
235 if ( !pdfOutputFile.getParentFile().exists() )
236 {
237 pdfOutputFile.getParentFile().mkdirs();
238 }
239
240 parse( fullDoc, module, outputITextFile, context );
241
242 generatePdf( outputITextFile, pdfOutputFile );
243 }
244 }
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260 private void parse( File fullDoc, SiteModule module, File iTextFile, DocumentRendererContext context )
261 throws DocumentRendererException, IOException
262 {
263 if ( getLogger().isDebugEnabled() )
264 {
265 getLogger().debug( "Parsing file " + fullDoc.getAbsolutePath() );
266 }
267
268 System.setProperty( "itext.basedir", iTextFile.getParentFile().getAbsolutePath() );
269
270 Writer writer = null;
271 ITextSink sink = null;
272 try
273 {
274 writer = WriterFactory.newXmlWriter( iTextFile );
275 sink = (ITextSink) new ITextSinkFactory().createSink( writer );
276
277 sink.setClassLoader( new URLClassLoader( new URL[] { iTextFile.getParentFile().toURI().toURL() } ) );
278
279 parse( fullDoc.getAbsolutePath(), module.getParserId(), sink, context );
280 }
281 finally
282 {
283 if ( sink != null )
284 {
285 sink.flush();
286 sink.close();
287 }
288 IOUtil.close( writer );
289 System.getProperties().remove( "itext.basedir" );
290 }
291 }
292
293
294
295
296
297
298
299
300
301 private Document generateDocument( List<File> iTextFiles )
302 throws DocumentRendererException, IOException
303 {
304 Document document = DOCUMENT_BUILDER.newDocument();
305 document.appendChild( document.createElement( ElementTags.ITEXT ) );
306
307 for ( File iTextFile : iTextFiles )
308 {
309 Document iTextDocument;
310
311 try
312 {
313 iTextDocument = DOCUMENT_BUILDER.parse( iTextFile );
314 }
315 catch ( SAXException e )
316 {
317 throw new DocumentRendererException( "SAX Error : " + e.getMessage() );
318 }
319
320
321 Node chapter = iTextDocument.getElementsByTagName( ElementTags.CHAPTER ).item( 0 );
322
323 try
324 {
325 document.getDocumentElement().appendChild( document.importNode( chapter, true ) );
326 }
327 catch ( DOMException e )
328 {
329 throw new DocumentRendererException( "Error appending chapter for "
330 + iTextFile + " : " + e.getMessage() );
331 }
332 }
333
334 return document;
335 }
336
337
338
339
340
341
342
343 private Transformer initTransformer()
344 throws DocumentRendererException
345 {
346 try
347 {
348 Transformer transformer = TRANSFORMER_FACTORY.newTransformer( new StreamSource( ITextPdfRenderer.class
349 .getResourceAsStream( XSLT_RESOURCE ) ) );
350
351 transformer.setErrorListener( TRANSFORMER_FACTORY.getErrorListener() );
352
353 transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "false" );
354
355 transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
356
357 transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
358
359 transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
360
361
362
363 return transformer;
364 }
365 catch ( TransformerConfigurationException e )
366 {
367 throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": "
368 + e.getMessage() );
369 }
370 catch ( IllegalArgumentException e )
371 {
372 throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": "
373 + e.getMessage() );
374 }
375 }
376
377
378
379
380
381
382
383
384
385 private void addTransformerParameters( Transformer transformer, DocumentModel documentModel, File iTextFile,
386 String generateTOC )
387 {
388 if ( documentModel == null )
389 {
390 return;
391 }
392
393
394 addTransformerParameter( transformer, "toc.position", generateTOC );
395
396
397 boolean hasNullMeta = false;
398 if ( documentModel.getMeta() == null )
399 {
400 hasNullMeta = true;
401 documentModel.setMeta( new DocumentMeta() );
402 }
403 addTransformerParameter( transformer, "meta.author", documentModel.getMeta().getAllAuthorNames(),
404 System.getProperty( "user.name", "null" ) );
405 addTransformerParameter( transformer, "meta.creator", documentModel.getMeta().getCreator(),
406 System.getProperty( "user.name", "null" ) );
407
408 SimpleDateFormat sdf = new SimpleDateFormat( "EEE MMM dd HH:mm:ss zzz yyyy" );
409 addTransformerParameter( transformer, "meta.creationdate", documentModel.getMeta().getCreationdate(),
410 sdf.format( new Date() ) );
411 addTransformerParameter( transformer, "meta.keywords", documentModel.getMeta().getAllKeyWords() );
412 addTransformerParameter( transformer, "meta.pagesize", documentModel.getMeta().getPageSize(),
413 ITextUtil.getPageSize( ITextUtil.getDefaultPageSize() ) );
414 addTransformerParameter( transformer, "meta.producer", documentModel.getMeta().getGenerator(),
415 "Apache Doxia iText" );
416 addTransformerParameter( transformer, "meta.subject", documentModel.getMeta().getSubject(),
417 ( documentModel.getMeta().getTitle() != null ? documentModel.getMeta().getTitle()
418 : "" ) );
419 addTransformerParameter( transformer, "meta.title", documentModel.getMeta().getTitle() );
420 if ( hasNullMeta )
421 {
422 documentModel.setMeta( null );
423 }
424
425
426 boolean hasNullCover = false;
427 if ( documentModel.getCover() == null )
428 {
429 hasNullCover = true;
430 documentModel.setCover( new DocumentCover() );
431 }
432 addTransformerParameter( transformer, "cover.author", documentModel.getCover().getAllAuthorNames(),
433 System.getProperty( "user.name", "null" ) );
434 String companyLogo = getLogoURL( documentModel.getCover().getCompanyLogo(), iTextFile.getParentFile() );
435 addTransformerParameter( transformer, "cover.companyLogo", companyLogo );
436 addTransformerParameter( transformer, "cover.companyName", documentModel.getCover().getCompanyName() );
437 if ( documentModel.getCover().getCoverdate() == null )
438 {
439 documentModel.getCover().setCoverDate( new Date() );
440 addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() );
441 documentModel.getCover().setCoverDate( null );
442 }
443 else
444 {
445 addTransformerParameter( transformer, "cover.date", documentModel.getCover().getCoverdate() );
446 }
447 addTransformerParameter( transformer, "cover.subtitle", documentModel.getCover().getCoverSubTitle() );
448 addTransformerParameter( transformer, "cover.title", documentModel.getCover().getCoverTitle() );
449 addTransformerParameter( transformer, "cover.type", documentModel.getCover().getCoverType() );
450 addTransformerParameter( transformer, "cover.version", documentModel.getCover().getCoverVersion() );
451 String projectLogo = getLogoURL( documentModel.getCover().getProjectLogo(), iTextFile.getParentFile() );
452 addTransformerParameter( transformer, "cover.projectLogo", projectLogo );
453 addTransformerParameter( transformer, "cover.projectName", documentModel.getCover().getProjectName() );
454 if ( hasNullCover )
455 {
456 documentModel.setCover( null );
457 }
458 }
459
460
461
462
463
464
465
466
467 private void addTransformerParameter( Transformer transformer, String name, String value, String defaultValue )
468 {
469 if ( StringUtils.isEmpty( value ) )
470 {
471 addTransformerParameter( transformer, name, defaultValue );
472 }
473 else
474 {
475 addTransformerParameter( transformer, name, value );
476 }
477 }
478
479
480
481
482
483
484
485 private void addTransformerParameter( Transformer transformer, String name, String value )
486 {
487 if ( StringUtils.isEmpty( value ) )
488 {
489 return;
490 }
491
492 transformer.setParameter( name, value );
493 }
494
495
496
497
498
499
500
501
502
503
504 private void transform( DocumentModel documentModel, Document document, File iTextFile, String generateTOC )
505 throws DocumentRendererException
506 {
507 Transformer transformer = initTransformer();
508
509 addTransformerParameters( transformer, documentModel, iTextFile, generateTOC );
510
511
512 Writer writer = null;
513 try
514 {
515 writer = WriterFactory.newXmlWriter( iTextFile );
516 transformer.transform( new DOMSource( document ), new StreamResult( writer ) );
517 }
518 catch ( TransformerException e )
519 {
520 throw new DocumentRendererException(
521 "Error transforming Document " + document + ": " + e.getMessage(),
522 e );
523 }
524 catch ( IOException e )
525 {
526 throw new DocumentRendererException(
527 "Error transforming Document " + document + ": " + e.getMessage(),
528 e );
529 }
530 finally
531 {
532 IOUtil.close( writer );
533 }
534 }
535
536
537
538
539
540
541
542
543
544 private List<File> parseAllFiles( Map<String, SiteModule> filesToProcess, File outputDirectory,
545 DocumentRendererContext context )
546 throws DocumentRendererException, IOException
547 {
548 List<File> iTextFiles = new LinkedList<File>();
549 for ( Map.Entry<String, SiteModule> entry : filesToProcess.entrySet() )
550 {
551 String key = entry.getKey();
552 SiteModule module = entry.getValue();
553 File fullDoc = new File( getBaseDir(), module.getSourceDirectory() + File.separator + key );
554
555 String outputITextName = key.substring( 0, key.lastIndexOf( '.') + 1 ) + "xml";
556 File outputITextFileTmp = new File( outputDirectory, outputITextName );
557 outputITextFileTmp.deleteOnExit();
558 if ( !outputITextFileTmp.getParentFile().exists() )
559 {
560 outputITextFileTmp.getParentFile().mkdirs();
561 }
562
563 iTextFiles.add( outputITextFileTmp );
564 parse( fullDoc, module, outputITextFileTmp, context );
565 }
566
567 return iTextFiles;
568 }
569
570
571
572
573
574
575
576
577
578 private List<File> parseTOCFiles( File outputDirectory, DocumentModel documentModel, DocumentRendererContext context )
579 throws DocumentRendererException, IOException
580 {
581 List<File> iTextFiles = new LinkedList<File>();
582 for ( Iterator<DocumentTOCItem> it = documentModel.getToc().getItems().iterator(); it.hasNext(); )
583 {
584 DocumentTOCItem tocItem = it.next();
585
586 if ( tocItem.getRef() == null )
587 {
588 getLogger().debug(
589 "No ref defined for the tocItem '" + tocItem.getName()
590 + "' in the document descriptor. IGNORING" );
591 continue;
592 }
593
594 String href = StringUtils.replace( tocItem.getRef(), "\\", "/" );
595 if ( href.lastIndexOf( '.') != -1 )
596 {
597 href = href.substring( 0, href.lastIndexOf( '.') );
598 }
599
600 Collection<SiteModule> modules = siteModuleManager.getSiteModules();
601 for ( SiteModule module : modules )
602 {
603 File moduleBasedir = new File( getBaseDir(), module.getSourceDirectory() );
604
605 if ( moduleBasedir.exists() )
606 {
607 String doc = href + "." + module.getExtension();
608 File source = new File( moduleBasedir, doc );
609
610
611 if ( !source.exists() )
612 {
613 if ( href.indexOf( "." + module.getExtension() ) != -1 )
614 {
615 doc = href + ".vm";
616 }
617 else
618 {
619 doc = href + "." + module.getExtension() + ".vm";
620 }
621 source = new File( moduleBasedir, doc );
622 }
623
624 if ( source.exists() )
625 {
626 String outputITextName = doc.substring( 0, doc.lastIndexOf( '.') + 1 ) + "xml";
627 File outputITextFileTmp = new File( outputDirectory, outputITextName );
628 outputITextFileTmp.deleteOnExit();
629 if ( !outputITextFileTmp.getParentFile().exists() )
630 {
631 outputITextFileTmp.getParentFile().mkdirs();
632 }
633
634 iTextFiles.add( outputITextFileTmp );
635 parse( source, module, outputITextFileTmp, context );
636 }
637 }
638 }
639 }
640
641 return iTextFiles;
642 }
643
644
645
646
647
648
649
650 private String getLogoURL( String logo, File parentFile )
651 {
652 if ( logo == null )
653 {
654 return null;
655 }
656
657 try
658 {
659 return new URL( logo ).toString();
660 }
661 catch ( MalformedURLException e )
662 {
663 try
664 {
665 File f = new File( parentFile, logo );
666 if ( !f.exists() )
667 {
668 getLogger().warn( "The logo " + f.getAbsolutePath() + " doesnt exist. IGNORING" );
669 }
670 else
671 {
672 return f.toURI().toURL().toString();
673 }
674 }
675 catch ( MalformedURLException e1 )
676 {
677 getLogger().debug( "Failed to convert to URL: " + logo, e1 );
678 }
679 }
680
681 return null;
682 }
683 }