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