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