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 context.put("shortTitle", content.getTitle());
551
552
553 StringBuilder title = new StringBuilder();
554 if (siteRenderingContext.getSiteModel() != null
555 && StringUtils.isNotEmpty(siteRenderingContext.getSiteModel().getName())) {
556 title.append(siteRenderingContext.getSiteModel().getName());
557 } else if (StringUtils.isNotEmpty(siteRenderingContext.getDefaultTitle())) {
558 title.append(siteRenderingContext.getDefaultTitle());
559 }
560
561 if (title.length() > 0 && StringUtils.isNotEmpty(content.getTitle())) {
562 title.append(" \u2013 ");
563 }
564 if (StringUtils.isNotEmpty(content.getTitle())) {
565 title.append(content.getTitle());
566 }
567
568 context.put("title", title.length() > 0 ? title.toString() : null);
569
570 context.put("headContent", content.getHead());
571
572 context.put("bodyContent", content.getBody());
573
574
575 context.put("documentDate", content.getDate());
576
577
578 context.put("docRenderingContext", content.getRenderingContext());
579
580 return context;
581 }
582
583 public void generateDocument(Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext)
584 throws RendererException {
585 mergeDocumentIntoSite(writer, sink, siteRenderingContext);
586 }
587
588
589 public void mergeDocumentIntoSite(Writer writer, DocumentContent content, SiteRenderingContext siteRenderingContext)
590 throws RendererException {
591 String templateName = siteRenderingContext.getTemplateName();
592
593 LOGGER.debug("Processing Velocity for template " + templateName + " on "
594 + content.getRenderingContext().getDoxiaSourcePath());
595
596 Context context = createSiteTemplateVelocityContext(content, siteRenderingContext);
597
598 ClassLoader old = null;
599
600 if (siteRenderingContext.getTemplateClassLoader() != null) {
601
602
603
604
605 old = Thread.currentThread().getContextClassLoader();
606
607 Thread.currentThread().setContextClassLoader(siteRenderingContext.getTemplateClassLoader());
608 }
609
610 try {
611 Template template;
612 Artifact skin = siteRenderingContext.getSkin();
613
614 try {
615 SkinModel skinModel = siteRenderingContext.getSkinModel();
616 String encoding = (skinModel == null) ? null : skinModel.getEncoding();
617
618 template = (encoding == null)
619 ? velocity.getEngine().getTemplate(templateName)
620 : velocity.getEngine().getTemplate(templateName, encoding);
621 } catch (ParseErrorException pee) {
622 throw new RendererException(
623 "Velocity parsing error while reading the site template " + "from " + skin.getId() + " skin",
624 pee);
625 } catch (ResourceNotFoundException rnfe) {
626 throw new RendererException(
627 "Could not find the site template " + "from " + skin.getId() + " skin", rnfe);
628 }
629
630 try {
631 StringWriter sw = new StringWriter();
632 template.merge(context, sw);
633 writer.write(sw.toString().replaceAll("\r?\n", SystemUtils.LINE_SEPARATOR));
634 } catch (VelocityException ve) {
635 throw new RendererException("Velocity error while merging site template.", ve);
636 } catch (IOException ioe) {
637 throw new RendererException("IO exception while merging site template.", ioe);
638 }
639 } finally {
640 IOUtil.close(writer);
641
642 if (old != null) {
643 Thread.currentThread().setContextClassLoader(old);
644 }
645 }
646 }
647
648 private SiteRenderingContext createSiteRenderingContext(
649 Map<String, ?> attributes, SiteModel siteModel, String defaultTitle, Locale locale) {
650 SiteRenderingContext context = new SiteRenderingContext();
651
652 context.setTemplateProperties(attributes);
653 context.setLocale(locale);
654 context.setSiteModel(siteModel);
655 context.setDefaultTitle(defaultTitle);
656
657 return context;
658 }
659
660
661 public SiteRenderingContext createContextForSkin(
662 Artifact skin, Map<String, ?> attributes, SiteModel siteModel, String defaultTitle, Locale locale)
663 throws IOException, RendererException {
664 SiteRenderingContext context = createSiteRenderingContext(attributes, siteModel, defaultTitle, locale);
665
666 context.setSkin(skin);
667
668 ZipFile zipFile = getZipFile(skin.getFile());
669 InputStream in = null;
670
671 try {
672 if (zipFile.getEntry(SKIN_TEMPLATE_LOCATION) == null) {
673 throw new RendererException("Skin does not contain template at " + SKIN_TEMPLATE_LOCATION);
674 }
675 context.setTemplateName(SKIN_TEMPLATE_LOCATION);
676 context.setTemplateClassLoader(
677 new URLClassLoader(new URL[] {skin.getFile().toURI().toURL()}));
678
679 ZipEntry skinDescriptorEntry = zipFile.getEntry(SkinModel.SKIN_DESCRIPTOR_LOCATION);
680 if (skinDescriptorEntry != null) {
681 in = zipFile.getInputStream(skinDescriptorEntry);
682
683 SkinModel skinModel = new SkinXpp3Reader().read(in);
684 context.setSkinModel(skinModel);
685
686 String toolsPrerequisite = skinModel.getPrerequisites() == null
687 ? null
688 : skinModel.getPrerequisites().getDoxiaSitetools();
689
690 Package p = DefaultSiteRenderer.class.getPackage();
691 String current = (p == null) ? null : p.getImplementationVersion();
692
693 if (StringUtils.isNotBlank(toolsPrerequisite)
694 && (current != null)
695 && !matchVersion(current, toolsPrerequisite)) {
696 throw new RendererException("Cannot use skin: has " + toolsPrerequisite
697 + " Doxia Sitetools prerequisite, but current is " + current);
698 }
699 }
700 } catch (XmlPullParserException e) {
701 throw new RendererException(
702 "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION + " skin descriptor from " + skin.getId()
703 + " skin",
704 e);
705 } finally {
706 IOUtil.close(in);
707 closeZipFile(zipFile);
708 }
709
710 return context;
711 }
712
713 boolean matchVersion(String current, String prerequisite) throws RendererException {
714 try {
715 ArtifactVersion v = new DefaultArtifactVersion(current);
716 VersionRange vr = VersionRange.createFromVersionSpec(prerequisite);
717
718 boolean matched = false;
719 ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
720 if (recommendedVersion == null) {
721 List<Restriction> restrictions = vr.getRestrictions();
722 for (Restriction restriction : restrictions) {
723 if (restriction.containsVersion(v)) {
724 matched = true;
725 break;
726 }
727 }
728 } else {
729
730 @SuppressWarnings("unchecked")
731 int compareTo = recommendedVersion.compareTo(v);
732 matched = (compareTo <= 0);
733 }
734
735 if (LOGGER.isDebugEnabled()) {
736 LOGGER.debug("Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
737 + ", matched = " + matched);
738 }
739
740 return matched;
741 } catch (InvalidVersionSpecificationException e) {
742 throw new RendererException("Invalid skin doxia-sitetools prerequisite: " + prerequisite, e);
743 }
744 }
745
746
747 public void copyResources(SiteRenderingContext siteRenderingContext, File outputDirectory) throws IOException {
748 ZipFile file = getZipFile(siteRenderingContext.getSkin().getFile());
749
750 try {
751 for (Enumeration<? extends ZipEntry> e = file.entries(); e.hasMoreElements(); ) {
752 ZipEntry entry = e.nextElement();
753
754 if (!entry.getName().startsWith("META-INF/")) {
755 File destFile = new File(outputDirectory, entry.getName());
756 if (!entry.isDirectory()) {
757 if (destFile.exists()) {
758
759
760 continue;
761 }
762
763 destFile.getParentFile().mkdirs();
764
765 copyFileFromZip(file, entry, destFile);
766 } else {
767 destFile.mkdirs();
768 }
769 }
770 }
771 } finally {
772 closeZipFile(file);
773 }
774
775
776 for (SiteDirectory siteDirectory : siteRenderingContext.getSiteDirectories()) {
777 File resourcesDirectory = new File(siteDirectory.getPath(), "resources");
778
779 if (resourcesDirectory != null && resourcesDirectory.exists()) {
780 copyDirectory(resourcesDirectory, outputDirectory);
781 }
782 }
783
784
785 File siteCssFile = new File(outputDirectory, "/css/site.css");
786 if (!siteCssFile.exists()) {
787
788 File cssDirectory = new File(outputDirectory, "/css/");
789 boolean created = cssDirectory.mkdirs();
790 if (created && LOGGER.isDebugEnabled()) {
791 LOGGER.debug("The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created.");
792 }
793
794
795 if (LOGGER.isDebugEnabled()) {
796 LOGGER.debug(
797 "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file.");
798 }
799 Writer writer = null;
800 try {
801 writer = WriterFactory.newWriter(siteCssFile, siteRenderingContext.getOutputEncoding());
802
803 writer.write("/* You can override this file with your own styles */");
804 } finally {
805 IOUtil.close(writer);
806 }
807 }
808 }
809
810 private static void copyFileFromZip(ZipFile file, ZipEntry entry, File destFile) throws IOException {
811 FileOutputStream fos = new FileOutputStream(destFile);
812
813 try {
814 IOUtil.copy(file.getInputStream(entry), fos);
815 } finally {
816 IOUtil.close(fos);
817 }
818 }
819
820
821
822
823
824
825
826
827 protected void copyDirectory(File source, File destination) throws IOException {
828 if (source.exists()) {
829 DirectoryScanner scanner = new DirectoryScanner();
830
831 String[] includedResources = {"**/*"};
832
833 scanner.setIncludes(includedResources);
834
835 scanner.addDefaultExcludes();
836
837 scanner.setBasedir(source);
838
839 scanner.scan();
840
841 List<String> includedFiles = Arrays.asList(scanner.getIncludedFiles());
842
843 for (String name : includedFiles) {
844 File sourceFile = new File(source, name);
845
846 File destinationFile = new File(destination, name);
847
848 FileUtils.copyFile(sourceFile, destinationFile);
849 }
850 }
851 }
852
853 private Reader validate(Reader source, String resource) throws ParseException, IOException {
854 LOGGER.debug("Validating: " + resource);
855
856 try {
857 String content = IOUtil.toString(new BufferedReader(source));
858
859 new XmlValidator().validate(content);
860
861 return new StringReader(content);
862 } finally {
863 IOUtil.close(source);
864 }
865 }
866
867
868 static boolean endsWithIgnoreCase(String str, String searchStr) {
869 if (str.length() < searchStr.length()) {
870 return false;
871 }
872
873 return str.regionMatches(true, str.length() - searchStr.length(), searchStr, 0, searchStr.length());
874 }
875
876 private static ZipFile getZipFile(File file) throws IOException {
877 if (file == null) {
878 throw new IOException("Error opening ZipFile: null");
879 }
880
881 try {
882
883 return new ZipFile(file);
884 } catch (ZipException ex) {
885 IOException ioe = new IOException("Error opening ZipFile: " + file.getAbsolutePath());
886 ioe.initCause(ex);
887 throw ioe;
888 }
889 }
890
891 private static void closeZipFile(ZipFile zipFile) {
892
893 try {
894 zipFile.close();
895 } catch (IOException e) {
896
897 }
898 }
899
900 private static String getSiteRendererVersion() {
901 InputStream inputStream = DefaultSiteRenderer.class.getResourceAsStream(
902 "/META-INF/" + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties");
903 if (inputStream == null) {
904 LOGGER.debug("pom.properties for doxia-site-renderer not found");
905 } else {
906 Properties properties = new Properties();
907 try (InputStream in = inputStream) {
908 properties.load(in);
909 return properties.getProperty("version");
910 } catch (IOException e) {
911 LOGGER.debug("Failed to load pom.properties, so Doxia SiteRenderer version will not be available", e);
912 }
913 }
914
915 return null;
916 }
917 }