View Javadoc

1   package org.apache.maven.doxia.siterenderer;
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.BufferedReader;
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.LineNumberReader;
29  import java.io.OutputStream;
30  import java.io.Reader;
31  import java.io.StringReader;
32  import java.io.StringWriter;
33  import java.io.UnsupportedEncodingException;
34  import java.io.Writer;
35  
36  import java.net.MalformedURLException;
37  import java.net.URL;
38  import java.net.URLClassLoader;
39  
40  import java.text.DateFormat;
41  import java.text.SimpleDateFormat;
42  
43  import java.util.Arrays;
44  import java.util.Collection;
45  import java.util.Collections;
46  import java.util.Date;
47  import java.util.Enumeration;
48  import java.util.Iterator;
49  import java.util.LinkedHashMap;
50  import java.util.LinkedList;
51  import java.util.List;
52  import java.util.Locale;
53  import java.util.Map;
54  import java.util.Properties;
55  import java.util.zip.ZipEntry;
56  import java.util.zip.ZipException;
57  import java.util.zip.ZipFile;
58  
59  import org.apache.maven.doxia.Doxia;
60  import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
61  import org.apache.maven.doxia.sink.render.RenderingContext;
62  import org.apache.maven.doxia.parser.ParseException;
63  import org.apache.maven.doxia.parser.Parser;
64  import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
65  import org.apache.maven.doxia.site.decoration.DecorationModel;
66  import org.apache.maven.doxia.module.site.SiteModule;
67  import org.apache.maven.doxia.module.site.manager.SiteModuleManager;
68  import org.apache.maven.doxia.module.site.manager.SiteModuleNotFoundException;
69  import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
70  import org.apache.maven.doxia.util.XmlValidator;
71  
72  import org.apache.velocity.Template;
73  import org.apache.velocity.context.Context;
74  import org.apache.velocity.tools.ToolManager;
75  
76  import org.codehaus.plexus.component.annotations.Component;
77  import org.codehaus.plexus.component.annotations.Requirement;
78  import org.codehaus.plexus.i18n.I18N;
79  import org.codehaus.plexus.logging.AbstractLogEnabled;
80  import org.codehaus.plexus.util.DirectoryScanner;
81  import org.codehaus.plexus.util.FileUtils;
82  import org.codehaus.plexus.util.IOUtil;
83  import org.codehaus.plexus.util.Os;
84  import org.codehaus.plexus.util.PathTool;
85  import org.codehaus.plexus.util.ReaderFactory;
86  import org.codehaus.plexus.util.StringUtils;
87  import org.codehaus.plexus.util.WriterFactory;
88  import org.codehaus.plexus.velocity.SiteResourceLoader;
89  import org.codehaus.plexus.velocity.VelocityComponent;
90  
91  /**
92   * <p>DefaultSiteRenderer class.</p>
93   *
94   * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
95   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
96   * @version $Id: DefaultSiteRenderer.java 1451951 2013-03-02 22:21:20Z olamy $
97   * @since 1.0
98   */
99  @Component( role = Renderer.class )
100 public class DefaultSiteRenderer
101     extends AbstractLogEnabled
102     implements Renderer
103 {
104     // ----------------------------------------------------------------------
105     // Requirements
106     // ----------------------------------------------------------------------
107 
108     @Requirement
109     private VelocityComponent velocity;
110 
111     @Requirement
112     private SiteModuleManager siteModuleManager;
113 
114     @Requirement
115     private Doxia doxia;
116 
117     @Requirement
118     private I18N i18n;
119 
120     private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
121 
122     private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
123 
124     private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
125 
126     // ----------------------------------------------------------------------
127     // Renderer implementation
128     // ----------------------------------------------------------------------
129 
130     /** {@inheritDoc} */
131     public void render( Collection<DocumentRenderer> documents, SiteRenderingContext siteRenderingContext,
132                         File outputDirectory )
133         throws RendererException, IOException
134     {
135         renderModule( documents, siteRenderingContext, outputDirectory );
136 
137         for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
138         {
139             copyResources( siteRenderingContext, new File( siteDirectory, "resources" ), outputDirectory );
140         }
141     }
142 
143     /** {@inheritDoc} */
144     public Map<String, DocumentRenderer> locateDocumentFiles( SiteRenderingContext siteRenderingContext )
145             throws IOException, RendererException
146     {
147         Map<String, DocumentRenderer> files = new LinkedHashMap<String, DocumentRenderer>();
148         Map<String, String> moduleExcludes = siteRenderingContext.getModuleExcludes();
149 
150         for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
151         {
152             if ( siteDirectory.exists() )
153             {
154                 Collection<SiteModule> modules = siteModuleManager.getSiteModules();
155                 for ( SiteModule module : modules )
156                 {
157                     File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
158 
159                     if ( moduleExcludes != null && moduleExcludes.containsKey( module.getParserId() ) )
160                     {
161                         addModuleFiles( moduleBasedir, module, moduleExcludes.get( module.getParserId() ),
162                                 files );
163                     }
164                     else
165                     {
166                         addModuleFiles( moduleBasedir, module, null, files );
167                     }
168                 }
169             }
170         }
171 
172         for ( ModuleReference module : siteRenderingContext.getModules() )
173         {
174             try
175             {
176                 if ( moduleExcludes != null && moduleExcludes.containsKey( module.getParserId() ) )
177                 {
178                     addModuleFiles( module.getBasedir(), siteModuleManager.getSiteModule( module.getParserId() ),
179                         moduleExcludes.get( module.getParserId() ), files );
180                 }
181                 else
182                 {
183                     addModuleFiles( module.getBasedir(), siteModuleManager.getSiteModule( module.getParserId() ), null,
184                             files );
185                 }
186             }
187             catch ( SiteModuleNotFoundException e )
188             {
189                 throw new RendererException( "Unable to find module: " + e.getMessage(), e );
190             }
191         }
192         return files;
193     }
194 
195     private void addModuleFiles( File moduleBasedir, SiteModule module, String excludes,
196                                  Map<String, DocumentRenderer> files )
197             throws IOException, RendererException
198     {
199         if ( moduleBasedir.exists() )
200         {
201             @SuppressWarnings ( "unchecked" )
202             List<String> allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
203 
204             String lowerCaseExtension = module.getExtension().toLowerCase( Locale.ENGLISH );
205             List<String> docs = new LinkedList<String>( allFiles );
206             // Take care of extension case
207             for ( Iterator<String> it = docs.iterator(); it.hasNext(); )
208             {
209                 String name = it.next().trim();
210 
211                 if ( !name.toLowerCase( Locale.ENGLISH ).endsWith( "." + lowerCaseExtension ) )
212                 {
213                     it.remove();
214                 }
215             }
216 
217             List<String> velocityFiles = new LinkedList<String>( allFiles );
218             // *.xml.vm
219             for ( Iterator<String> it = velocityFiles.iterator(); it.hasNext(); )
220             {
221                 String name = it.next().trim();
222 
223                 if ( !name.toLowerCase( Locale.ENGLISH ).endsWith( lowerCaseExtension + ".vm" ) )
224                 {
225                     it.remove();
226                 }
227             }
228             docs.addAll( velocityFiles );
229 
230             for ( String doc : docs )
231             {
232                 String docc = doc.trim();
233 
234                 RenderingContext context =
235                         new RenderingContext( moduleBasedir, docc, module.getParserId(), module.getExtension() );
236 
237                 // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
238                 if ( docc.toLowerCase( Locale.ENGLISH ).endsWith( ".vm" ) )
239                 {
240                     context.setAttribute( "velocity", "true" );
241                 }
242 
243                 String key = context.getOutputName();
244                 key = StringUtils.replace( key, "\\", "/" );
245 
246                 if ( files.containsKey( key ) )
247                 {
248                     DocumentRenderer renderer = files.get( key );
249 
250                     RenderingContext originalContext = renderer.getRenderingContext();
251 
252                     File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
253 
254                     throw new RendererException( "Files '" + module.getSourceDirectory() + File.separator + docc
255                         + "' clashes with existing '" + originalDoc + "'." );
256                 }
257                 // -----------------------------------------------------------------------
258                 // Handle key without case differences
259                 // -----------------------------------------------------------------------
260                 for ( Map.Entry<String, DocumentRenderer> entry : files.entrySet() )
261                 {
262                     if ( entry.getKey().equalsIgnoreCase( key ) )
263                     {
264                         RenderingContext originalContext = entry.getValue().getRenderingContext();
265 
266                         File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
267 
268                         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
269                         {
270                             throw new RendererException( "Files '" + module.getSourceDirectory() + File.separator
271                                 + docc + "' clashes with existing '" + originalDoc + "'." );
272                         }
273 
274                         if ( getLogger().isWarnEnabled() )
275                         {
276                             getLogger().warn(
277                                               "Files '" + module.getSourceDirectory() + File.separator + docc
278                                                   + "' could clashes with existing '" + originalDoc + "'." );
279                         }
280                     }
281                 }
282 
283                 files.put( key, new DoxiaDocumentRenderer( context ) );
284             }
285         }
286     }
287 
288     private void renderModule( Collection<DocumentRenderer> docs, SiteRenderingContext siteRenderingContext,
289                                File outputDirectory )
290             throws IOException, RendererException
291     {
292         for ( DocumentRenderer docRenderer : docs )
293         {
294             RenderingContext renderingContext = docRenderer.getRenderingContext();
295 
296             File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
297 
298             File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
299 
300             boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
301                 || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
302 
303             if ( modified || docRenderer.isOverwrite() )
304             {
305                 if ( !outputFile.getParentFile().exists() )
306                 {
307                     outputFile.getParentFile().mkdirs();
308                 }
309 
310                 if ( getLogger().isDebugEnabled() )
311                 {
312                     getLogger().debug( "Generating " + outputFile );
313                 }
314 
315                 Writer writer = null;
316                 try
317                 {
318                     writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
319                     docRenderer.renderDocument( writer, this, siteRenderingContext );
320                 }
321                 finally
322                 {
323                     IOUtil.close( writer );
324                 }
325             }
326             else
327             {
328                 if ( getLogger().isDebugEnabled() )
329                 {
330                     getLogger().debug( inputFile + " unchanged, not regenerating..." );
331                 }
332             }
333         }
334     }
335 
336     /** {@inheritDoc} */
337     public void renderDocument( Writer writer, RenderingContext renderingContext, SiteRenderingContext siteContext )
338             throws RendererException, FileNotFoundException, UnsupportedEncodingException
339     {
340         SiteRendererSink sink = new SiteRendererSink( renderingContext );
341 
342         File doc = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
343 
344         Reader reader = null;
345         try
346         {
347             String resource = doc.getAbsolutePath();
348 
349             Parser parser = doxia.getParser( renderingContext.getParserId() );
350 
351             // TODO: DOXIA-111: the filter used here must be checked generally.
352             if ( renderingContext.getAttribute( "velocity" ) != null )
353             {
354                 try
355                 {
356                     SiteResourceLoader.setResource( resource );
357 
358                     Context vc = createVelocityContext( sink, siteContext );
359 
360                     StringWriter sw = new StringWriter();
361 
362                     velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
363 
364                     reader = new StringReader( sw.toString() );
365                     if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
366                     {
367                         reader = validate( reader, resource );
368                     }
369                 }
370                 catch ( Exception e )
371                 {
372                     if ( getLogger().isDebugEnabled() )
373                     {
374                         getLogger().error( "Error parsing " + resource + " as a velocity template, using as text.", e );
375                     }
376                     else
377                     {
378                         getLogger().error( "Error parsing " + resource + " as a velocity template, using as text." );
379                     }
380                 }
381             }
382             else
383             {
384                 switch ( parser.getType() )
385                 {
386                     case Parser.XML_TYPE:
387                         reader = ReaderFactory.newXmlReader( doc );
388                         if ( siteContext.isValidate() )
389                         {
390                             reader = validate( reader, resource );
391                         }
392                         break;
393 
394                     case Parser.TXT_TYPE:
395                     case Parser.UNKNOWN_TYPE:
396                     default:
397                         reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
398                 }
399             }
400             sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
401 
402             if ( reader == null ) // can happen if velocity throws above
403             {
404                 throw new RendererException( "Error getting a parser for '" + doc + "'" );
405             }
406             doxia.parse( reader, renderingContext.getParserId(), sink );
407         }
408         catch ( ParserNotFoundException e )
409         {
410             throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
411         }
412         catch ( ParseException e )
413         {
414             throw new RendererException( "Error parsing '"
415                     + doc + "': line [" + e.getLineNumber() + "] " + e.getMessage(), e );
416         }
417         catch ( IOException e )
418         {
419             throw new RendererException( "IOException when processing '" + doc + "'", e );
420         }
421         finally
422         {
423             sink.flush();
424 
425             sink.close();
426 
427             IOUtil.close( reader );
428         }
429 
430         generateDocument( writer, sink, siteContext );
431     }
432 
433     private Context createVelocityContext( SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
434     {
435         ToolManager toolManager = new ToolManager( true );
436         Context context = toolManager.createContext();
437 
438         // ----------------------------------------------------------------------
439         // Data objects
440         // ----------------------------------------------------------------------
441 
442         RenderingContext renderingContext = sink.getRenderingContext();
443         context.put( "relativePath", renderingContext.getRelativePath() );
444 
445         // Add infos from document
446         context.put( "authors", sink.getAuthors() );
447 
448         context.put( "shortTitle", sink.getTitle() );
449 
450         // DOXIASITETOOLS-70: Prepend the project name to the title, if any
451         String title = "";
452         if ( siteRenderingContext.getDecoration() != null
453                 && siteRenderingContext.getDecoration().getName() != null )
454         {
455             title = siteRenderingContext.getDecoration().getName();
456         }
457         else if ( siteRenderingContext.getDefaultWindowTitle() != null )
458         {
459             title = siteRenderingContext.getDefaultWindowTitle();
460         }
461 
462         if ( title.length() > 0 )
463         {
464             title += " - ";
465         }
466         title += sink.getTitle();
467 
468         context.put( "title", title );
469 
470         context.put( "headContent", sink.getHead() );
471 
472         context.put( "bodyContent", sink.getBody() );
473 
474         context.put( "decoration", siteRenderingContext.getDecoration() );
475 
476         SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
477         if ( StringUtils.isNotEmpty( sink.getDate() ) )
478         {
479             try
480             {
481                 // we support only ISO-8601 date
482                 context.put( "dateCreation",
483                         sdf.format( new SimpleDateFormat( "yyyy-MM-dd" ).parse( sink.getDate() ) ) );
484             }
485             catch ( java.text.ParseException e )
486             {
487                 getLogger().debug( "Could not parse date: " + sink.getDate() + ", ignoring!", e );
488             }
489         }
490         context.put( "dateRevision", sdf.format( new Date() ) );
491 
492         context.put( "currentDate", new Date() );
493 
494         context.put( "publishDate", siteRenderingContext.getPublishDate() );
495 
496         Locale locale = siteRenderingContext.getLocale();
497 
498         DateFormat dateFormat = DateFormat.getDateInstance( DateFormat.DEFAULT, locale );
499 
500         if ( siteRenderingContext.getDecoration().getPublishDate() != null )
501         {
502             if ( StringUtils.isNotBlank( siteRenderingContext.getDecoration().getPublishDate().getFormat() ) )
503             {
504                 dateFormat =
505                     new SimpleDateFormat( siteRenderingContext.getDecoration().getPublishDate().getFormat(), locale );
506             }
507         }
508 
509         context.put( "dateFormat", dateFormat );
510 
511         String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
512         context.put( "currentFileName", currentFileName );
513 
514         context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
515 
516         context.put( "locale", locale );
517         context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
518 
519         InputStream inputStream = null;
520         try
521         {
522             inputStream = this.getClass().getClassLoader().getResourceAsStream( "META-INF/maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
523             if ( inputStream == null )
524             {
525                 getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
526             }
527             else
528             {
529                 Properties properties = new Properties();
530                 properties.load( inputStream );
531                 context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
532             }
533         }
534         catch( IOException e )
535         {
536             getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available in the velocityContext." );
537         }
538         finally
539         {
540             IOUtil.close( inputStream );
541         }
542 
543         // Add user properties
544         Map<String, ?> templateProperties = siteRenderingContext.getTemplateProperties();
545 
546         if ( templateProperties != null )
547         {
548             for ( Map.Entry<String, ?> entry : templateProperties.entrySet() )
549             {
550                 context.put( entry.getKey(), entry.getValue() );
551             }
552         }
553 
554         // ----------------------------------------------------------------------
555         // Tools
556         // ----------------------------------------------------------------------
557 
558         context.put( "PathTool", new PathTool() );
559 
560         context.put( "FileUtils", new FileUtils() );
561 
562         context.put( "StringUtils", new StringUtils() );
563 
564         context.put( "i18n", i18n );
565 
566         return context;
567     }
568 
569     /** {@inheritDoc} */
570     public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
571             throws RendererException
572     {
573         Context context = createVelocityContext( sink, siteRenderingContext );
574 
575         writeTemplate( writer, context, siteRenderingContext );
576     }
577 
578     private void writeTemplate( Writer writer, Context context, SiteRenderingContext siteContext )
579             throws RendererException
580     {
581         ClassLoader old = null;
582 
583         if ( siteContext.getTemplateClassLoader() != null )
584         {
585             // -------------------------------------------------------------------------
586             // If no template classloader was set we'll just use the context classloader
587             // -------------------------------------------------------------------------
588 
589             old = Thread.currentThread().getContextClassLoader();
590 
591             Thread.currentThread().setContextClassLoader( siteContext.getTemplateClassLoader() );
592         }
593 
594         try
595         {
596             processTemplate( siteContext.getTemplateName(), context, writer );
597         }
598         finally
599         {
600             IOUtil.close( writer );
601 
602             if ( old != null )
603             {
604                 Thread.currentThread().setContextClassLoader( old );
605             }
606         }
607     }
608 
609     /**
610      * @noinspection OverlyBroadCatchBlock,UnusedCatchParameter
611      */
612     private void processTemplate( String templateName, Context context, Writer writer )
613             throws RendererException
614     {
615         Template template;
616 
617         try
618         {
619             template = velocity.getEngine().getTemplate( templateName );
620         }
621         catch ( Exception e )
622         {
623             throw new RendererException( "Could not find the template '" + templateName, e );
624         }
625 
626         try
627         {
628             template.merge( context, writer );
629         }
630         catch ( Exception e )
631         {
632             throw new RendererException( "Error while generating code.", e );
633         }
634     }
635 
636     /** {@inheritDoc} */
637     public SiteRenderingContext createContextForSkin( File skinFile, Map<String, ?> attributes, DecorationModel decoration,
638                                                       String defaultWindowTitle, Locale locale )
639             throws IOException
640     {
641         SiteRenderingContext context = new SiteRenderingContext();
642 
643         ZipFile zipFile = getZipFile( skinFile );
644 
645         try
646         {
647             if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
648             {
649                 context.setTemplateName( SKIN_TEMPLATE_LOCATION );
650                 context.setTemplateClassLoader( new URLClassLoader( new URL[]{skinFile.toURI().toURL()} ) );
651             }
652             else
653             {
654                 context.setTemplateName( DEFAULT_TEMPLATE );
655                 context.setTemplateClassLoader( getClass().getClassLoader() );
656                 context.setUsingDefaultTemplate( true );
657             }
658         }
659         finally
660         {
661             closeZipFile( zipFile );
662         }
663 
664         context.setTemplateProperties( attributes );
665         context.setLocale( locale );
666         context.setDecoration( decoration );
667         context.setDefaultWindowTitle( defaultWindowTitle );
668         context.setSkinJarFile( skinFile );
669 
670         return context;
671     }
672 
673     private static ZipFile getZipFile( File file )
674         throws IOException
675     {
676         if ( file == null )
677         {
678             throw new IOException( "Error opening ZipFile: null" );
679         }
680 
681         try
682         {
683             // TODO: plexus-archiver, if it could do the excludes
684             return new ZipFile( file );
685         }
686         catch ( ZipException ex )
687         {
688             IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
689             ioe.initCause( ex );
690             throw ioe;
691         }
692     }
693 
694     /** {@inheritDoc} */
695     public SiteRenderingContext createContextForTemplate( File templateFile, File skinFile, Map<String, ?> attributes,
696                                                           DecorationModel decoration, String defaultWindowTitle,
697                                                           Locale locale )
698             throws MalformedURLException
699     {
700         SiteRenderingContext context = new SiteRenderingContext();
701 
702         context.setTemplateName( templateFile.getName() );
703         context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
704 
705         context.setTemplateProperties( attributes );
706         context.setLocale( locale );
707         context.setDecoration( decoration );
708         context.setDefaultWindowTitle( defaultWindowTitle );
709         context.setSkinJarFile( skinFile );
710 
711         return context;
712     }
713 
714     private static void closeZipFile( ZipFile zipFile )
715     {
716         // TODO: move to plexus utils
717         try
718         {
719             zipFile.close();
720         }
721         catch ( IOException e )
722         {
723             // ignore
724         }
725     }
726 
727     /** {@inheritDoc} */
728     public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory, File outputDirectory )
729             throws IOException
730     {
731         if ( siteRenderingContext.getSkinJarFile() != null )
732         {
733             ZipFile file = getZipFile( siteRenderingContext.getSkinJarFile() );
734 
735             try
736             {
737                 for ( Enumeration<? extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
738                 {
739                     ZipEntry entry = e.nextElement();
740 
741                     if ( !entry.getName().startsWith( "META-INF/" ) )
742                     {
743                         File destFile = new File( outputDirectory, entry.getName() );
744                         if ( !entry.isDirectory() )
745                         {
746                             destFile.getParentFile().mkdirs();
747 
748                             copyFileFromZip( file, entry, destFile );
749                         }
750                         else
751                         {
752                             destFile.mkdirs();
753                         }
754                     }
755                 }
756             }
757             finally
758             {
759                 closeZipFile( file );
760             }
761         }
762 
763         if ( siteRenderingContext.isUsingDefaultTemplate() )
764         {
765             InputStream resourceList = getClass().getClassLoader()
766                     .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
767 
768             if ( resourceList != null )
769             {
770                 Reader r = null;
771                 LineNumberReader reader = null;
772                 try
773                 {
774                     r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
775                     reader = new LineNumberReader( r );
776 
777                     String line = reader.readLine();
778 
779                     while ( line != null )
780                     {
781                         InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
782 
783                         if ( is == null )
784                         {
785                             throw new IOException( "The resource " + line + " doesn't exist." );
786                         }
787 
788                         File outputFile = new File( outputDirectory, line );
789 
790                         if ( !outputFile.getParentFile().exists() )
791                         {
792                             outputFile.getParentFile().mkdirs();
793                         }
794 
795                         OutputStream os = null;
796                         try
797                         {
798                             // for the images
799                             os = new FileOutputStream( outputFile );
800                             IOUtil.copy( is, os );
801                         }
802                         finally
803                         {
804                             IOUtil.close( os );
805                         }
806 
807                         IOUtil.close( is );
808 
809                         line = reader.readLine();
810                     }
811                 }
812                 finally
813                 {
814                     IOUtil.close( reader );
815                     IOUtil.close( r );
816                 }
817             }
818         }
819 
820         // Copy extra site resources
821         if ( resourcesDirectory != null && resourcesDirectory.exists() )
822         {
823             copyDirectory( resourcesDirectory, outputDirectory );
824         }
825 
826         // Check for the existence of /css/site.css
827         File siteCssFile = new File( outputDirectory, "/css/site.css" );
828         if ( !siteCssFile.exists() )
829         {
830             // Create the subdirectory css if it doesn't exist, DOXIA-151
831             File cssDirectory = new File( outputDirectory, "/css/" );
832             boolean created = cssDirectory.mkdirs();
833             if ( created && getLogger().isDebugEnabled() )
834             {
835                 getLogger().debug(
836                     "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
837             }
838 
839             // If the file is not there - create an empty file, DOXIA-86
840             if ( getLogger().isDebugEnabled() )
841             {
842                 getLogger().debug(
843                     "The file '" + siteCssFile.getAbsolutePath() + "' does not exists. Creating an empty file." );
844             }
845             Writer writer = null;
846             try
847             {
848                 writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
849                 //DOXIA-290...the file should not be 0 bytes.
850                 writer.write( "/* You can override this file with your own styles */"  );
851             }
852             finally
853             {
854                 IOUtil.close( writer );
855             }
856         }
857     }
858 
859     private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
860             throws IOException
861     {
862         FileOutputStream fos = new FileOutputStream( destFile );
863 
864         try
865         {
866             IOUtil.copy( file.getInputStream( entry ), fos );
867         }
868         finally
869         {
870             IOUtil.close( fos );
871         }
872     }
873 
874     /**
875      * Copy the directory
876      *
877      * @param source      source file to be copied
878      * @param destination destination file
879      * @throws java.io.IOException if any
880      */
881     protected void copyDirectory( File source, File destination )
882             throws IOException
883     {
884         if ( source.exists() )
885         {
886             DirectoryScanner scanner = new DirectoryScanner();
887 
888             String[] includedResources = {"**/**"};
889 
890             scanner.setIncludes( includedResources );
891 
892             scanner.addDefaultExcludes();
893 
894             scanner.setBasedir( source );
895 
896             scanner.scan();
897 
898             List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
899 
900             for ( String name : includedFiles )
901             {
902                 File sourceFile = new File( source, name );
903 
904                 File destinationFile = new File( destination, name );
905 
906                 FileUtils.copyFile( sourceFile, destinationFile );
907             }
908         }
909     }
910 
911     private Reader validate( Reader source, String resource )
912             throws ParseException, IOException
913     {
914         getLogger().debug( "Validating: " + resource );
915 
916         try
917         {
918             String content = IOUtil.toString( new BufferedReader( source ) );
919 
920             new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
921 
922             return new StringReader( content );
923         }
924         finally
925         {
926             IOUtil.close( source );
927         }
928     }
929 
930 }