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