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