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