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