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