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