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