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