View Javadoc

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