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