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