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