View Javadoc
1   package org.apache.maven.doxia.docrenderer.itext;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.Reader;
25  import java.io.Writer;
26  import java.net.URL;
27  import java.net.URLClassLoader;
28  import java.util.Collection;
29  import java.util.Date;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.List;
33  
34  import javax.xml.parsers.DocumentBuilderFactory;
35  import javax.xml.parsers.ParserConfigurationException;
36  import javax.xml.transform.OutputKeys;
37  import javax.xml.transform.Transformer;
38  import javax.xml.transform.TransformerConfigurationException;
39  import javax.xml.transform.TransformerException;
40  import javax.xml.transform.TransformerFactory;
41  import javax.xml.transform.dom.DOMSource;
42  import javax.xml.transform.stream.StreamResult;
43  import javax.xml.transform.stream.StreamSource;
44  
45  import org.apache.maven.doxia.Doxia;
46  import org.apache.maven.doxia.docrenderer.DocRenderer;
47  import org.apache.maven.doxia.docrenderer.DocumentRendererException;
48  import org.apache.maven.doxia.document.DocumentModel;
49  import org.apache.maven.doxia.document.DocumentTOCItem;
50  import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Reader;
51  import org.apache.maven.doxia.module.itext.ITextSink;
52  import org.apache.maven.doxia.module.itext.ITextSinkFactory;
53  import org.apache.maven.doxia.module.itext.ITextUtil;
54  import org.apache.maven.doxia.parser.module.ParserModule;
55  import org.apache.maven.doxia.parser.module.ParserModuleManager;
56  import org.apache.maven.doxia.parser.ParseException;
57  import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
58  import org.apache.xml.utils.DefaultErrorHandler;
59  import org.codehaus.plexus.logging.AbstractLogEnabled;
60  import org.codehaus.plexus.util.FileUtils;
61  import org.codehaus.plexus.util.IOUtil;
62  import org.codehaus.plexus.util.ReaderFactory;
63  import org.codehaus.plexus.util.StringUtils;
64  import org.codehaus.plexus.util.WriterFactory;
65  import org.codehaus.plexus.util.xml.XmlUtil;
66  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
67  import org.w3c.dom.DOMException;
68  import org.w3c.dom.Document;
69  import org.w3c.dom.Node;
70  import org.xml.sax.SAXException;
71  
72  import com.lowagie.text.ElementTags;
73  
74  /**
75   * Abstract <code>document</code> render with the <code>iText</code> framework
76   *
77   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
78   * @deprecated since 1.1, use an implementation of {@link org.apache.maven.doxia.docrenderer.DocumentRenderer}.
79   */
80  public abstract class AbstractITextRender
81      extends AbstractLogEnabled
82      implements DocRenderer
83  {
84      private static final String XSLT_RESOURCE = "org/apache/maven/doxia/docrenderer/pdf/itext/TOC.xslt";
85  
86      private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
87  
88      private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
89  
90      /**
91       * @plexus.requirement
92       */
93      protected ParserModuleManager parserModuleManager;
94  
95      /**
96       * @plexus.requirement
97       */
98      protected Doxia doxia;
99  
100     static
101     {
102         TRANSFORMER_FACTORY.setErrorListener( new DefaultErrorHandler() );
103     }
104 
105     private List<String> getModuleFileNames( ParserModule module, File moduleBasedir )
106         throws IOException
107     {
108         StringBuilder includes = new StringBuilder();
109 
110         for ( String extension: module.getExtensions() )
111         {
112             if ( includes.length() > 0 )
113             {
114                 includes.append( ',' );
115             }
116             includes.append( "**/*." );
117             includes.append( extension );
118         }
119 
120         return FileUtils.getFileNames( moduleBasedir, includes.toString(), null, false );
121     }
122 
123     /** {@inheritDoc} */
124     public void render( File siteDirectory, File outputDirectory )
125         throws DocumentRendererException, IOException
126     {
127         Collection<ParserModule> modules = parserModuleManager.getParserModules();
128         for ( ParserModule module : modules )
129         {
130             File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
131 
132             if ( moduleBasedir.exists() )
133             {
134                 List<String> docs = getModuleFileNames( module, moduleBasedir );
135 
136                 for ( String doc : docs )
137                 {
138                     String fullPathDoc = new File( moduleBasedir, doc ).getPath();
139 
140                     String outputITextName = doc.substring( 0, doc.indexOf( '.' ) + 1 ) + "xml";
141                     File outputITextFile = new File( outputDirectory, outputITextName );
142                     if ( !outputITextFile.getParentFile().exists() )
143                     {
144                         outputITextFile.getParentFile().mkdirs();
145                     }
146                     String iTextOutputName = doc.substring( 0, doc.indexOf( '.' ) + 1 ) + getOutputExtension();
147                     File iTextOutputFile = new File( outputDirectory, iTextOutputName );
148                     if ( !iTextOutputFile.getParentFile().exists() )
149                     {
150                         iTextOutputFile.getParentFile().mkdirs();
151                     }
152 
153                     parse( fullPathDoc, module, outputITextFile );
154 
155                     generateOutput( outputITextFile, iTextOutputFile );
156                 }
157             }
158         }
159     }
160 
161     /** {@inheritDoc} */
162     public void render( File siteDirectory, File outputDirectory, File documentDescriptor )
163         throws DocumentRendererException, IOException
164     {
165         if ( ( documentDescriptor == null ) || ( !documentDescriptor.exists() ) )
166         {
167             if ( getLogger().isInfoEnabled() )
168             {
169                 getLogger().info( "No documentDescriptor is found. Generate all documents." );
170             }
171             render( siteDirectory, outputDirectory );
172             return;
173         }
174 
175         DocumentModel documentModel;
176         Reader reader = null;
177         try
178         {
179             reader = ReaderFactory.newXmlReader( documentDescriptor );
180             documentModel = new DocumentXpp3Reader().read( reader );
181         }
182         catch ( XmlPullParserException e )
183         {
184             throw new DocumentRendererException( "Error parsing document descriptor", e );
185         }
186         catch ( IOException e )
187         {
188             throw new DocumentRendererException( "Error reading document descriptor", e );
189         }
190         finally
191         {
192             IOUtil.close( reader );
193         }
194 
195         if ( documentModel.getOutputName() == null )
196         {
197             if ( getLogger().isInfoEnabled() )
198             {
199                 getLogger().info( "No outputName is defined in the document descriptor. Using 'generated_itext'" );
200             }
201             documentModel.setOutputName( "generated_itext" );
202         }
203 
204         if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) )
205         {
206             if ( getLogger().isInfoEnabled() )
207             {
208                 getLogger().info( "No TOC is defined in the document descriptor. Merging all documents." );
209             }
210         }
211 
212         List<File> iTextFiles = new LinkedList<File>();
213         Collection<ParserModule> modules = parserModuleManager.getParserModules();
214         for ( ParserModule module : modules )
215         {
216             File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
217 
218             if ( moduleBasedir.exists() )
219             {
220                 @SuppressWarnings ( "unchecked" )
221                 List<String> docs = getModuleFileNames( module, moduleBasedir );
222 
223                 for ( String doc : docs )
224                 {
225                     String fullPathDoc = new File( moduleBasedir, doc ).getPath();
226 
227                     String outputITextName = doc.substring( 0, doc.lastIndexOf( '.' ) + 1 ) + "xml";
228                     File outputITextFile = new File( outputDirectory, outputITextName );
229 
230                     if ( ( documentModel.getToc() == null ) || ( documentModel.getToc().getItems() == null ) )
231                     {
232                         iTextFiles.add( outputITextFile );
233 
234                         if ( !outputITextFile.getParentFile().exists() )
235                         {
236                             outputITextFile.getParentFile().mkdirs();
237                         }
238 
239                         parse( fullPathDoc, module, outputITextFile );
240                     }
241                     else
242                     {
243                         for ( Iterator<DocumentTOCItem> k = documentModel.getToc().getItems().iterator(); k.hasNext(); )
244                         {
245                             DocumentTOCItem tocItem = k.next();
246 
247                             if ( tocItem.getRef() == null )
248                             {
249                                 if ( getLogger().isInfoEnabled() )
250                                 {
251                                     getLogger().info( "No ref defined for an tocItem in the document descriptor." );
252                                 }
253                                 continue;
254                             }
255 
256                             String outTmp = StringUtils.replace( outputITextFile.getAbsolutePath(), "\\", "/" );
257                             outTmp = outTmp.substring( 0, outTmp.lastIndexOf( '.' ) );
258 
259                             String outRef = StringUtils.replace( tocItem.getRef(), "\\", "/" );
260                             if ( outRef.lastIndexOf( '.' ) != -1 )
261                             {
262                                 outRef = outRef.substring( 0, outRef.lastIndexOf( '.' ) );
263                             }
264                             else
265                             {
266                                 outRef = outRef.substring( 0, outRef.length() );
267                             }
268 
269                             if ( outTmp.indexOf( outRef ) != -1 )
270                             {
271                                 iTextFiles.add( outputITextFile );
272 
273                                 if ( !outputITextFile.getParentFile().exists() )
274                                 {
275                                     outputITextFile.getParentFile().mkdirs();
276                                 }
277 
278                                 parse( fullPathDoc, module, outputITextFile );
279                             }
280                         }
281                     }
282                 }
283             }
284         }
285 
286         File iTextFile = new File( outputDirectory, documentModel.getOutputName() + ".xml" );
287         File iTextOutput = new File( outputDirectory, documentModel.getOutputName() + "." + getOutputExtension() );
288         Document document = generateDocument( iTextFiles );
289         transform( documentModel, document, iTextFile );
290         generateOutput( iTextFile, iTextOutput );
291     }
292 
293     /**
294      * Generate an ouput file with the iText framework
295      *
296      * @param iTextFile
297      * @param iTextOutput
298      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
299      * @throws java.io.IOException if any
300      */
301     public abstract void generateOutput( File iTextFile, File iTextOutput )
302         throws DocumentRendererException, IOException;
303 
304     /**
305      * Parse a sink
306      *
307      * @param fullPathDoc
308      * @param module
309      * @param outputITextFile
310      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException
311      * @throws java.io.IOException
312      */
313     private void parse( String fullPathDoc, ParserModule module, File outputITextFile )
314         throws DocumentRendererException, IOException
315     {
316         Writer writer = WriterFactory.newXmlWriter( outputITextFile );
317         ITextSink sink = (ITextSink) new ITextSinkFactory().createSink( writer );
318 
319         sink.setClassLoader( new URLClassLoader( new URL[] { outputITextFile.getParentFile().toURI().toURL() } ) );
320 
321         Reader reader = null;
322         try
323         {
324             File f = new File( fullPathDoc );
325             if ( XmlUtil.isXml( f ) )
326             {
327                 reader = ReaderFactory.newXmlReader( f );
328             }
329             else
330             {
331                 // TODO Platform dependent?
332                 reader = ReaderFactory.newPlatformReader( f );
333             }
334 
335             System.setProperty( "itext.basedir", outputITextFile.getParentFile().getAbsolutePath() );
336 
337             doxia.parse( reader, module.getParserId(), sink );
338         }
339         catch ( ParserNotFoundException e )
340         {
341             throw new DocumentRendererException( "Error getting a parser for '"
342                     + fullPathDoc + "': " + e.getMessage() );
343         }
344         catch ( ParseException e )
345         {
346             throw new DocumentRendererException( "Error parsing '"
347                     + fullPathDoc + "': line [" + e.getLineNumber() + "] " + e.getMessage(), e );
348         }
349         finally
350         {
351             IOUtil.close( reader );
352 
353             sink.flush();
354 
355             sink.close();
356 
357             IOUtil.close( writer );
358 
359             System.getProperties().remove( "itext.basedir" );
360         }
361     }
362 
363     /**
364      * Merge all iTextFiles to a single one
365      *
366      * @param iTextFiles
367      * @return a document
368      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
369      * @throws java.io.IOException if any
370      */
371     private Document generateDocument( List<File> iTextFiles )
372         throws DocumentRendererException, IOException
373     {
374         Document document;
375         try
376         {
377             document = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().newDocument();
378         }
379         catch ( ParserConfigurationException e )
380         {
381             throw new DocumentRendererException( "Error building document :" + e.getMessage() );
382         }
383         document.appendChild( document.createElement( ElementTags.ITEXT ) ); // Used only to set a root
384 
385         for ( File iTextFile : iTextFiles )
386         {
387             Document iTextDocument;
388             try
389             {
390                 iTextDocument = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder().parse( iTextFile );
391             }
392             catch ( SAXException e )
393             {
394                 throw new DocumentRendererException( "SAX Error : " + e.getMessage() );
395             }
396             catch ( ParserConfigurationException e )
397             {
398                 throw new DocumentRendererException( "Error parsing configuration : " + e.getMessage() );
399             }
400 
401             // Only one chapter per doc
402             Node chapter = iTextDocument.getElementsByTagName( ElementTags.CHAPTER ).item( 0 );
403             try
404             {
405                 document.getDocumentElement().appendChild( document.importNode( chapter, true ) );
406             }
407             catch ( DOMException e )
408             {
409                 throw new DocumentRendererException( "Error appending chapter for "
410                         + iTextFile + " : " + e.getMessage() );
411             }
412         }
413 
414         return document;
415     }
416 
417     /**
418      * Init the transformer object
419      *
420      * @return an instanced transformer object
421      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
422      */
423     private Transformer initTransformer()
424         throws DocumentRendererException
425     {
426         try
427         {
428             Transformer transformer = TRANSFORMER_FACTORY.newTransformer( new StreamSource( DefaultPdfRenderer.class
429                 .getResourceAsStream( "/" + XSLT_RESOURCE ) ) );
430             transformer.setErrorListener( TRANSFORMER_FACTORY.getErrorListener() );
431 
432             transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "false" );
433             transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
434             transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
435             transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
436 
437             return transformer;
438         }
439         catch ( TransformerConfigurationException e )
440         {
441             throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": "
442                 + e.getMessage() );
443         }
444         catch ( IllegalArgumentException e )
445         {
446             throw new DocumentRendererException( "Error configuring Transformer for " + XSLT_RESOURCE + ": "
447                 + e.getMessage() );
448         }
449     }
450 
451     /**
452      * Add transformer parameters
453      *
454      * @param transformer
455      * @param documentModel
456      */
457     private void addTransformerParameters( Transformer transformer, DocumentModel documentModel )
458     {
459         if ( documentModel.getMeta().getTitle() != null )
460         {
461             transformer.setParameter( "title", documentModel.getMeta().getTitle() );
462         }
463         if ( documentModel.getMeta().getAuthor() != null )
464         {
465             transformer.setParameter( "author", documentModel.getMeta().getAuthor() );
466         }
467         transformer.setParameter( "creationdate", new Date().toString() );
468         if ( documentModel.getMeta().getSubject() != null )
469         {
470             transformer.setParameter( "subject", documentModel.getMeta().getSubject() );
471         }
472         if ( documentModel.getMeta().getKeywords() != null )
473         {
474             transformer.setParameter( "keywords", documentModel.getMeta().getKeywords() );
475         }
476         transformer.setParameter( "producer", "Generated with Doxia by " + System.getProperty( "user.name" ) );
477         if ( ITextUtil.isPageSizeSupported( documentModel.getMeta().getTitle() ) )
478         {
479             transformer.setParameter( "pagesize", documentModel.getMeta().getPageSize() );
480         }
481         else
482         {
483             transformer.setParameter( "pagesize", "A4" );
484         }
485 
486         transformer.setParameter( "frontPageHeader", "" );
487         if ( documentModel.getMeta().getTitle() != null )
488         {
489             transformer.setParameter( "frontPageTitle", documentModel.getMeta().getTitle() );
490         }
491         transformer.setParameter( "frontPageFooter", "Generated date " + new Date().toString() );
492     }
493 
494     /**
495      * Transform a document to an iTextFile
496      *
497      * @param documentModel
498      * @param document
499      * @param iTextFile
500      * @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any.
501      */
502     private void transform( DocumentModel documentModel, Document document, File iTextFile )
503         throws DocumentRendererException
504     {
505         Transformer transformer = initTransformer();
506 
507         addTransformerParameters( transformer, documentModel );
508 
509         try
510         {
511             transformer.transform( new DOMSource( document ), new StreamResult( iTextFile ) );
512         }
513         catch ( TransformerException e )
514         {
515             throw new DocumentRendererException( "Error transformer Document from "
516                     + document + ": " + e.getMessage() );
517         }
518     }
519 }