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.i18n.I18N;
77  import org.codehaus.plexus.logging.AbstractLogEnabled;
78  import org.codehaus.plexus.util.DirectoryScanner;
79  import org.codehaus.plexus.util.FileUtils;
80  import org.codehaus.plexus.util.IOUtil;
81  import org.codehaus.plexus.util.Os;
82  import org.codehaus.plexus.util.PathTool;
83  import org.codehaus.plexus.util.ReaderFactory;
84  import org.codehaus.plexus.util.StringUtils;
85  import org.codehaus.plexus.util.WriterFactory;
86  import org.codehaus.plexus.velocity.SiteResourceLoader;
87  import org.codehaus.plexus.velocity.VelocityComponent;
88  
89  /**
90   * <p>DefaultSiteRenderer class.</p>
91   *
92   * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
93   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
94   * @version $Id: DefaultSiteRenderer.java 1310722 2012-04-07 10:22:18Z dennisl $
95   * @since 1.0
96   * @plexus.component role-hint="default"
97   */
98  public class DefaultSiteRenderer
99      extends AbstractLogEnabled
100     implements Renderer
101 {
102     // ----------------------------------------------------------------------
103     // Requirements
104     // ----------------------------------------------------------------------
105 
106     /** @plexus.requirement */
107     private VelocityComponent velocity;
108 
109     /**
110      * @plexus.requirement
111      */
112     private SiteModuleManager siteModuleManager;
113 
114     /** @plexus.requirement */
115     private Doxia doxia;
116 
117     /** @plexus.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             doxia.parse( reader, renderingContext.getParserId(), sink );
402         }
403         catch ( ParserNotFoundException e )
404         {
405             throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
406         }
407         catch ( ParseException e )
408         {
409             throw new RendererException( "Error parsing '"
410                     + doc + "': line [" + e.getLineNumber() + "] " + e.getMessage(), e );
411         }
412         catch ( IOException e )
413         {
414             throw new RendererException( "IOException when processing '" + doc + "'", e );
415         }
416         finally
417         {
418             sink.flush();
419 
420             sink.close();
421 
422             IOUtil.close( reader );
423         }
424 
425         generateDocument( writer, sink, siteContext );
426     }
427 
428     private Context createVelocityContext( SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
429     {
430         ToolManager toolManager = new ToolManager( true );
431         Context context = toolManager.createContext();
432 
433         // ----------------------------------------------------------------------
434         // Data objects
435         // ----------------------------------------------------------------------
436 
437         RenderingContext renderingContext = sink.getRenderingContext();
438         context.put( "relativePath", renderingContext.getRelativePath() );
439 
440         // Add infos from document
441         context.put( "authors", sink.getAuthors() );
442 
443         context.put( "shortTitle", sink.getTitle() );
444 
445         // DOXIASITETOOLS-70: Prepend the project name to the title, if any
446         String title = "";
447         if ( siteRenderingContext.getDecoration() != null
448                 && siteRenderingContext.getDecoration().getName() != null )
449         {
450             title = siteRenderingContext.getDecoration().getName();
451         }
452         else if ( siteRenderingContext.getDefaultWindowTitle() != null )
453         {
454             title = siteRenderingContext.getDefaultWindowTitle();
455         }
456 
457         if ( title.length() > 0 )
458         {
459             title += " - ";
460         }
461         title += sink.getTitle();
462 
463         context.put( "title", title );
464 
465         context.put( "headContent", sink.getHead() );
466 
467         context.put( "bodyContent", sink.getBody() );
468 
469         context.put( "decoration", siteRenderingContext.getDecoration() );
470 
471         SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
472         if ( StringUtils.isNotEmpty( sink.getDate() ) )
473         {
474             try
475             {
476                 // we support only ISO-8601 date
477                 context.put( "dateCreation",
478                         sdf.format( new SimpleDateFormat( "yyyy-MM-dd" ).parse( sink.getDate() ) ) );
479             }
480             catch ( java.text.ParseException e )
481             {
482                 getLogger().debug( "Could not parse date: " + sink.getDate() + ", ignoring!", e );
483             }
484         }
485         context.put( "dateRevision", sdf.format( new Date() ) );
486 
487         context.put( "currentDate", new Date() );
488         
489         context.put( "publishDate", siteRenderingContext.getPublishDate() );
490 
491         Locale locale = siteRenderingContext.getLocale();
492         context.put( "dateFormat", DateFormat.getDateInstance( DateFormat.DEFAULT, locale ) );
493 
494         String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
495         context.put( "currentFileName", currentFileName );
496 
497         context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
498 
499         context.put( "locale", locale );
500         context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
501 
502         InputStream inputStream = null;
503         try
504         {
505             inputStream = this.getClass().getClassLoader().getResourceAsStream( "META-INF/maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
506             if ( inputStream == null )
507             {
508                 getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
509             }
510             else
511             {
512                 Properties properties = new Properties();
513                 properties.load( inputStream );
514                 context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
515             }
516         }
517         catch( IOException e )
518         {
519             getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available in the velocityContext." );
520         }
521         finally
522         {
523             IOUtil.close( inputStream );
524         }
525 
526         // Add user properties
527         Map<String, ?> templateProperties = siteRenderingContext.getTemplateProperties();
528 
529         if ( templateProperties != null )
530         {
531             for ( Map.Entry<String, ?> entry : templateProperties.entrySet() )
532             {
533                 context.put( entry.getKey(), entry.getValue() );
534             }
535         }
536 
537         // ----------------------------------------------------------------------
538         // Tools
539         // ----------------------------------------------------------------------
540 
541         context.put( "PathTool", new PathTool() );
542 
543         context.put( "FileUtils", new FileUtils() );
544 
545         context.put( "StringUtils", new StringUtils() );
546 
547         context.put( "i18n", i18n );
548 
549         return context;
550     }
551 
552     /** {@inheritDoc} */
553     public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
554             throws RendererException
555     {
556         Context context = createVelocityContext( sink, siteRenderingContext );
557 
558         writeTemplate( writer, context, siteRenderingContext );
559     }
560 
561     private void writeTemplate( Writer writer, Context context, SiteRenderingContext siteContext )
562             throws RendererException
563     {
564         ClassLoader old = null;
565 
566         if ( siteContext.getTemplateClassLoader() != null )
567         {
568             // -------------------------------------------------------------------------
569             // If no template classloader was set we'll just use the context classloader
570             // -------------------------------------------------------------------------
571 
572             old = Thread.currentThread().getContextClassLoader();
573 
574             Thread.currentThread().setContextClassLoader( siteContext.getTemplateClassLoader() );
575         }
576 
577         try
578         {
579             processTemplate( siteContext.getTemplateName(), context, writer );
580         }
581         finally
582         {
583             IOUtil.close( writer );
584 
585             if ( old != null )
586             {
587                 Thread.currentThread().setContextClassLoader( old );
588             }
589         }
590     }
591 
592     /**
593      * @noinspection OverlyBroadCatchBlock,UnusedCatchParameter
594      */
595     private void processTemplate( String templateName, Context context, Writer writer )
596             throws RendererException
597     {
598         Template template;
599 
600         try
601         {
602             template = velocity.getEngine().getTemplate( templateName );
603         }
604         catch ( Exception e )
605         {
606             throw new RendererException( "Could not find the template '" + templateName, e );
607         }
608 
609         try
610         {
611             template.merge( context, writer );
612         }
613         catch ( Exception e )
614         {
615             throw new RendererException( "Error while generating code.", e );
616         }
617     }
618 
619     /** {@inheritDoc} */
620     public SiteRenderingContext createContextForSkin( File skinFile, Map<String, ?> attributes, DecorationModel decoration,
621                                                       String defaultWindowTitle, Locale locale )
622             throws IOException
623     {
624         SiteRenderingContext context = new SiteRenderingContext();
625 
626         ZipFile zipFile = getZipFile( skinFile );
627 
628         try
629         {
630             if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
631             {
632                 context.setTemplateName( SKIN_TEMPLATE_LOCATION );
633                 context.setTemplateClassLoader( new URLClassLoader( new URL[]{skinFile.toURI().toURL()} ) );
634             }
635             else
636             {
637                 context.setTemplateName( DEFAULT_TEMPLATE );
638                 context.setTemplateClassLoader( getClass().getClassLoader() );
639                 context.setUsingDefaultTemplate( true );
640             }
641         }
642         finally
643         {
644             closeZipFile( zipFile );
645         }
646 
647         context.setTemplateProperties( attributes );
648         context.setLocale( locale );
649         context.setDecoration( decoration );
650         context.setDefaultWindowTitle( defaultWindowTitle );
651         context.setSkinJarFile( skinFile );
652 
653         return context;
654     }
655 
656     private static ZipFile getZipFile( File file )
657         throws IOException
658     {
659         if ( file == null )
660         {
661             throw new IOException( "Error opening ZipFile: null" );
662         }
663 
664         try
665         {
666             // TODO: plexus-archiver, if it could do the excludes
667             return new ZipFile( file );
668         }
669         catch ( ZipException ex )
670         {
671             IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
672             ioe.initCause( ex );
673             throw ioe;
674         }
675     }
676 
677     /** {@inheritDoc} */
678     public SiteRenderingContext createContextForTemplate( File templateFile, File skinFile, Map<String, ?> attributes,
679                                                           DecorationModel decoration, String defaultWindowTitle,
680                                                           Locale locale )
681             throws MalformedURLException
682     {
683         SiteRenderingContext context = new SiteRenderingContext();
684 
685         context.setTemplateName( templateFile.getName() );
686         context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
687 
688         context.setTemplateProperties( attributes );
689         context.setLocale( locale );
690         context.setDecoration( decoration );
691         context.setDefaultWindowTitle( defaultWindowTitle );
692         context.setSkinJarFile( skinFile );
693 
694         return context;
695     }
696 
697     private static void closeZipFile( ZipFile zipFile )
698     {
699         // TODO: move to plexus utils
700         try
701         {
702             zipFile.close();
703         }
704         catch ( IOException e )
705         {
706             // ignore
707         }
708     }
709 
710     /** {@inheritDoc} */
711     public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory, File outputDirectory )
712             throws IOException
713     {
714         if ( siteRenderingContext.getSkinJarFile() != null )
715         {
716             ZipFile file = getZipFile( siteRenderingContext.getSkinJarFile() );
717 
718             try
719             {
720                 for ( Enumeration<? extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
721                 {
722                     ZipEntry entry = e.nextElement();
723 
724                     if ( !entry.getName().startsWith( "META-INF/" ) )
725                     {
726                         File destFile = new File( outputDirectory, entry.getName() );
727                         if ( !entry.isDirectory() )
728                         {
729                             destFile.getParentFile().mkdirs();
730 
731                             copyFileFromZip( file, entry, destFile );
732                         }
733                         else
734                         {
735                             destFile.mkdirs();
736                         }
737                     }
738                 }
739             }
740             finally
741             {
742                 closeZipFile( file );
743             }
744         }
745 
746         if ( siteRenderingContext.isUsingDefaultTemplate() )
747         {
748             InputStream resourceList = getClass().getClassLoader()
749                     .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
750 
751             if ( resourceList != null )
752             {
753                 Reader r = null;
754                 try
755                 {
756                     r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
757                     LineNumberReader reader = new LineNumberReader( r );
758 
759                     String line = reader.readLine();
760 
761                     while ( line != null )
762                     {
763                         InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
764 
765                         if ( is == null )
766                         {
767                             throw new IOException( "The resource " + line + " doesn't exist." );
768                         }
769 
770                         File outputFile = new File( outputDirectory, line );
771 
772                         if ( !outputFile.getParentFile().exists() )
773                         {
774                             outputFile.getParentFile().mkdirs();
775                         }
776 
777                         OutputStream os = null;
778                         try
779                         {
780                             // for the images
781                             os = new FileOutputStream( outputFile );
782                             IOUtil.copy( is, os );
783                         }
784                         finally
785                         {
786                             IOUtil.close( os );
787                         }
788 
789                         IOUtil.close( is );
790 
791                         line = reader.readLine();
792                     }
793                 }
794                 finally
795                 {
796                     IOUtil.close( r );
797                 }
798             }
799         }
800 
801         // Copy extra site resources
802         if ( resourcesDirectory != null && resourcesDirectory.exists() )
803         {
804             copyDirectory( resourcesDirectory, outputDirectory );
805         }
806 
807         // Check for the existence of /css/site.css
808         File siteCssFile = new File( outputDirectory, "/css/site.css" );
809         if ( !siteCssFile.exists() )
810         {
811             // Create the subdirectory css if it doesn't exist, DOXIA-151
812             File cssDirectory = new File( outputDirectory, "/css/" );
813             boolean created = cssDirectory.mkdirs();
814             if ( created && getLogger().isDebugEnabled() )
815             {
816                 getLogger().debug(
817                     "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
818             }
819 
820             // If the file is not there - create an empty file, DOXIA-86
821             if ( getLogger().isDebugEnabled() )
822             {
823                 getLogger().debug(
824                     "The file '" + siteCssFile.getAbsolutePath() + "' does not exists. Creating an empty file." );
825             }
826             Writer writer = null;
827             try
828             {
829                 writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
830                 //DOXIA-290...the file should not be 0 bytes.
831                 writer.write( "/* You can override this file with your own styles */"  );
832             }
833             finally
834             {
835                 IOUtil.close( writer );
836             }
837         }
838     }
839 
840     private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
841             throws IOException
842     {
843         FileOutputStream fos = new FileOutputStream( destFile );
844 
845         try
846         {
847             IOUtil.copy( file.getInputStream( entry ), fos );
848         }
849         finally
850         {
851             IOUtil.close( fos );
852         }
853     }
854 
855     /**
856      * Copy the directory
857      *
858      * @param source      source file to be copied
859      * @param destination destination file
860      * @throws java.io.IOException if any
861      */
862     protected void copyDirectory( File source, File destination )
863             throws IOException
864     {
865         if ( source.exists() )
866         {
867             DirectoryScanner scanner = new DirectoryScanner();
868 
869             String[] includedResources = {"**/**"};
870 
871             scanner.setIncludes( includedResources );
872 
873             scanner.addDefaultExcludes();
874 
875             scanner.setBasedir( source );
876 
877             scanner.scan();
878 
879             List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
880 
881             for ( String name : includedFiles )
882             {
883                 File sourceFile = new File( source, name );
884 
885                 File destinationFile = new File( destination, name );
886 
887                 FileUtils.copyFile( sourceFile, destinationFile );
888             }
889         }
890     }
891 
892     private Reader validate( Reader source, String resource )
893             throws ParseException, IOException
894     {
895         getLogger().debug( "Validating: " + resource );
896 
897         try
898         {
899             String content = IOUtil.toString( new BufferedReader( source ) );
900 
901             new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
902 
903             return new StringReader( content );
904         }
905         finally
906         {
907             IOUtil.close( source );
908         }
909     }
910 
911 }