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