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.SiteRenderingContext.SiteDirectory;
70 import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
71 import org.apache.maven.doxia.util.XmlValidator;
72 import org.apache.velocity.Template;
73 import org.apache.velocity.context.Context;
74 import org.apache.velocity.exception.ParseErrorException;
75 import org.apache.velocity.exception.ResourceNotFoundException;
76 import org.apache.velocity.exception.VelocityException;
77 import org.apache.velocity.tools.Scope;
78 import org.apache.velocity.tools.ToolManager;
79 import org.apache.velocity.tools.config.ConfigurationUtils;
80 import org.apache.velocity.tools.config.EasyFactoryConfiguration;
81 import org.apache.velocity.tools.config.FactoryConfiguration;
82 import org.apache.velocity.tools.generic.AlternatorTool;
83 import org.apache.velocity.tools.generic.ClassTool;
84 import org.apache.velocity.tools.generic.ComparisonDateTool;
85 import org.apache.velocity.tools.generic.ContextTool;
86 import org.apache.velocity.tools.generic.ConversionTool;
87 import org.apache.velocity.tools.generic.DisplayTool;
88 import org.apache.velocity.tools.generic.EscapeTool;
89 import org.apache.velocity.tools.generic.FieldTool;
90 import org.apache.velocity.tools.generic.LinkTool;
91 import org.apache.velocity.tools.generic.LoopTool;
92 import org.apache.velocity.tools.generic.MathTool;
93 import org.apache.velocity.tools.generic.NumberTool;
94 import org.apache.velocity.tools.generic.RenderTool;
95 import org.apache.velocity.tools.generic.ResourceTool;
96 import org.apache.velocity.tools.generic.SortTool;
97 import org.apache.velocity.tools.generic.XmlTool;
98 import org.codehaus.plexus.PlexusContainer;
99 import org.codehaus.plexus.util.DirectoryScanner;
100 import org.codehaus.plexus.util.FileUtils;
101 import org.codehaus.plexus.util.IOUtil;
102 import org.codehaus.plexus.util.Os;
103 import org.codehaus.plexus.util.PathTool;
104 import org.codehaus.plexus.util.ReaderFactory;
105 import org.codehaus.plexus.util.StringUtils;
106 import org.codehaus.plexus.util.WriterFactory;
107 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
108 import org.codehaus.plexus.velocity.VelocityComponent;
109 import org.slf4j.Logger;
110 import org.slf4j.LoggerFactory;
111
112
113
114
115
116
117
118
119 @Singleton
120 @Named
121 public class DefaultSiteRenderer implements Renderer {
122 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSiteRenderer.class);
123
124
125
126
127
128 @Inject
129 private VelocityComponent velocity;
130
131 @Inject
132 private ParserModuleManager parserModuleManager;
133
134 @Inject
135 private Doxia doxia;
136
137 @Inject
138 private PlexusContainer plexus;
139
140 private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
141
142 private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
143
144 private static final String DOXIA_SITE_RENDERER_VERSION = getSiteRendererVersion();
145
146
147
148
149
150
151 public Map<String, DocumentRenderer> locateDocumentFiles(SiteRenderingContext siteRenderingContext)
152 throws IOException, RendererException {
153 Map<String, DocumentRenderer> files = new LinkedHashMap<>();
154 Map<String, String> moduleExcludes = siteRenderingContext.getModuleExcludes();
155
156
157 for (SiteDirectory siteDirectory : siteRenderingContext.getSiteDirectories()) {
158 File siteDirectoryPath = siteDirectory.getPath();
159 if (siteDirectoryPath.exists()) {
160 Collection<ParserModule> modules = parserModuleManager.getParserModules();
161
162 for (ParserModule module : modules) {
163 File moduleBasedir = new File(siteDirectoryPath, module.getSourceDirectory());
164
165 String excludes = (moduleExcludes == null) ? null : moduleExcludes.get(module.getParserId());
166
167 addModuleFiles(
168 siteRenderingContext.getRootDirectory(),
169 moduleBasedir,
170 module,
171 excludes,
172 files,
173 siteDirectory.isEditable());
174 }
175 }
176 }
177
178 return files;
179 }
180
181 private List<String> filterExtensionIgnoreCase(List<String> fileNames, String extension) {
182 List<String> filtered = new LinkedList<>(fileNames);
183 for (Iterator<String> it = filtered.iterator(); it.hasNext(); ) {
184 String name = it.next();
185
186
187 if (!endsWithIgnoreCase(name, extension)) {
188 it.remove();
189 }
190 }
191 return filtered;
192 }
193
194 private void addModuleFiles(
195 File rootDir,
196 File moduleBasedir,
197 ParserModule module,
198 String excludes,
199 Map<String, DocumentRenderer> files,
200 boolean editable)
201 throws IOException, RendererException {
202 if (!moduleBasedir.exists() || ArrayUtils.isEmpty(module.getExtensions())) {
203 return;
204 }
205
206 String moduleRelativePath =
207 PathTool.getRelativeFilePath(rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath());
208
209 List<String> allFiles = FileUtils.getFileNames(moduleBasedir, "**/*", excludes, false);
210
211 for (String extension : module.getExtensions()) {
212 String fullExtension = "." + extension;
213
214 List<String> docs = filterExtensionIgnoreCase(allFiles, fullExtension);
215
216
217 List<String> velocityFiles = filterExtensionIgnoreCase(allFiles, fullExtension + ".vm");
218
219 docs.addAll(velocityFiles);
220
221 for (String doc : docs) {
222 DocumentRenderingContext docRenderingContext = new DocumentRenderingContext(
223 moduleBasedir, moduleRelativePath, doc, module.getParserId(), extension, editable);
224
225
226 if (endsWithIgnoreCase(doc, ".vm")) {
227 docRenderingContext.setAttribute("velocity", "true");
228 }
229
230 String key = docRenderingContext.getOutputName();
231
232 if (files.containsKey(key)) {
233 DocumentRenderer docRenderer = files.get(key);
234
235 DocumentRenderingContext originalDocRenderingContext = docRenderer.getRenderingContext();
236
237 File originalDoc = new File(
238 originalDocRenderingContext.getBasedir(), originalDocRenderingContext.getInputName());
239
240 throw new RendererException("File '" + module.getSourceDirectory() + File.separator + doc
241 + "' clashes with existing '" + originalDoc + "'.");
242 }
243
244
245
246 for (Map.Entry<String, DocumentRenderer> entry : files.entrySet()) {
247 if (entry.getKey().equalsIgnoreCase(key)) {
248 DocumentRenderingContext originalDocRenderingContext =
249 entry.getValue().getRenderingContext();
250
251 File originalDoc = new File(
252 originalDocRenderingContext.getBasedir(), originalDocRenderingContext.getInputName());
253
254 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
255 throw new RendererException("File '" + module.getSourceDirectory() + File.separator + doc
256 + "' clashes with existing '" + originalDoc + "'.");
257 }
258
259 if (LOGGER.isWarnEnabled()) {
260 LOGGER.warn("File '" + module.getSourceDirectory() + File.separator + doc
261 + "' could clash with existing '" + originalDoc + "'.");
262 }
263 }
264 }
265
266 files.put(key, new DoxiaDocumentRenderer(docRenderingContext));
267 }
268 }
269 }
270
271
272 public void render(
273 Collection<DocumentRenderer> documents, SiteRenderingContext siteRenderingContext, File outputDirectory)
274 throws RendererException, IOException {
275 for (DocumentRenderer docRenderer : documents) {
276 DocumentRenderingContext docRenderingContext = docRenderer.getRenderingContext();
277
278 File outputFile = new File(outputDirectory, docRenderer.getOutputName());
279
280 File inputFile = new File(docRenderingContext.getBasedir(), docRenderingContext.getInputName());
281
282 boolean modified = !outputFile.exists()
283 || (inputFile.lastModified() > outputFile.lastModified())
284 || (siteRenderingContext.getSiteModel().getLastModified() > outputFile.lastModified());
285
286 if (modified || docRenderer.isOverwrite()) {
287 if (!outputFile.getParentFile().exists()) {
288 outputFile.getParentFile().mkdirs();
289 }
290
291 if (LOGGER.isDebugEnabled()) {
292 LOGGER.debug("Generating " + outputFile);
293 }
294
295 Writer writer = null;
296 try {
297 if (!docRenderer.isExternalReport()) {
298 writer = WriterFactory.newWriter(outputFile, siteRenderingContext.getOutputEncoding());
299 }
300 docRenderer.renderDocument(writer, this, siteRenderingContext);
301 } finally {
302 IOUtil.close(writer);
303 }
304 } else {
305 if (LOGGER.isDebugEnabled()) {
306 LOGGER.debug(inputFile + " unchanged, not regenerating...");
307 }
308 }
309 }
310 }
311
312
313 public void renderDocument(
314 Writer writer, DocumentRenderingContext docRenderingContext, SiteRenderingContext siteContext)
315 throws RendererException {
316 SiteRendererSink sink = new SiteRendererSink(docRenderingContext);
317
318 File doc = new File(docRenderingContext.getBasedir(), docRenderingContext.getInputName());
319
320 Reader reader = null;
321 try {
322 String resource = doc.getAbsolutePath();
323
324 Parser parser = doxia.getParser(docRenderingContext.getParserId());
325 ParserConfigurator configurator = siteContext.getParserConfigurator();
326 boolean isConfigured = false;
327 if (configurator != null) {
328 isConfigured = configurator.configure(docRenderingContext.getParserId(), doc.toPath(), parser);
329 }
330 if (!isConfigured) {
331
332 parser.setEmitComments(false);
333 parser.setEmitAnchorsForIndexableEntries(true);
334 }
335
336
337 if (docRenderingContext.getAttribute("velocity") != null) {
338 LOGGER.debug("Processing Velocity for " + docRenderingContext.getDoxiaSourcePath());
339 try {
340 Context vc = createDocumentVelocityContext(docRenderingContext, siteContext);
341
342 StringWriter sw = new StringWriter();
343
344 velocity.getEngine().mergeTemplate(resource, siteContext.getInputEncoding(), vc, sw);
345
346 String doxiaContent = sw.toString();
347
348 if (siteContext.getProcessedContentOutput() != null) {
349
350 saveVelocityProcessedContent(docRenderingContext, siteContext, doxiaContent);
351 }
352
353 reader = new StringReader(doxiaContent);
354 } catch (VelocityException e) {
355 throw new RendererException(
356 "Error parsing " + docRenderingContext.getDoxiaSourcePath() + " as a Velocity template", e);
357 }
358
359 if (parser.getType() == Parser.XML_TYPE && siteContext.isValidate()) {
360 reader = validate(reader, resource);
361 }
362 } else {
363 switch (parser.getType()) {
364 case Parser.XML_TYPE:
365 reader = ReaderFactory.newXmlReader(doc);
366 if (siteContext.isValidate()) {
367 reader = validate(reader, resource);
368 }
369 break;
370
371 case Parser.TXT_TYPE:
372 case Parser.UNKNOWN_TYPE:
373 default:
374 reader = ReaderFactory.newReader(doc, siteContext.getInputEncoding());
375 }
376 }
377
378 doxia.parse(reader, docRenderingContext.getParserId(), sink, docRenderingContext.getDoxiaSourcePath());
379 } catch (ParserNotFoundException e) {
380 throw new RendererException("Error getting a parser for '" + doc + "'", e);
381 } catch (ParseException e) {
382 StringBuilder errorMsgBuilder = new StringBuilder();
383 errorMsgBuilder.append("Error parsing '").append(doc).append("'");
384 if (e.getLineNumber() > 0) {
385 errorMsgBuilder.append(", line ").append(e.getLineNumber());
386 }
387 throw new RendererException(errorMsgBuilder.toString(), e);
388 } catch (IOException e) {
389 throw new RendererException("Error while processing '" + doc + "'", e);
390 } finally {
391 sink.flush();
392
393 sink.close();
394
395 IOUtil.close(reader);
396 }
397
398 mergeDocumentIntoSite(writer, (DocumentContent) sink, siteContext);
399 }
400
401 private void saveVelocityProcessedContent(
402 DocumentRenderingContext docRenderingContext, SiteRenderingContext siteContext, String doxiaContent)
403 throws IOException {
404 if (!siteContext.getProcessedContentOutput().exists()) {
405 siteContext.getProcessedContentOutput().mkdirs();
406 }
407
408 String inputPath = docRenderingContext.getInputName();
409
410 File outputFile =
411 new File(siteContext.getProcessedContentOutput(), inputPath.substring(0, inputPath.length() - 3));
412
413 File outputParent = outputFile.getParentFile();
414 if (!outputParent.exists()) {
415 outputParent.mkdirs();
416 }
417
418 FileUtils.fileWrite(outputFile, siteContext.getInputEncoding(), doxiaContent);
419 }
420
421
422
423
424
425
426
427 protected Context createToolManagedVelocityContext(SiteRenderingContext siteRenderingContext) {
428 Locale locale = siteRenderingContext.getLocale();
429 String dateFormat = siteRenderingContext.getSiteModel().getPublishDate().getFormat();
430 String timeZoneId = siteRenderingContext.getSiteModel().getPublishDate().getTimezone();
431 TimeZone timeZone =
432 "system".equalsIgnoreCase(timeZoneId) ? TimeZone.getDefault() : TimeZone.getTimeZone(timeZoneId);
433
434 EasyFactoryConfiguration config = new EasyFactoryConfiguration(false);
435 config.property("safeMode", Boolean.FALSE);
436 config.toolbox(Scope.REQUEST)
437 .tool(ContextTool.class)
438 .tool(LinkTool.class)
439 .tool(LoopTool.class)
440 .tool(RenderTool.class);
441 config.toolbox(Scope.APPLICATION)
442 .property("locale", locale)
443 .tool(AlternatorTool.class)
444 .tool(ClassTool.class)
445 .tool(ComparisonDateTool.class)
446 .property("format", dateFormat)
447 .property("timezone", timeZone)
448 .tool(ConversionTool.class)
449 .property("dateFormat", dateFormat)
450 .tool(DisplayTool.class)
451 .tool(EscapeTool.class)
452 .tool(FieldTool.class)
453 .tool(MathTool.class)
454 .tool(NumberTool.class)
455 .tool(ResourceTool.class)
456 .property("bundles", new String[] {"site-renderer"})
457 .tool(SortTool.class)
458 .tool(XmlTool.class);
459
460 FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath(TOOLS_LOCATION);
461
462 if (customConfig != null) {
463 config.addConfiguration(customConfig);
464 }
465
466 ToolManager manager = new ToolManager(false, false);
467 manager.configure(config);
468
469 return manager.createContext();
470 }
471
472
473
474
475
476
477
478
479 protected Context createDocumentVelocityContext(
480 DocumentRenderingContext docRenderingContext, SiteRenderingContext siteRenderingContext) {
481 Context context = createToolManagedVelocityContext(siteRenderingContext);
482
483
484
485
486 context.put("relativePath", docRenderingContext.getRelativePath());
487
488 String currentFilePath = docRenderingContext.getOutputName();
489 context.put("currentFilePath", currentFilePath);
490
491 context.put("currentFileName", currentFilePath);
492
493 String alignedFilePath = PathTool.calculateLink(currentFilePath, docRenderingContext.getRelativePath());
494 context.put("alignedFilePath", alignedFilePath);
495
496 context.put("alignedFileName", alignedFilePath);
497
498 context.put("site", siteRenderingContext.getSiteModel());
499
500 context.put("decoration", siteRenderingContext.getSiteModel());
501
502 context.put("locale", siteRenderingContext.getLocale());
503 context.put("supportedLocales", Collections.unmodifiableList(siteRenderingContext.getSiteLocales()));
504
505 context.put("publishDate", siteRenderingContext.getPublishDate());
506
507 if (DOXIA_SITE_RENDERER_VERSION != null) {
508 context.put("doxiaSiteRendererVersion", DOXIA_SITE_RENDERER_VERSION);
509 }
510
511
512 Map<String, ?> templateProperties = siteRenderingContext.getTemplateProperties();
513
514 if (templateProperties != null) {
515 for (Map.Entry<String, ?> entry : templateProperties.entrySet()) {
516 context.put(entry.getKey(), entry.getValue());
517 }
518 }
519
520
521
522
523
524 context.put("PathTool", new PathTool());
525
526 context.put("StringUtils", new StringUtils());
527
528 context.put("plexus", plexus);
529 return context;
530 }
531
532
533
534
535
536
537
538
539
540 protected Context createSiteTemplateVelocityContext(
541 DocumentContent content, SiteRenderingContext siteRenderingContext) {
542
543 Context context = createDocumentVelocityContext(content.getRenderingContext(), siteRenderingContext);
544
545
546
547
548 context.put("authors", content.getAuthors());
549
550 String shortTitle = content.getTitle();
551 context.put("shortTitle", shortTitle);
552
553 String projectTitle = null;
554 if (StringUtils.isNotEmpty(siteRenderingContext.getSiteModel().getName())) {
555 projectTitle = siteRenderingContext.getSiteModel().getName();
556 } else if (StringUtils.isNotEmpty(siteRenderingContext.getDefaultTitle())) {
557 projectTitle = siteRenderingContext.getDefaultTitle();
558 }
559
560 StringBuilder title = new StringBuilder();
561 if (StringUtils.isNotEmpty(shortTitle)) {
562 title.append(shortTitle);
563 }
564
565 if (title.length() > 0 && StringUtils.isNotEmpty(projectTitle)) {
566 title.append(" \u2013 ");
567 }
568
569 if (StringUtils.isNotEmpty(projectTitle)) {
570 title.append(projectTitle);
571 }
572
573 context.put("title", title.length() > 0 ? title.toString() : null);
574
575 context.put("headContent", content.getHead());
576
577 context.put("bodyContent", content.getBody());
578
579
580 context.put("documentDate", content.getDate());
581
582
583 context.put("docRenderingContext", content.getRenderingContext());
584
585 return context;
586 }
587
588 public void generateDocument(Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext)
589 throws RendererException {
590 mergeDocumentIntoSite(writer, sink, siteRenderingContext);
591 }
592
593
594 public void mergeDocumentIntoSite(Writer writer, DocumentContent content, SiteRenderingContext siteRenderingContext)
595 throws RendererException {
596 String templateName = siteRenderingContext.getTemplateName();
597
598 LOGGER.debug("Processing Velocity for template " + templateName + " on "
599 + content.getRenderingContext().getDoxiaSourcePath());
600
601 Context context = createSiteTemplateVelocityContext(content, siteRenderingContext);
602
603 ClassLoader old = null;
604
605 if (siteRenderingContext.getTemplateClassLoader() != null) {
606
607
608
609
610 old = Thread.currentThread().getContextClassLoader();
611
612 Thread.currentThread().setContextClassLoader(siteRenderingContext.getTemplateClassLoader());
613 }
614
615 try {
616 Template template;
617 Artifact skin = siteRenderingContext.getSkin();
618
619 try {
620 SkinModel skinModel = siteRenderingContext.getSkinModel();
621 String encoding = (skinModel == null) ? null : skinModel.getEncoding();
622
623 template = (encoding == null)
624 ? velocity.getEngine().getTemplate(templateName)
625 : velocity.getEngine().getTemplate(templateName, encoding);
626 } catch (ParseErrorException pee) {
627 throw new RendererException(
628 "Velocity parsing error while reading the site template " + "from " + skin.getId() + " skin",
629 pee);
630 } catch (ResourceNotFoundException rnfe) {
631 throw new RendererException(
632 "Could not find the site template " + "from " + skin.getId() + " skin", rnfe);
633 }
634
635 try {
636 StringWriter sw = new StringWriter();
637 template.merge(context, sw);
638 writer.write(sw.toString().replaceAll("\r?\n", SystemUtils.LINE_SEPARATOR));
639 } catch (VelocityException ve) {
640 throw new RendererException("Velocity error while merging site template.", ve);
641 } catch (IOException ioe) {
642 throw new RendererException("IO exception while merging site template.", ioe);
643 }
644 } finally {
645 IOUtil.close(writer);
646
647 if (old != null) {
648 Thread.currentThread().setContextClassLoader(old);
649 }
650 }
651 }
652
653 private SiteRenderingContext createSiteRenderingContext(
654 Map<String, ?> attributes, SiteModel siteModel, String defaultTitle, Locale locale) {
655 SiteRenderingContext context = new SiteRenderingContext();
656
657 context.setTemplateProperties(attributes);
658 context.setLocale(locale);
659 context.setSiteModel(siteModel);
660 context.setDefaultTitle(defaultTitle);
661
662 return context;
663 }
664
665
666 public SiteRenderingContext createContextForSkin(
667 Artifact skin, Map<String, ?> attributes, SiteModel siteModel, String defaultTitle, Locale locale)
668 throws IOException, RendererException {
669 SiteRenderingContext context = createSiteRenderingContext(attributes, siteModel, defaultTitle, locale);
670
671 context.setSkin(skin);
672
673 ZipFile zipFile = getZipFile(skin.getFile());
674 InputStream in = null;
675
676 try {
677 if (zipFile.getEntry(SKIN_TEMPLATE_LOCATION) == null) {
678 throw new RendererException("Skin does not contain template at " + SKIN_TEMPLATE_LOCATION);
679 }
680 context.setTemplateName(SKIN_TEMPLATE_LOCATION);
681 context.setTemplateClassLoader(
682 new URLClassLoader(new URL[] {skin.getFile().toURI().toURL()}));
683
684 ZipEntry skinDescriptorEntry = zipFile.getEntry(SkinModel.SKIN_DESCRIPTOR_LOCATION);
685 if (skinDescriptorEntry != null) {
686 in = zipFile.getInputStream(skinDescriptorEntry);
687
688 SkinModel skinModel = new SkinXpp3Reader().read(in);
689 context.setSkinModel(skinModel);
690
691 String toolsPrerequisite = skinModel.getPrerequisites() == null
692 ? null
693 : skinModel.getPrerequisites().getDoxiaSitetools();
694
695 Package p = DefaultSiteRenderer.class.getPackage();
696 String current = (p == null) ? null : p.getImplementationVersion();
697
698 if (StringUtils.isNotBlank(toolsPrerequisite)
699 && (current != null)
700 && !matchVersion(current, toolsPrerequisite)) {
701 throw new RendererException("Cannot use skin: has " + toolsPrerequisite
702 + " Doxia Sitetools prerequisite, but current is " + current);
703 }
704 }
705 } catch (XmlPullParserException e) {
706 throw new RendererException(
707 "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION + " skin descriptor from " + skin.getId()
708 + " skin",
709 e);
710 } finally {
711 IOUtil.close(in);
712 closeZipFile(zipFile);
713 }
714
715 return context;
716 }
717
718 boolean matchVersion(String current, String prerequisite) throws RendererException {
719 try {
720 ArtifactVersion v = new DefaultArtifactVersion(current);
721 VersionRange vr = VersionRange.createFromVersionSpec(prerequisite);
722
723 boolean matched = false;
724 ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
725 if (recommendedVersion == null) {
726 List<Restriction> restrictions = vr.getRestrictions();
727 for (Restriction restriction : restrictions) {
728 if (restriction.containsVersion(v)) {
729 matched = true;
730 break;
731 }
732 }
733 } else {
734
735 @SuppressWarnings("unchecked")
736 int compareTo = recommendedVersion.compareTo(v);
737 matched = (compareTo <= 0);
738 }
739
740 if (LOGGER.isDebugEnabled()) {
741 LOGGER.debug("Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
742 + ", matched = " + matched);
743 }
744
745 return matched;
746 } catch (InvalidVersionSpecificationException e) {
747 throw new RendererException("Invalid skin doxia-sitetools prerequisite: " + prerequisite, e);
748 }
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 (SiteDirectory siteDirectory : siteRenderingContext.getSiteDirectories()) {
782 File resourcesDirectory = new File(siteDirectory.getPath(), "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 }