This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature/conditional-skin-resources in repository https://gitbox.apache.org/repos/asf/maven-doxia-sitetools.git
commit dec562d83a0c4532b17d070e9e1616ca02faa93b Author: Konrad Windszus <[email protected]> AuthorDate: Sat Jan 10 20:59:12 2026 +0100 Support conditional resources from skins Extend Skin descriptor with resource conditions. All resources without a condition are always included, the other ones only if the condition evaluates to true. This closes #609 --- doxia-site-renderer/pom.xml | 16 ++++ .../doxia/siterenderer/DefaultSiteRenderer.java | 75 ++++++++++++----- .../siterenderer/DefaultSiteRendererTest.java | 93 +++++++++++++++------- .../src/test/resources/site/site.xml | 5 ++ .../resources/skin-minimal/META-INF/maven/site.vm | 1 + .../META-INF/maven/site.vm | 1 + .../META-INF/maven/skin.xml | 37 +++++++++ .../skin-with-conditional-resources/js/exclude.js | 1 + .../skin-with-conditional-resources/js/include.js | 1 + .../skin-with-conditional-resources/js/include2.js | 1 + doxia-skin-model/pom.xml | 2 +- doxia-skin-model/src/main/mdo/skin.mdo | 39 ++++++++- 12 files changed, 223 insertions(+), 49 deletions(-) diff --git a/doxia-site-renderer/pom.xml b/doxia-site-renderer/pom.xml index 3f51a0c..53e7f52 100644 --- a/doxia-site-renderer/pom.xml +++ b/doxia-site-renderer/pom.xml @@ -37,6 +37,22 @@ under the License. <velocityToolsVersion>3.1</velocityToolsVersion> </properties> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.rat</groupId> + <artifactId>apache-rat-plugin</artifactId> + <configuration> + <excludes combine.children="append"> + <exclude>src/test/resources/**/*.vm</exclude> + <exclude>src/test/resources/**/*.js</exclude> + </excludes> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> <dependencies> <dependency> <groupId>org.apache.maven</groupId> diff --git a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java index 9e951c5..86284e2 100644 --- a/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java +++ b/doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java @@ -37,6 +37,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -66,12 +67,14 @@ import org.apache.maven.doxia.parser.manager.ParserNotFoundException; import org.apache.maven.doxia.parser.module.ParserModule; import org.apache.maven.doxia.parser.module.ParserModuleManager; import org.apache.maven.doxia.site.SiteModel; +import org.apache.maven.doxia.site.skin.ResourceCondition; import org.apache.maven.doxia.site.skin.SkinModel; import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader; import org.apache.maven.doxia.siterenderer.SiteRenderingContext.SiteDirectory; import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink; import org.apache.maven.doxia.util.XmlValidator; import org.apache.velocity.Template; +import org.apache.velocity.app.Velocity; import org.apache.velocity.context.Context; import org.apache.velocity.exception.ParseErrorException; import org.apache.velocity.exception.ResourceNotFoundException; @@ -520,29 +523,30 @@ public class DefaultSiteRenderer implements Renderer { /** * Create a Velocity Context for a Doxia document, containing every information about rendered document. * - * @param docRenderingContext the document's rendering context + * @param docRenderingContext the document's rendering context (may be null in which case the context does not contain document specific information) * @param siteRenderingContext the site rendering context * @return a Velocity tools managed context */ protected Context createDocumentVelocityContext( DocumentRenderingContext docRenderingContext, SiteRenderingContext siteRenderingContext) { Context context = createToolManagedVelocityContext(siteRenderingContext); - // ---------------------------------------------------------------------- - // Data objects - // ---------------------------------------------------------------------- - - context.put("relativePath", docRenderingContext.getRelativePath()); - - String currentFilePath = docRenderingContext.getOutputName(); - context.put("currentFilePath", currentFilePath); - // TODO Deprecated -- will be removed! - context.put("currentFileName", currentFilePath); - - String alignedFilePath = PathTool.calculateLink(currentFilePath, docRenderingContext.getRelativePath()); - context.put("alignedFilePath", alignedFilePath); - // TODO Deprecated -- will be removed! - context.put("alignedFileName", alignedFilePath); - + if (docRenderingContext != null) { + // ---------------------------------------------------------------------- + // Data objects + // ---------------------------------------------------------------------- + + context.put("relativePath", docRenderingContext.getRelativePath()); + + String currentFilePath = docRenderingContext.getOutputName(); + context.put("currentFilePath", currentFilePath); + // TODO Deprecated -- will be removed! + context.put("currentFileName", currentFilePath); + + String alignedFilePath = PathTool.calculateLink(currentFilePath, docRenderingContext.getRelativePath()); + context.put("alignedFilePath", alignedFilePath); + // TODO Deprecated -- will be removed! + context.put("alignedFileName", alignedFilePath); + } context.put("site", siteRenderingContext.getSiteModel()); // TODO Deprecated -- will be removed! context.put("decoration", siteRenderingContext.getSiteModel()); @@ -802,6 +806,8 @@ public class DefaultSiteRenderer implements Renderer { public void copyResources(SiteRenderingContext siteRenderingContext, File outputDirectory) throws IOException { ZipFile file = getZipFile(siteRenderingContext.getSkin().getFile()); + Context velocityContext = createDocumentVelocityContext(null, siteRenderingContext); + Map<String, String> resourceConditions = createResourceConditionsMap(siteRenderingContext.getSkinModel()); try { for (Enumeration<? extends ZipEntry> e = file.entries(); e.hasMoreElements(); ) { ZipEntry entry = e.nextElement(); @@ -814,7 +820,9 @@ public class DefaultSiteRenderer implements Renderer { // resource continue; } - + if (!isResourceRelevant(entry.getName(), velocityContext, resourceConditions)) { + continue; + } destFile.getParentFile().mkdirs(); copyFileFromZip(file, entry, destFile); @@ -862,6 +870,37 @@ public class DefaultSiteRenderer implements Renderer { } } + private boolean isResourceRelevant(String name, Context velocityContext, Map<String, String> resourceConditions) { + if (resourceConditions == null + || !resourceConditions.containsKey(name)) { + LOGGER.debug("No condition for resource: " + name); + } else { + String condition = resourceConditions.get(name); + LOGGER.debug("Evaluating condition for resource: " + name + " with condition: " + condition); + StringWriter writer = new StringWriter(); + Velocity.evaluate(velocityContext, writer, "conditional-resource-evaluation", condition); + String result = writer.toString().trim(); + LOGGER.debug("Condition evaluation result: " + result); + if (!Boolean.parseBoolean(result)) { + LOGGER.debug("Excluding resource: " + name); + return false; + } + } + return true; + } + + private Map<String, String> createResourceConditionsMap(SkinModel skinModel) { + Map<String, String> resourceConditions = new HashMap<>(); + if (skinModel != null && skinModel.getResourceConditions() != null) { + for (ResourceCondition resource : skinModel.getResourceConditions()) { + if (resource.getVtlCondition() != null && !resource.getVtlCondition().isEmpty()) { + resourceConditions.put(resource.getResourceName(), resource.getVtlCondition()); + } + } + } + return resourceConditions; + } + private static void copyFileFromZip(ZipFile file, ZipEntry entry, File destFile) throws IOException { FileOutputStream fos = new FileOutputStream(destFile); diff --git a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java index a909127..88ec2d0 100644 --- a/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java +++ b/doxia-site-renderer/src/test/java/org/apache/maven/doxia/siterenderer/DefaultSiteRendererTest.java @@ -20,7 +20,6 @@ package org.apache.maven.doxia.siterenderer; import javax.inject.Inject; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -28,7 +27,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.net.URI; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -37,6 +43,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.jar.JarOutputStream; +import java.util.stream.Stream; import java.util.zip.ZipEntry; import org.apache.commons.io.IOUtils; @@ -98,10 +105,6 @@ public class DefaultSiteRendererTest { @Inject private PlexusContainer container; - private File skinJar = new File(getBasedir(), "target/test-classes/skin.jar"); - - private File minimalSkinJar = new File(getBasedir(), "target/test-classes/minimal-skin.jar"); - /** * @throws java.lang.Exception if something goes wrong. */ @@ -109,27 +112,33 @@ public class DefaultSiteRendererTest { protected void setUp() throws Exception { siteRenderer = (SiteRenderer) container.lookup(SiteRenderer.class); - InputStream skinIS = getClass().getResourceAsStream("velocity-toolmanager.vm"); - JarOutputStream jarOS = new JarOutputStream(new FileOutputStream(skinJar)); - try { - jarOS.putNextEntry(new ZipEntry("META-INF/maven/site.vm")); - IOUtil.copy(skinIS, jarOS); - jarOS.closeEntry(); - } finally { - IOUtil.close(skinIS); - IOUtil.close(jarOS); + } + + private void createJarFromDirectory(Path srcDirectory, Path jarfile) throws IOException { + Map<String, String> env = new HashMap<>(); + env.put("create", "true"); + try (FileSystem zipfs = FileSystems.newFileSystem(URI.create("jar:file:" + jarfile.toString()), env)) { + deepCopy(srcDirectory, zipfs); } + } - skinIS = new ByteArrayInputStream( - "<main id=\"contentBox\">$bodyContent</main>".getBytes(StandardCharsets.UTF_8)); - jarOS = new JarOutputStream(new FileOutputStream(minimalSkinJar)); - try { - jarOS.putNextEntry(new ZipEntry("META-INF/maven/site.vm")); - IOUtil.copy(skinIS, jarOS); - jarOS.closeEntry(); - } finally { - IOUtil.close(skinIS); - IOUtil.close(jarOS); + private void deepCopy(Path src, FileSystem zipfs) throws IOException { + try (Stream<Path> stream = Files.walk(src)) { + stream.forEach(source -> { + try { + // skip root + if (!src.equals(source)) { + Path relativeSrcPath = src.relativize(source); + if (Files.isDirectory(source)) { + Files.createDirectories(zipfs.getPath(relativeSrcPath.toString())); + } else { + Files.copy(source, zipfs.getPath(relativeSrcPath.toString()), StandardCopyOption.REPLACE_EXISTING); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); } } @@ -211,6 +220,10 @@ public class DefaultSiteRendererTest { void render() throws Exception { // Safety org.apache.commons.io.FileUtils.deleteDirectory(getTestFile(OUTPUT)); + + + File minimalSkinJar = new File(getBasedir(), "target/test-classes/minimal-skin.jar"); + createJarFromDirectory(getTestFile("src/test/resources/skin-minimal").toPath(), minimalSkinJar.toPath()); // ---------------------------------------------------------------------- // Render the site from src/test/resources/site to OUTPUT @@ -218,11 +231,11 @@ public class DefaultSiteRendererTest { SiteModel siteModel = new SiteXpp3Reader().read(new FileInputStream(getTestFile("src/test/resources/site/site.xml"))); - SiteRenderingContext ctxt = getSiteRenderingContext(siteModel, "src/test/resources/site", false); + SiteRenderingContext ctxt = getSiteRenderingContext(siteModel, minimalSkinJar, "src/test/resources/site", false); ctxt.setRootDirectory(getTestFile("")); siteRenderer.render(siteRenderer.locateDocumentFiles(ctxt, true).values(), ctxt, getTestFile(OUTPUT)); - ctxt = getSiteRenderingContext(siteModel, "src/test/resources/site-validate", true); + ctxt = getSiteRenderingContext(siteModel, minimalSkinJar, "src/test/resources/site-validate", true); ctxt.setRootDirectory(getTestFile("")); siteRenderer.render(siteRenderer.locateDocumentFiles(ctxt, true).values(), ctxt, getTestFile(OUTPUT)); @@ -296,7 +309,13 @@ public class DefaultSiteRendererTest { void velocityToolManagerForSkin() throws Exception { StringWriter writer = new StringWriter(); - File skinFile = skinJar; + File skinFile = new File(getBasedir(), "target/test-classes/skin.jar"); + try (InputStream skinIS = getClass().getResourceAsStream("velocity-toolmanager.vm"); + JarOutputStream jarOS = new JarOutputStream(new FileOutputStream(skinFile))) { + jarOS.putNextEntry(new ZipEntry("META-INF/maven/site.vm")); + IOUtil.copy(skinIS, jarOS); + jarOS.closeEntry(); + } Map<String, Object> attributes = new HashMap<>(); @@ -362,9 +381,25 @@ public class DefaultSiteRendererTest { assertEquals(expectedOutputFiles, outputFiles); } - private SiteRenderingContext getSiteRenderingContext(SiteModel siteModel, String siteDir, boolean validate) + @Test + void copyResourcesConditionally() throws Exception { + File skinJar = new File(getBasedir(), "target/test-classes/skin-with-conditional-resources.jar"); + createJarFromDirectory(getTestFile("src/test/resources/skin-with-conditional-resources").toPath(), skinJar.toPath()); + + SiteModel siteModel = + new SiteXpp3Reader().read(new FileInputStream(getTestFile("src/test/resources/site/site.xml"))); + SiteRenderingContext context = getSiteRenderingContext(siteModel, skinJar, "src/test/resources/site", false); + File sourceDirectory = getTestFile("src/test/resources/site-validate"); + context.setRootDirectory(sourceDirectory); + Path outputDirectory = Files.createTempDirectory("site-output-"); + siteRenderer.copyResources(context, outputDirectory.toFile()); + assertTrue(Files.exists(outputDirectory.resolve("js/include.js"))); + assertTrue(Files.exists(outputDirectory.resolve("js/include2.js"))); + assertFalse(Files.exists(outputDirectory.resolve("js/exclude.js"))); + } + + private SiteRenderingContext getSiteRenderingContext(SiteModel siteModel, File skinFile, String siteDir, boolean validate) throws RendererException, IOException { - File skinFile = minimalSkinJar; final Map<String, String> attributes = new HashMap<>(); attributes.put("outputEncoding", "UTF-8"); diff --git a/doxia-site-renderer/src/test/resources/site/site.xml b/doxia-site-renderer/src/test/resources/site/site.xml index f154b72..4c54ce5 100644 --- a/doxia-site-renderer/src/test/resources/site/site.xml +++ b/doxia-site-renderer/src/test/resources/site/site.xml @@ -55,4 +55,9 @@ under the License. </item> </menu> </body> + <custom> + <fluidoSkin> + <sourceLineNumbersEnabled>true</sourceLineNumbersEnabled> + </fluidoSkin> + </custom> </site> diff --git a/doxia-site-renderer/src/test/resources/skin-minimal/META-INF/maven/site.vm b/doxia-site-renderer/src/test/resources/skin-minimal/META-INF/maven/site.vm new file mode 100644 index 0000000..c04cadf --- /dev/null +++ b/doxia-site-renderer/src/test/resources/skin-minimal/META-INF/maven/site.vm @@ -0,0 +1 @@ +<main id="contentBox">$bodyContent</main> \ No newline at end of file diff --git a/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/META-INF/maven/site.vm b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/META-INF/maven/site.vm new file mode 100644 index 0000000..31d7361 --- /dev/null +++ b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/META-INF/maven/site.vm @@ -0,0 +1 @@ +<main id=\"contentBox\">$bodyContent</main> \ No newline at end of file diff --git a/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/META-INF/maven/skin.xml b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/META-INF/maven/skin.xml new file mode 100644 index 0000000..8e4aa4c --- /dev/null +++ b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/META-INF/maven/skin.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> + +<skin xmlns="http://maven.apache.org/SKIN/2.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/SKIN/2.1.0 http://maven.apache.org/xsd/skin-2.1.0.xsd"> + <prerequisites> + <doxia-sitetools>2.2.0</doxia-sitetools> + </prerequisites> + <resource-conditions> + <resource-condition> + <resource-name>js/exclude.js</resource-name> + <vtl-condition>false</vtl-condition> + </resource-condition> + <resource-condition> + <resource-name>js/include2.js</resource-name> + <vtl-condition><![CDATA[$site.getCustomValue( 'fluidoSkin.sourceLineNumbersEnabled', 'false' )]]></vtl-condition> + </resource-condition> + </resource-conditions> +</skin> \ No newline at end of file diff --git a/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/exclude.js b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/exclude.js new file mode 100644 index 0000000..3f71a2f --- /dev/null +++ b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/exclude.js @@ -0,0 +1 @@ +// must not be copied \ No newline at end of file diff --git a/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/include.js b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/include.js new file mode 100644 index 0000000..3f71a2f --- /dev/null +++ b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/include.js @@ -0,0 +1 @@ +// must not be copied \ No newline at end of file diff --git a/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/include2.js b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/include2.js new file mode 100644 index 0000000..3f71a2f --- /dev/null +++ b/doxia-site-renderer/src/test/resources/skin-with-conditional-resources/js/include2.js @@ -0,0 +1 @@ +// must not be copied \ No newline at end of file diff --git a/doxia-skin-model/pom.xml b/doxia-skin-model/pom.xml index ef5397c..02f403e 100644 --- a/doxia-skin-model/pom.xml +++ b/doxia-skin-model/pom.xml @@ -49,7 +49,7 @@ under the License. <model>src/main/mdo/skin.mdo</model> </models> <!-- TODO Do not forget to update the version in the description. See DOXIASITETOOLS-98. --> - <version>2.0.0</version> + <version>2.1.0</version> <firstVersion>1.7.0</firstVersion> </configuration> <executions> diff --git a/doxia-skin-model/src/main/mdo/skin.mdo b/doxia-skin-model/src/main/mdo/skin.mdo index 60bb8f1..4efac4f 100644 --- a/doxia-skin-model/src/main/mdo/skin.mdo +++ b/doxia-skin-model/src/main/mdo/skin.mdo @@ -29,7 +29,7 @@ under the License. <p>An XSD is available at:</p> <ul> <!-- There is no property filtering in Modello, this has to be updated manually. See DOXIASITETOOLS-98. --> - <li><a href="https://maven.apache.org/xsd/skin-2.0.0.xsd">https://maven.apache.org/xsd/skin-2.0.0.xsd</a></li> + <li><a href="https://maven.apache.org/xsd/skin-2.1.0.xsd">https://maven.apache.org/xsd/skin-2.1.0.xsd</a></li> </ul> ]]></description> @@ -67,6 +67,16 @@ under the License. <type>String</type> <required>false</required> </field> + <field xdoc.separator="blank" xml.tagName="resource-conditions"> + <name>resourceConditions</name> + <version>2.1.0+</version> + <description>Resource conditions</description> + <association> + <type>ResourceCondition</type> + <multiplicity>*</multiplicity> + </association> + <required>false</required> + </field> </fields> <codeSegments> <codeSegment> @@ -97,5 +107,32 @@ under the License. </field> </fields> </class> + <class java.clone="deep"> + <name>ResourceCondition</name> + <version>2.1.0+</version> + <description>Describes the condition for including a specific Skin resource.</description> + <fields> + <field xml.tagName="resource-name"> + <name>resourceName</name> + <version>2.1.0+</version> + <type>String</type> + <description><![CDATA[ + The name of the resource to which this condition applies. Refers to the path within the JAR (i.e. must always use "/" as separator and must not start with a slash). + ]]> + </description> + <required>true</required> + </field> + <field xml.tagName="vtl-condition"> + <name>vtlCondition</name> + <version>2.1.0+</version> + <type>String</type> + <description><![CDATA[ + The Velocity (VTL) expression to be evaluated to decide if the resource should be included. Only if the condition evaluates to true, the resource will be included. + ]]> + </description> + <required>true</required> + </field> + </fields> + </class> </classes> </model>
