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