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.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 import java.net.MalformedURLException;
36 import java.net.URL;
37 import java.net.URLClassLoader;
38 import java.text.DateFormat;
39 import java.text.SimpleDateFormat;
40 import java.util.Arrays;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.Date;
44 import java.util.Enumeration;
45 import java.util.Iterator;
46 import java.util.LinkedHashMap;
47 import java.util.LinkedList;
48 import java.util.List;
49 import java.util.Locale;
50 import java.util.Map;
51 import java.util.Properties;
52 import java.util.zip.ZipEntry;
53 import java.util.zip.ZipException;
54 import java.util.zip.ZipFile;
55
56 import org.apache.commons.lang3.ArrayUtils;
57 import org.apache.commons.lang3.SystemUtils;
58 import org.apache.maven.artifact.Artifact;
59 import org.apache.maven.artifact.versioning.ArtifactVersion;
60 import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
61 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
62 import org.apache.maven.artifact.versioning.Restriction;
63 import org.apache.maven.artifact.versioning.VersionRange;
64 import org.apache.maven.doxia.Doxia;
65 import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
66 import org.apache.maven.doxia.parser.ParseException;
67 import org.apache.maven.doxia.parser.Parser;
68 import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
69 import org.apache.maven.doxia.site.decoration.DecorationModel;
70 import org.apache.maven.doxia.site.decoration.PublishDate;
71 import org.apache.maven.doxia.site.skin.SkinModel;
72 import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
73 import org.apache.maven.doxia.parser.module.ParserModule;
74 import org.apache.maven.doxia.parser.module.ParserModuleManager;
75 import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
76 import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
77 import org.apache.maven.doxia.util.XmlValidator;
78 import org.apache.velocity.Template;
79 import org.apache.velocity.context.Context;
80 import org.apache.velocity.exception.ParseErrorException;
81 import org.apache.velocity.exception.ResourceNotFoundException;
82 import org.apache.velocity.exception.VelocityException;
83 import org.apache.velocity.tools.Scope;
84 import org.apache.velocity.tools.ToolManager;
85 import org.apache.velocity.tools.config.ConfigurationUtils;
86 import org.apache.velocity.tools.config.EasyFactoryConfiguration;
87 import org.apache.velocity.tools.config.FactoryConfiguration;
88 import org.apache.velocity.tools.generic.AlternatorTool;
89 import org.apache.velocity.tools.generic.ClassTool;
90 import org.apache.velocity.tools.generic.ComparisonDateTool;
91 import org.apache.velocity.tools.generic.ContextTool;
92 import org.apache.velocity.tools.generic.ConversionTool;
93 import org.apache.velocity.tools.generic.DisplayTool;
94 import org.apache.velocity.tools.generic.EscapeTool;
95 import org.apache.velocity.tools.generic.FieldTool;
96 import org.apache.velocity.tools.generic.LinkTool;
97 import org.apache.velocity.tools.generic.LoopTool;
98 import org.apache.velocity.tools.generic.MathTool;
99 import org.apache.velocity.tools.generic.NumberTool;
100 import org.apache.velocity.tools.generic.RenderTool;
101 import org.apache.velocity.tools.generic.ResourceTool;
102 import org.apache.velocity.tools.generic.SortTool;
103 import org.apache.velocity.tools.generic.XmlTool;
104 import org.codehaus.plexus.PlexusContainer;
105 import org.codehaus.plexus.component.annotations.Component;
106 import org.codehaus.plexus.component.annotations.Requirement;
107 import org.codehaus.plexus.i18n.I18N;
108 import org.codehaus.plexus.logging.AbstractLogEnabled;
109 import org.codehaus.plexus.util.DirectoryScanner;
110 import org.codehaus.plexus.util.FileUtils;
111 import org.codehaus.plexus.util.IOUtil;
112 import org.codehaus.plexus.util.Os;
113 import org.codehaus.plexus.util.PathTool;
114 import org.codehaus.plexus.util.PropertyUtils;
115 import org.codehaus.plexus.util.ReaderFactory;
116 import org.codehaus.plexus.util.StringUtils;
117 import org.codehaus.plexus.util.WriterFactory;
118 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
119 import org.codehaus.plexus.velocity.VelocityComponent;
120
121
122
123
124
125
126
127
128 @Component( role = Renderer.class )
129 public class DefaultSiteRenderer
130 extends AbstractLogEnabled
131 implements Renderer
132 {
133
134
135
136
137 @Requirement
138 private VelocityComponent velocity;
139
140 @Requirement
141 private ParserModuleManager parserModuleManager;
142
143 @Requirement
144 private Doxia doxia;
145
146 @Requirement
147 private I18N i18n;
148
149 @Requirement
150 private PlexusContainer plexus;
151
152 private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
153
154 private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
155
156 private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
157
158 private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
159
160
161
162
163
164
165 public Map<String, DocumentRenderer> locateDocumentFiles( SiteRenderingContext siteRenderingContext )
166 throws IOException, RendererException
167 {
168 return locateDocumentFiles( siteRenderingContext, false );
169 }
170
171
172 public Map<String, DocumentRenderer> locateDocumentFiles( SiteRenderingContext siteRenderingContext,
173 boolean editable )
174 throws IOException, RendererException
175 {
176 Map<String, DocumentRenderer> files = new LinkedHashMap<String, DocumentRenderer>();
177 Map<String, String> moduleExcludes = siteRenderingContext.getModuleExcludes();
178
179
180 for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
181 {
182 if ( siteDirectory.exists() )
183 {
184 Collection<ParserModule> modules = parserModuleManager.getParserModules();
185
186 for ( ParserModule module : modules )
187 {
188 File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
189
190 String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
191
192 addModuleFiles( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files,
193 editable );
194 }
195 }
196 }
197
198
199 for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
200 {
201 try
202 {
203 ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
204
205 String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
206
207 addModuleFiles( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes,
208 files, editable );
209 }
210 catch ( ParserModuleNotFoundException e )
211 {
212 throw new RendererException( "Unable to find module: " + e.getMessage(), e );
213 }
214 }
215 return files;
216 }
217
218 private List<String> filterExtensionIgnoreCase( List<String> fileNames, String extension )
219 {
220 List<String> filtered = new LinkedList<String>( fileNames );
221 for ( Iterator<String> it = filtered.iterator(); it.hasNext(); )
222 {
223 String name = it.next();
224
225
226 if ( !endsWithIgnoreCase( name, extension ) )
227 {
228 it.remove();
229 }
230 }
231 return filtered;
232 }
233
234 private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
235 Map<String, DocumentRenderer> files, boolean editable )
236 throws IOException, RendererException
237 {
238 if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
239 {
240 return;
241 }
242
243 String moduleRelativePath =
244 PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
245
246 List<String> allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
247
248 for ( String extension : module.getExtensions() )
249 {
250 String fullExtension = "." + extension;
251
252 List<String> docs = filterExtensionIgnoreCase( allFiles, fullExtension );
253
254
255 List<String> velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
256
257 docs.addAll( velocityFiles );
258
259 for ( String doc : docs )
260 {
261 RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
262 module.getParserId(), extension, editable );
263
264
265 if ( endsWithIgnoreCase( doc, ".vm" ) )
266 {
267 context.setAttribute( "velocity", "true" );
268 }
269
270 String key = context.getOutputName();
271 key = StringUtils.replace( key, "\\", "/" );
272
273 if ( files.containsKey( key ) )
274 {
275 DocumentRenderer renderer = files.get( key );
276
277 RenderingContext originalContext = renderer.getRenderingContext();
278
279 File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
280
281 throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
282 + "' clashes with existing '" + originalDoc + "'." );
283 }
284
285
286
287 for ( Map.Entry<String, DocumentRenderer> entry : files.entrySet() )
288 {
289 if ( entry.getKey().equalsIgnoreCase( key ) )
290 {
291 RenderingContext originalContext = entry.getValue().getRenderingContext();
292
293 File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
294
295 if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
296 {
297 throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
298 + doc + "' clashes with existing '" + originalDoc + "'." );
299 }
300
301 if ( getLogger().isWarnEnabled() )
302 {
303 getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
304 + "' could clash with existing '" + originalDoc + "'." );
305 }
306 }
307 }
308
309 files.put( key, new DoxiaDocumentRenderer( context ) );
310 }
311 }
312 }
313
314
315 public void render( Collection<DocumentRenderer> documents, SiteRenderingContext siteRenderingContext,
316 File outputDirectory )
317 throws RendererException, IOException
318 {
319 for ( DocumentRenderer docRenderer : documents )
320 {
321 RenderingContext renderingContext = docRenderer.getRenderingContext();
322
323 File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
324
325 File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
326
327 boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
328 || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
329
330 if ( modified || docRenderer.isOverwrite() )
331 {
332 if ( !outputFile.getParentFile().exists() )
333 {
334 outputFile.getParentFile().mkdirs();
335 }
336
337 if ( getLogger().isDebugEnabled() )
338 {
339 getLogger().debug( "Generating " + outputFile );
340 }
341
342 Writer writer = null;
343 try
344 {
345 if ( !docRenderer.isExternalReport() )
346 {
347 writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
348 }
349 docRenderer.renderDocument( writer, this, siteRenderingContext );
350 }
351 finally
352 {
353 IOUtil.close( writer );
354 }
355 }
356 else
357 {
358 if ( getLogger().isDebugEnabled() )
359 {
360 getLogger().debug( inputFile + " unchanged, not regenerating..." );
361 }
362 }
363 }
364 }
365
366
367 public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
368 throws RendererException, FileNotFoundException, UnsupportedEncodingException
369 {
370 SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
371
372 File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
373
374 Reader reader = null;
375 try
376 {
377 String resource = doc.getAbsolutePath();
378
379 Parser parser = doxia.getParser( docRenderingContext.getParserId() );
380
381 parser.setEmitComments( false );
382
383
384 if ( docRenderingContext.getAttribute( "velocity" ) != null )
385 {
386 getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
387 try
388 {
389 Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
390
391 StringWriter sw = new StringWriter();
392
393 velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
394
395 String doxiaContent = sw.toString();
396
397 if ( siteContext.getProcessedContentOutput() != null )
398 {
399
400 saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
401 }
402
403 reader = new StringReader( doxiaContent );
404 }
405 catch ( VelocityException e )
406 {
407 throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
408 + " as a Velocity template: " + e.getMessage(), e );
409 }
410
411 if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
412 {
413 reader = validate( reader, resource );
414 }
415 }
416 else
417 {
418 switch ( parser.getType() )
419 {
420 case Parser.XML_TYPE:
421 reader = ReaderFactory.newXmlReader( doc );
422 if ( siteContext.isValidate() )
423 {
424 reader = validate( reader, resource );
425 }
426 break;
427
428 case Parser.TXT_TYPE:
429 case Parser.UNKNOWN_TYPE:
430 default:
431 reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
432 }
433 }
434 sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
435
436 doxia.parse( reader, docRenderingContext.getParserId(), sink );
437 }
438 catch ( ParserNotFoundException e )
439 {
440 throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
441 }
442 catch ( ParseException e )
443 {
444 throw new RendererException( "Error parsing '"
445 + doc + "': line [" + e.getLineNumber() + "] " + e.getMessage(), e );
446 }
447 catch ( IOException e )
448 {
449 throw new RendererException( "IOException when processing '" + doc + "'", e );
450 }
451 finally
452 {
453 sink.flush();
454
455 sink.close();
456
457 IOUtil.close( reader );
458 }
459
460 mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
461 }
462
463 private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
464 String doxiaContent )
465 throws IOException
466 {
467 if ( !siteContext.getProcessedContentOutput().exists() )
468 {
469 siteContext.getProcessedContentOutput().mkdirs();
470 }
471
472 String input = docRenderingContext.getInputName();
473 File outputFile = new File( siteContext.getProcessedContentOutput(),
474 input.substring( 0, input.length() - 3 ) );
475
476 File outputParent = outputFile.getParentFile();
477 if ( !outputParent.exists() )
478 {
479 outputParent.mkdirs();
480 }
481
482 FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
483 }
484
485
486
487
488
489
490
491 protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
492 {
493 Locale locale = siteRenderingContext.getLocale();
494 String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
495
496 EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
497 config.property( "safeMode", Boolean.FALSE );
498 config.toolbox( Scope.REQUEST )
499 .tool( ContextTool.class )
500 .tool( LinkTool.class )
501 .tool( LoopTool.class )
502 .tool( RenderTool.class );
503 config.toolbox( Scope.APPLICATION ).property( "locale", locale )
504 .tool( AlternatorTool.class )
505 .tool( ClassTool.class )
506 .tool( ComparisonDateTool.class ).property( "format", dateFormat )
507 .tool( ConversionTool.class ).property( "dateFormat", dateFormat )
508 .tool( DisplayTool.class )
509 .tool( EscapeTool.class )
510 .tool( FieldTool.class )
511 .tool( MathTool.class )
512 .tool( NumberTool.class )
513 .tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
514 .tool( SortTool.class )
515 .tool( XmlTool.class );
516
517 FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
518
519 if ( customConfig != null )
520 {
521 config.addConfiguration( customConfig );
522 }
523
524 ToolManager manager = new ToolManager( false, false );
525 manager.configure( config );
526
527 return manager.createContext();
528 }
529
530
531
532
533
534
535
536
537 protected Context createDocumentVelocityContext( RenderingContext renderingContext,
538 SiteRenderingContext siteRenderingContext )
539 {
540 Context context = createToolManagedVelocityContext( siteRenderingContext );
541
542
543
544
545 context.put( "relativePath", renderingContext.getRelativePath() );
546
547 String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
548 context.put( "currentFileName", currentFileName );
549
550 context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
551
552 context.put( "decoration", siteRenderingContext.getDecoration() );
553
554 Locale locale = siteRenderingContext.getLocale();
555 context.put( "locale", locale );
556 context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
557
558 context.put( "currentDate", new Date() );
559 SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
560 context.put( "dateRevision", sdf.format( new Date() ) );
561
562 context.put( "publishDate", siteRenderingContext.getPublishDate() );
563
564 PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
565 DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
566 context.put( "dateFormat", dateFormat );
567
568
569 InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
570 + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
571 Properties properties = PropertyUtils.loadProperties( inputStream );
572 if ( inputStream == null )
573 {
574 getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
575 }
576 else if ( properties == null )
577 {
578 getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
579 + " in the Velocity context." );
580 }
581 else
582 {
583 context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
584 }
585
586
587 Map<String, ?> templateProperties = siteRenderingContext.getTemplateProperties();
588
589 if ( templateProperties != null )
590 {
591 for ( Map.Entry<String, ?> entry : templateProperties.entrySet() )
592 {
593 context.put( entry.getKey(), entry.getValue() );
594 }
595 }
596
597
598
599
600
601 context.put( "PathTool", new PathTool() );
602
603 context.put( "FileUtils", new FileUtils() );
604
605 context.put( "StringUtils", new StringUtils() );
606
607 context.put( "i18n", i18n );
608
609 context.put( "plexus", plexus );
610 return context;
611 }
612
613
614
615
616
617
618
619
620
621 protected Context createSiteTemplateVelocityContext( DocumentContent content,
622 SiteRenderingContext siteRenderingContext )
623 {
624
625 Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext );
626
627
628
629
630 context.put( "authors", content.getAuthors() );
631
632 context.put( "shortTitle", content.getTitle() );
633
634
635 String title = "";
636 if ( siteRenderingContext.getDecoration() != null
637 && siteRenderingContext.getDecoration().getName() != null )
638 {
639 title = siteRenderingContext.getDecoration().getName();
640 }
641 else if ( siteRenderingContext.getDefaultWindowTitle() != null )
642 {
643 title = siteRenderingContext.getDefaultWindowTitle();
644 }
645
646 if ( title.length() > 0 )
647 {
648 title += " – ";
649 }
650 title += content.getTitle();
651
652 context.put( "title", title );
653
654 context.put( "headContent", content.getHead() );
655
656 context.put( "bodyContent", content.getBody() );
657
658
659 String documentDate = content.getDate();
660 if ( StringUtils.isNotEmpty( documentDate ) )
661 {
662 context.put( "documentDate", documentDate );
663
664
665
666
667 try
668 {
669
670 Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
671
672 context.put( "creationDate", creationDate );
673 SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
674 context.put( "dateCreation", sdf.format( creationDate ) );
675 }
676 catch ( java.text.ParseException e )
677 {
678 getLogger().warn( "Could not parse date '" + documentDate + "' from "
679 + content.getRenderingContext().getInputName()
680 + " (expected yyyy-MM-dd format), ignoring!" );
681 }
682 }
683
684
685 context.put( "docRenderingContext", content.getRenderingContext() );
686
687 return context;
688 }
689
690
691 public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
692 throws RendererException
693 {
694 mergeDocumentIntoSite( writer, sink, siteRenderingContext );
695 }
696
697
698 public void mergeDocumentIntoSite( Writer writer, DocumentContent content,
699 SiteRenderingContext siteRenderingContext )
700 throws RendererException
701 {
702 String templateName = siteRenderingContext.getTemplateName();
703
704 getLogger().debug( "Processing Velocity for template " + templateName + " on "
705 + content.getRenderingContext().getInputName() );
706
707 Context context = createSiteTemplateVelocityContext( content, siteRenderingContext );
708
709 ClassLoader old = null;
710
711 if ( siteRenderingContext.getTemplateClassLoader() != null )
712 {
713
714
715
716
717 old = Thread.currentThread().getContextClassLoader();
718
719 Thread.currentThread().setContextClassLoader( siteRenderingContext.getTemplateClassLoader() );
720 }
721
722 try
723 {
724 Template template;
725 Artifact skin = siteRenderingContext.getSkin();
726
727 try
728 {
729 SkinModel skinModel = siteRenderingContext.getSkinModel();
730 String encoding = ( skinModel == null ) ? null : skinModel.getEncoding();
731
732 template = ( encoding == null ) ? velocity.getEngine().getTemplate( templateName )
733 : velocity.getEngine().getTemplate( templateName, encoding );
734 }
735 catch ( ParseErrorException pee )
736 {
737 throw new RendererException( "Velocity parsing error while reading the site decoration template "
738 + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
739 pee );
740 }
741 catch ( ResourceNotFoundException rnfe )
742 {
743 throw new RendererException( "Could not find the site decoration template "
744 + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
745 rnfe );
746 }
747
748 try
749 {
750 StringWriter sw = new StringWriter();
751 template.merge( context, sw );
752 writer.write( sw.toString().replaceAll( "\r?\n", SystemUtils.LINE_SEPARATOR ) );
753 }
754 catch ( VelocityException ve )
755 {
756 throw new RendererException( "Velocity error while merging site decoration template.", ve );
757 }
758 catch ( IOException ioe )
759 {
760 throw new RendererException( "IO exception while merging site decoration template.", ioe );
761 }
762 }
763 finally
764 {
765 IOUtil.close( writer );
766
767 if ( old != null )
768 {
769 Thread.currentThread().setContextClassLoader( old );
770 }
771 }
772 }
773
774 private SiteRenderingContext createSiteRenderingContext( Map<String, ?> attributes, DecorationModel decoration,
775 String defaultWindowTitle, Locale locale )
776 {
777 SiteRenderingContext context = new SiteRenderingContext();
778
779 context.setTemplateProperties( attributes );
780 context.setLocale( locale );
781 context.setDecoration( decoration );
782 context.setDefaultWindowTitle( defaultWindowTitle );
783
784 return context;
785 }
786
787
788 public SiteRenderingContext createContextForSkin( Artifact skin, Map<String, ?> attributes,
789 DecorationModel decoration, String defaultWindowTitle,
790 Locale locale )
791 throws IOException, RendererException
792 {
793 SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
794
795 context.setSkin( skin );
796
797 ZipFile zipFile = getZipFile( skin.getFile() );
798 InputStream in = null;
799
800 try
801 {
802 if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
803 {
804 context.setTemplateName( SKIN_TEMPLATE_LOCATION );
805 context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
806 }
807 else
808 {
809 context.setTemplateName( DEFAULT_TEMPLATE );
810 context.setTemplateClassLoader( getClass().getClassLoader() );
811 context.setUsingDefaultTemplate( true );
812 }
813
814 ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
815 if ( skinDescriptorEntry != null )
816 {
817 in = zipFile.getInputStream( skinDescriptorEntry );
818
819 SkinModel skinModel = new SkinXpp3Reader().read( in );
820 context.setSkinModel( skinModel );
821
822 String toolsPrerequisite =
823 skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
824
825 Package p = DefaultSiteRenderer.class.getPackage();
826 String current = ( p == null ) ? null : p.getImplementationVersion();
827
828 if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
829 && !matchVersion( current, toolsPrerequisite ) )
830 {
831 throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
832 + " Doxia Sitetools prerequisite, but current is " + current );
833 }
834 }
835 }
836 catch ( XmlPullParserException e )
837 {
838 throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
839 + " skin descriptor from " + skin.getId() + " skin", e );
840 }
841 finally
842 {
843 IOUtil.close( in );
844 closeZipFile( zipFile );
845 }
846
847 return context;
848 }
849
850 boolean matchVersion( String current, String prerequisite )
851 throws RendererException
852 {
853 try
854 {
855 ArtifactVersion v = new DefaultArtifactVersion( current );
856 VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
857
858 boolean matched = false;
859 ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
860 if ( recommendedVersion == null )
861 {
862 List<Restriction> restrictions = vr.getRestrictions();
863 for ( Restriction restriction : restrictions )
864 {
865 if ( restriction.containsVersion( v ) )
866 {
867 matched = true;
868 break;
869 }
870 }
871 }
872 else
873 {
874
875 @SuppressWarnings( "unchecked" )
876 int compareTo = recommendedVersion.compareTo( v );
877 matched = ( compareTo <= 0 );
878 }
879
880 if ( getLogger().isDebugEnabled() )
881 {
882 getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
883 + ", matched = " + matched );
884 }
885
886 return matched;
887 }
888 catch ( InvalidVersionSpecificationException e )
889 {
890 throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
891 }
892 }
893
894
895 @Deprecated
896 public SiteRenderingContext createContextForTemplate( File templateFile, Map<String, ?> attributes,
897 DecorationModel decoration, String defaultWindowTitle,
898 Locale locale )
899 throws MalformedURLException
900 {
901 SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
902
903 context.setTemplateName( templateFile.getName() );
904 context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
905
906 return context;
907 }
908
909
910 public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
911 File outputDirectory )
912 throws IOException
913 {
914 throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
915 }
916
917
918 public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
919 throws IOException
920 {
921 if ( siteRenderingContext.getSkin() != null )
922 {
923 ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
924
925 try
926 {
927 for ( Enumeration<? extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
928 {
929 ZipEntry entry = e.nextElement();
930
931 if ( !entry.getName().startsWith( "META-INF/" ) )
932 {
933 File destFile = new File( outputDirectory, entry.getName() );
934 if ( !entry.isDirectory() )
935 {
936 if ( destFile.exists() )
937 {
938
939
940 continue;
941 }
942
943 destFile.getParentFile().mkdirs();
944
945 copyFileFromZip( file, entry, destFile );
946 }
947 else
948 {
949 destFile.mkdirs();
950 }
951 }
952 }
953 }
954 finally
955 {
956 closeZipFile( file );
957 }
958 }
959
960 if ( siteRenderingContext.isUsingDefaultTemplate() )
961 {
962 InputStream resourceList = getClass().getClassLoader()
963 .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
964
965 if ( resourceList != null )
966 {
967 Reader r = null;
968 LineNumberReader reader = null;
969 try
970 {
971 r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
972 reader = new LineNumberReader( r );
973
974 String line;
975
976 while ( ( line = reader.readLine() ) != null )
977 {
978 if ( line.startsWith( "#" ) || line.trim().length() == 0 )
979 {
980 continue;
981 }
982
983 InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
984
985 if ( is == null )
986 {
987 throw new IOException( "The resource " + line + " doesn't exist." );
988 }
989
990 File outputFile = new File( outputDirectory, line );
991
992 if ( outputFile.exists() )
993 {
994
995
996 continue;
997 }
998
999 if ( !outputFile.getParentFile().exists() )
1000 {
1001 outputFile.getParentFile().mkdirs();
1002 }
1003
1004 OutputStream os = null;
1005 try
1006 {
1007
1008 os = new FileOutputStream( outputFile );
1009 IOUtil.copy( is, os );
1010 }
1011 finally
1012 {
1013 IOUtil.close( os );
1014 }
1015
1016 IOUtil.close( is );
1017 }
1018 }
1019 finally
1020 {
1021 IOUtil.close( reader );
1022 IOUtil.close( r );
1023 }
1024 }
1025 }
1026
1027
1028 for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
1029 {
1030 File resourcesDirectory = new File( siteDirectory, "resources" );
1031
1032 if ( resourcesDirectory != null && resourcesDirectory.exists() )
1033 {
1034 copyDirectory( resourcesDirectory, outputDirectory );
1035 }
1036 }
1037
1038
1039 File siteCssFile = new File( outputDirectory, "/css/site.css" );
1040 if ( !siteCssFile.exists() )
1041 {
1042
1043 File cssDirectory = new File( outputDirectory, "/css/" );
1044 boolean created = cssDirectory.mkdirs();
1045 if ( created && getLogger().isDebugEnabled() )
1046 {
1047 getLogger().debug(
1048 "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
1049 }
1050
1051
1052 if ( getLogger().isDebugEnabled() )
1053 {
1054 getLogger().debug(
1055 "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
1056 }
1057 Writer writer = null;
1058 try
1059 {
1060 writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
1061
1062 writer.write( "/* You can override this file with your own styles */" );
1063 }
1064 finally
1065 {
1066 IOUtil.close( writer );
1067 }
1068 }
1069 }
1070
1071 private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
1072 throws IOException
1073 {
1074 FileOutputStream fos = new FileOutputStream( destFile );
1075
1076 try
1077 {
1078 IOUtil.copy( file.getInputStream( entry ), fos );
1079 }
1080 finally
1081 {
1082 IOUtil.close( fos );
1083 }
1084 }
1085
1086
1087
1088
1089
1090
1091
1092
1093 protected void copyDirectory( File source, File destination )
1094 throws IOException
1095 {
1096 if ( source.exists() )
1097 {
1098 DirectoryScanner scanner = new DirectoryScanner();
1099
1100 String[] includedResources = {"**/**"};
1101
1102 scanner.setIncludes( includedResources );
1103
1104 scanner.addDefaultExcludes();
1105
1106 scanner.setBasedir( source );
1107
1108 scanner.scan();
1109
1110 List<String> includedFiles = Arrays.asList( scanner.getIncludedFiles() );
1111
1112 for ( String name : includedFiles )
1113 {
1114 File sourceFile = new File( source, name );
1115
1116 File destinationFile = new File( destination, name );
1117
1118 FileUtils.copyFile( sourceFile, destinationFile );
1119 }
1120 }
1121 }
1122
1123 private Reader validate( Reader source, String resource )
1124 throws ParseException, IOException
1125 {
1126 getLogger().debug( "Validating: " + resource );
1127
1128 try
1129 {
1130 String content = IOUtil.toString( new BufferedReader( source ) );
1131
1132 new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
1133
1134 return new StringReader( content );
1135 }
1136 finally
1137 {
1138 IOUtil.close( source );
1139 }
1140 }
1141
1142
1143 static boolean endsWithIgnoreCase( String str, String searchStr )
1144 {
1145 if ( str.length() < searchStr.length() )
1146 {
1147 return false;
1148 }
1149
1150 return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
1151 }
1152
1153 private static ZipFile getZipFile( File file )
1154 throws IOException
1155 {
1156 if ( file == null )
1157 {
1158 throw new IOException( "Error opening ZipFile: null" );
1159 }
1160
1161 try
1162 {
1163
1164 return new ZipFile( file );
1165 }
1166 catch ( ZipException ex )
1167 {
1168 IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
1169 ioe.initCause( ex );
1170 throw ioe;
1171 }
1172 }
1173
1174 private static void closeZipFile( ZipFile zipFile )
1175 {
1176
1177 try
1178 {
1179 zipFile.close();
1180 }
1181 catch ( IOException e )
1182 {
1183
1184 }
1185 }
1186 }