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