1 package org.apache.maven.doxia.siterenderer;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.FileWriter;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.LineNumberReader;
30 import java.io.OutputStreamWriter;
31 import java.io.Reader;
32 import java.io.StringReader;
33 import java.io.StringWriter;
34 import java.io.UnsupportedEncodingException;
35 import java.io.Writer;
36
37 import java.net.MalformedURLException;
38 import java.net.URL;
39 import java.net.URLClassLoader;
40
41 import java.text.DateFormat;
42
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.Date;
47 import java.util.Enumeration;
48 import java.util.Iterator;
49 import java.util.LinkedHashMap;
50 import java.util.List;
51 import java.util.Locale;
52 import java.util.Map;
53 import java.util.zip.ZipEntry;
54 import java.util.zip.ZipFile;
55
56 import org.apache.maven.doxia.Doxia;
57 import org.apache.maven.doxia.module.xhtml.decoration.render.RenderingContext;
58 import org.apache.maven.doxia.parser.ParseException;
59 import org.apache.maven.doxia.parser.Parser;
60 import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
61 import org.apache.maven.doxia.site.decoration.DecorationModel;
62 import org.apache.maven.doxia.module.site.SiteModule;
63 import org.apache.maven.doxia.module.site.manager.SiteModuleManager;
64 import org.apache.maven.doxia.module.site.manager.SiteModuleNotFoundException;
65 import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
66
67 import org.apache.velocity.Template;
68 import org.apache.velocity.VelocityContext;
69 import org.apache.velocity.context.Context;
70
71 import org.codehaus.plexus.i18n.I18N;
72 import org.codehaus.plexus.logging.AbstractLogEnabled;
73 import org.codehaus.plexus.util.DirectoryScanner;
74 import org.codehaus.plexus.util.FileUtils;
75 import org.codehaus.plexus.util.IOUtil;
76 import org.codehaus.plexus.util.Os;
77 import org.codehaus.plexus.util.PathTool;
78 import org.codehaus.plexus.util.ReaderFactory;
79 import org.codehaus.plexus.util.StringUtils;
80 import org.codehaus.plexus.velocity.SiteResourceLoader;
81 import org.codehaus.plexus.velocity.VelocityComponent;
82
83
84
85
86
87
88
89
90
91 public class DefaultSiteRenderer
92 extends AbstractLogEnabled
93 implements Renderer
94 {
95
96
97
98
99
100
101
102 private VelocityComponent velocity;
103
104
105
106
107 private SiteModuleManager siteModuleManager;
108
109
110
111
112 private Doxia doxia;
113
114
115
116
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
127
128
129
130 public void render( Collection documents,
131 SiteRenderingContext siteRenderingContext,
132 File outputDirectory )
133 throws RendererException, IOException
134 {
135 renderModule( documents, siteRenderingContext, outputDirectory );
136
137 for ( Iterator i = siteRenderingContext.getSiteDirectories().iterator(); i.hasNext(); )
138 {
139 File siteDirectory = (File) i.next();
140 copyResources( siteRenderingContext, new File( siteDirectory, "resources" ), outputDirectory );
141 }
142 }
143
144
145 public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
146 throws IOException, RendererException
147 {
148 Map files = new LinkedHashMap();
149 Map moduleExcludes = siteRenderingContext.getModuleExcludes();
150
151 for ( Iterator i = siteRenderingContext.getSiteDirectories().iterator(); i.hasNext(); )
152 {
153 File siteDirectory = (File) i.next();
154 if ( siteDirectory.exists() )
155 {
156 for ( Iterator j = siteModuleManager.getSiteModules().iterator(); j.hasNext(); )
157 {
158 SiteModule module = (SiteModule) j.next();
159
160 File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
161
162 if ( moduleExcludes != null && moduleExcludes.containsKey( module.getParserId() ) )
163 {
164 addModuleFiles( moduleBasedir, module, (String) moduleExcludes.get( module.getParserId() ),
165 files );
166 }
167 else
168 {
169 addModuleFiles( moduleBasedir, module, null, files );
170 }
171 }
172 }
173 }
174
175 for ( Iterator i = siteRenderingContext.getModules().iterator(); i.hasNext(); )
176 {
177 ModuleReference module = (ModuleReference) i.next();
178
179 try
180 {
181 if ( moduleExcludes != null && moduleExcludes.containsKey( module.getParserId() ) )
182 {
183 addModuleFiles( module.getBasedir(), siteModuleManager.getSiteModule( module.getParserId() ),
184 (String) moduleExcludes.get( module.getParserId() ), files );
185 }
186 else
187 {
188 addModuleFiles( module.getBasedir(), siteModuleManager.getSiteModule( module.getParserId() ), null,
189 files );
190 }
191 }
192 catch ( SiteModuleNotFoundException e )
193 {
194 throw new RendererException( "Unable to find module: " + e.getMessage(), e );
195 }
196 }
197 return files;
198 }
199
200 private void addModuleFiles( File moduleBasedir,
201 SiteModule module,
202 String excludes,
203 Map files )
204 throws IOException, RendererException
205 {
206 if ( moduleBasedir.exists() )
207 {
208 List docs = new ArrayList();
209
210 docs.addAll( FileUtils.getFileNames( moduleBasedir, "**/*." + module.getExtension(), excludes, false ) );
211
212
213 docs.addAll( FileUtils.getFileNames( moduleBasedir, "**/*." + module.getExtension() + ".vm", excludes, false ) );
214
215 for ( Iterator k = docs.iterator(); k.hasNext(); )
216 {
217 String doc = (String) k.next();
218
219 RenderingContext context = new RenderingContext( moduleBasedir, doc, module.getParserId(), module.getExtension() );
220
221
222 if ( doc.endsWith( ".vm" ) )
223 {
224 context.setAttribute( "velocity", "true" );
225 }
226
227 String key = context.getOutputName();
228
229 if ( files.containsKey( key ) )
230 {
231 DocumentRenderer renderer = (DocumentRenderer) files.get( key );
232
233 RenderingContext originalContext = renderer.getRenderingContext();
234
235 File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
236
237 throw new RendererException( "Files '" + doc + "' clashes with existing '" + originalDoc + "'." );
238 }
239
240
241
242 for ( Iterator iter = files.entrySet().iterator(); iter.hasNext(); )
243 {
244 Map.Entry entry = (Map.Entry) iter.next();
245 if ( entry.getKey().toString().toLowerCase().equals( key.toLowerCase() ) )
246 {
247 DocumentRenderer renderer = (DocumentRenderer) files.get( entry.getKey() );
248
249 RenderingContext originalContext = renderer.getRenderingContext();
250
251 File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
252
253 if ( Os.isFamily( "windows" ) )
254 {
255 throw new RendererException(
256 "Files '" + doc + "' clashes with existing '" + originalDoc + "'." );
257 }
258
259 getLogger().warn( "Files '" + doc + "' could clashes with existing '" + originalDoc + "'." );
260 }
261 }
262
263 files.put( key, new DoxiaDocumentRenderer( context ) );
264 }
265 }
266 }
267
268 private void renderModule( Collection docs,
269 SiteRenderingContext siteRenderingContext,
270 File outputDirectory )
271 throws IOException, RendererException
272 {
273 for ( Iterator i = docs.iterator(); i.hasNext(); )
274 {
275 DocumentRenderer docRenderer = (DocumentRenderer) i.next();
276
277 RenderingContext renderingContext = docRenderer.getRenderingContext();
278
279 File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
280
281 File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
282
283 boolean modified = false;
284 if ( !outputFile.exists() || inputFile.lastModified() > outputFile.lastModified() )
285 {
286 modified = true;
287 }
288
289 if ( modified || docRenderer.isOverwrite() )
290 {
291 if ( !outputFile.getParentFile().exists() )
292 {
293 outputFile.getParentFile().mkdirs();
294 }
295
296 getLogger().debug( "Generating " + outputFile );
297
298 OutputStreamWriter writer = new OutputStreamWriter( new FileOutputStream( outputFile ),
299 siteRenderingContext.getOutputEncoding() );
300
301 try
302 {
303 docRenderer.renderDocument( writer, this, siteRenderingContext );
304 }
305 finally
306 {
307 IOUtil.close( writer );
308 }
309 }
310 else
311 {
312 getLogger().debug( inputFile + " unchanged, not regenerating..." );
313 }
314 }
315 }
316
317
318 public void renderDocument( Writer writer,
319 RenderingContext renderingContext,
320 SiteRenderingContext context )
321 throws RendererException, FileNotFoundException, UnsupportedEncodingException
322 {
323 SiteRendererSink sink = new SiteRendererSink( renderingContext );
324
325 File doc = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
326
327 try
328 {
329 Reader reader = null;
330 Parser parser = doxia.getParser( renderingContext.getParserId() );
331
332
333 if ( renderingContext.getAttribute( "velocity" ) != null )
334 {
335 String resource = doc.getAbsolutePath();
336
337 try
338 {
339 SiteResourceLoader.setResource( resource );
340
341 Context vc = createContext( sink, context );
342
343 StringWriter sw = new StringWriter();
344
345 velocity.getEngine().mergeTemplate( resource, context.getInputEncoding(), vc, sw );
346
347 reader = new StringReader( sw.toString() );
348 }
349 catch ( Exception e )
350 {
351 if ( getLogger().isDebugEnabled() )
352 {
353 getLogger().error( "Error parsing " + resource + " as a velocity template, using as text.", e );
354 }
355 else
356 {
357 getLogger().error( "Error parsing " + resource + " as a velocity template, using as text." );
358 }
359 }
360 }
361 else
362 {
363 switch ( parser.getType() )
364 {
365 case Parser.XML_TYPE:
366 reader = ReaderFactory.newXmlReader( doc );
367 break;
368
369 case Parser.TXT_TYPE:
370 case Parser.UNKNOWN_TYPE:
371 default:
372 reader = ReaderFactory.newReader( doc, context.getInputEncoding() );
373 }
374 }
375
376 doxia.parse( reader, renderingContext.getParserId(), sink );
377
378 generateDocument( writer, sink, context );
379 }
380 catch ( ParserNotFoundException e )
381 {
382 throw new RendererException( "Error getting a parser for " + doc + ": " + e.getMessage() );
383 }
384 catch ( ParseException e )
385 {
386 getLogger().error( "Error parsing " + doc + ": line [" + e.getLineNumber() + "] " + e.getMessage(), e );
387 }
388 catch ( IOException e )
389 {
390 getLogger().error( "Error parsing " + doc + " to detect encoding", e );
391 }
392 finally
393 {
394 sink.flush();
395
396 sink.close();
397 }
398 }
399
400 private Context createContext( SiteRendererSink sink,
401 SiteRenderingContext siteRenderingContext )
402 {
403 VelocityContext context = new VelocityContext();
404
405
406
407
408
409 RenderingContext renderingContext = sink.getRenderingContext();
410 context.put( "relativePath", renderingContext.getRelativePath() );
411
412
413 context.put( "authors", sink.getAuthors() );
414
415 String title = "";
416 if ( siteRenderingContext.getDecoration().getName() != null )
417 {
418 title = siteRenderingContext.getDecoration().getName();
419 }
420 else if ( siteRenderingContext.getDefaultWindowTitle() != null )
421 {
422 title = siteRenderingContext.getDefaultWindowTitle();
423 }
424
425 if ( title.length() > 0 )
426 {
427 title += " - ";
428 }
429 title += sink.getTitle();
430
431 context.put( "title", title );
432
433 context.put( "bodyContent", sink.getBody() );
434
435 context.put( "decoration", siteRenderingContext.getDecoration() );
436
437 context.put( "currentDate", new Date() );
438
439 Locale locale = siteRenderingContext.getLocale();
440 context.put( "dateFormat", DateFormat.getDateInstance( DateFormat.DEFAULT, locale ) );
441
442 String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
443 context.put( "currentFileName", currentFileName );
444
445 context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
446
447 context.put( "locale", locale );
448
449
450 Map templateProperties = siteRenderingContext.getTemplateProperties();
451
452 if ( templateProperties != null )
453 {
454 for ( Iterator i = templateProperties.keySet().iterator(); i.hasNext(); )
455 {
456 String key = (String) i.next();
457
458 context.put( key, templateProperties.get( key ) );
459 }
460 }
461
462
463
464
465
466 context.put( "PathTool", new PathTool() );
467
468 context.put( "FileUtils", new FileUtils() );
469
470 context.put( "StringUtils", new StringUtils() );
471
472 context.put( "i18n", i18n );
473
474 return context;
475 }
476
477
478 public void generateDocument( Writer writer,
479 SiteRendererSink sink,
480 SiteRenderingContext siteRenderingContext )
481 throws RendererException
482 {
483 Context context = createContext( sink, siteRenderingContext );
484
485 writeTemplate( writer, context, siteRenderingContext );
486 }
487
488 private void writeTemplate( Writer writer,
489 Context context,
490 SiteRenderingContext siteContext )
491 throws RendererException
492 {
493 ClassLoader old = null;
494
495 if ( siteContext.getTemplateClassLoader() != null )
496 {
497
498
499
500
501 old = Thread.currentThread().getContextClassLoader();
502
503 Thread.currentThread().setContextClassLoader( siteContext.getTemplateClassLoader() );
504 }
505
506 try
507 {
508 processTemplate( siteContext.getTemplateName(), context, writer );
509 }
510 finally
511 {
512 IOUtil.close( writer );
513
514 if ( old != null )
515 {
516 Thread.currentThread().setContextClassLoader( old );
517 }
518 }
519 }
520
521
522
523
524 private void processTemplate( String templateName,
525 Context context,
526 Writer writer )
527 throws RendererException
528 {
529 Template template;
530
531 try
532 {
533 template = velocity.getEngine().getTemplate( templateName );
534 }
535 catch ( Exception e )
536 {
537 throw new RendererException( "Could not find the template '" + templateName );
538 }
539
540 try
541 {
542 template.merge( context, writer );
543 }
544 catch ( Exception e )
545 {
546 throw new RendererException( "Error while generating code.", e );
547 }
548 }
549
550
551 public SiteRenderingContext createContextForSkin( File skinFile,
552 Map attributes,
553 DecorationModel decoration,
554 String defaultWindowTitle,
555 Locale locale )
556 throws IOException
557 {
558 SiteRenderingContext context = new SiteRenderingContext();
559
560
561 ZipFile zipFile = new ZipFile( skinFile );
562 try
563 {
564 if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
565 {
566 context.setTemplateName( SKIN_TEMPLATE_LOCATION );
567 context.setTemplateClassLoader( new URLClassLoader( new URL[]{skinFile.toURL()} ) );
568 }
569 else
570 {
571 context.setTemplateName( DEFAULT_TEMPLATE );
572 context.setTemplateClassLoader( getClass().getClassLoader() );
573 context.setUsingDefaultTemplate( true );
574 }
575 }
576 finally
577 {
578 closeZipFile( zipFile );
579 }
580
581 context.setTemplateProperties( attributes );
582 context.setLocale( locale );
583 context.setDecoration( decoration );
584 context.setDefaultWindowTitle( defaultWindowTitle );
585 context.setSkinJarFile( skinFile );
586
587 return context;
588 }
589
590
591 public SiteRenderingContext createContextForTemplate( File templateFile,
592 File skinFile,
593 Map attributes,
594 DecorationModel decoration,
595 String defaultWindowTitle,
596 Locale locale )
597 throws MalformedURLException
598 {
599 SiteRenderingContext context = new SiteRenderingContext();
600
601 context.setTemplateName( templateFile.getName() );
602 context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
603
604 context.setTemplateProperties( attributes );
605 context.setLocale( locale );
606 context.setDecoration( decoration );
607 context.setDefaultWindowTitle( defaultWindowTitle );
608 context.setSkinJarFile( skinFile );
609
610 return context;
611 }
612
613 private void closeZipFile( ZipFile zipFile )
614 {
615
616 try
617 {
618 zipFile.close();
619 }
620 catch ( IOException e )
621 {
622
623 }
624 }
625
626
627 public void copyResources( SiteRenderingContext siteRenderingContext,
628 File resourcesDirectory,
629 File outputDirectory )
630 throws IOException
631 {
632 if ( siteRenderingContext.getSkinJarFile() != null )
633 {
634
635 ZipFile file = new ZipFile( siteRenderingContext.getSkinJarFile() );
636 try
637 {
638 for ( Enumeration e = file.entries(); e.hasMoreElements(); )
639 {
640 ZipEntry entry = (ZipEntry) e.nextElement();
641
642 if ( !entry.getName().startsWith( "META-INF/" ) )
643 {
644 File destFile = new File( outputDirectory, entry.getName() );
645 if ( !entry.isDirectory() )
646 {
647 destFile.getParentFile().mkdirs();
648
649 copyFileFromZip( file, entry, destFile );
650 }
651 else
652 {
653 destFile.mkdirs();
654 }
655 }
656 }
657 }
658 finally
659 {
660 file.close();
661 }
662 }
663
664 if ( siteRenderingContext.isUsingDefaultTemplate() )
665 {
666 InputStream resourceList = getClass().getClassLoader()
667 .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
668
669 if ( resourceList != null )
670 {
671 LineNumberReader reader = new LineNumberReader( new InputStreamReader( resourceList ) );
672
673 String line = reader.readLine();
674
675 while ( line != null )
676 {
677 InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
678
679 if ( is == null )
680 {
681 throw new IOException( "The resource " + line + " doesn't exist." );
682 }
683
684 File outputFile = new File( outputDirectory, line );
685
686 if ( !outputFile.getParentFile().exists() )
687 {
688 outputFile.getParentFile().mkdirs();
689 }
690
691 FileOutputStream w = new FileOutputStream( outputFile );
692
693 IOUtil.copy( is, w );
694
695 IOUtil.close( is );
696
697 IOUtil.close( w );
698
699 line = reader.readLine();
700 }
701 }
702 }
703
704
705 if ( resourcesDirectory != null && resourcesDirectory.exists() )
706 {
707 copyDirectory( resourcesDirectory, outputDirectory );
708 }
709
710
711 File siteCssFile = new File( outputDirectory, "/css/site.css" );
712 if ( !siteCssFile.exists() )
713 {
714
715 File cssDirectory = new File( outputDirectory, "/css/" );
716 boolean created = cssDirectory.mkdirs();
717 if ( created && getLogger().isDebugEnabled() )
718 {
719 getLogger().debug(
720 "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
721 }
722
723
724 if ( getLogger().isDebugEnabled() )
725 {
726 getLogger().debug(
727 "The file '" + siteCssFile.getAbsolutePath() + "' does not exists. Creating an empty file." );
728 }
729 FileWriter w = new FileWriter( siteCssFile );
730
731
732 w.write( "/* You can override this file with your own styles */" );
733 IOUtil.close( w );
734 }
735 }
736
737 private void copyFileFromZip( ZipFile file,
738 ZipEntry entry,
739 File destFile )
740 throws IOException
741 {
742 FileOutputStream fos = new FileOutputStream( destFile );
743
744 try
745 {
746 IOUtil.copy( file.getInputStream( entry ), fos );
747 }
748 finally
749 {
750 IOUtil.close( fos );
751 }
752 }
753
754
755
756
757
758
759
760
761 protected void copyDirectory( File source,
762 File destination )
763 throws IOException
764 {
765 if ( source.exists() )
766 {
767 DirectoryScanner scanner = new DirectoryScanner();
768
769 String[] includedResources = {"**/**"};
770
771 scanner.setIncludes( includedResources );
772
773 scanner.addDefaultExcludes();
774
775 scanner.setBasedir( source );
776
777 scanner.scan();
778
779 List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
780
781 for ( Iterator j = includedFiles.iterator(); j.hasNext(); )
782 {
783 String name = (String) j.next();
784
785 File sourceFile = new File( source, name );
786
787 File destinationFile = new File( destination, name );
788
789 FileUtils.copyFile( sourceFile, destinationFile );
790 }
791 }
792 }
793
794 }