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