This is an automated email from the ASF dual-hosted git repository. ppalaga pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-enforcer.git
The following commit(s) were added to refs/heads/master by this push: new 26d2ba8 [MENFORCER-507] Add xsltLocation parameter to ExternalRules 26d2ba8 is described below commit 26d2ba89b0f43a3d4fbb7b52ec92888cfd80bc8a Author: Peter Palaga <ppal...@redhat.com> AuthorDate: Mon Jul 1 15:25:02 2024 +0200 [MENFORCER-507] Add xsltLocation parameter to ExternalRules --- .../apache/maven/enforcer/rules/ExternalRules.java | 118 +++++++++++++++++++-- enforcer-rules/src/site/apt/externalRules.apt.vm | 60 +++++++++++ .../maven/enforcer/rules/TestExternalRules.java | 21 ++++ .../resources/enforcer-rules/allow-findbugs.xsl | 34 ++++++ .../enforcer-rules/banned-dependencies.xml | 31 ++++++ 5 files changed, 256 insertions(+), 8 deletions(-) diff --git a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/ExternalRules.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/ExternalRules.java index 35fdb4a..52dd8f2 100644 --- a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/ExternalRules.java +++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/ExternalRules.java @@ -20,15 +20,26 @@ package org.apache.maven.enforcer.rules; import javax.inject.Inject; import javax.inject.Named; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Objects; import org.apache.maven.enforcer.rule.api.AbstractEnforcerRuleConfigProvider; import org.apache.maven.enforcer.rule.api.EnforcerRuleError; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; import org.apache.maven.enforcer.rules.utils.ExpressionEvaluator; import org.apache.maven.plugin.MojoExecution; import org.codehaus.plexus.util.xml.Xpp3Dom; @@ -46,11 +57,75 @@ public final class ExternalRules extends AbstractEnforcerRuleConfigProvider { private static final String LOCATION_PREFIX_CLASSPATH = "classpath:"; /** - * The external rules location. If it starts with "classpath:", the resource is read from the classpath. + * The external rules location. If it starts with <code>classpath:</code> the resource is read from the classpath. * Otherwise, it is handled as a filesystem path, either absolute, or relative to <code>${project.basedir}</code> + * + * @since 3.2.0 */ private String location; + /** + * An optional location of an XSLT file used to transform the rule document available via {@link #location} before + * it is applied. If it starts with <code>classpath:</code> the resource is read from the classpath. + * Otherwise, it is handled as a filesystem path, either absolute, or relative to <code>${project.basedir}</code> + * <p> + * This is useful, when you want to consume rules defined in an external project, but you need to + * remove or adapt some of those for the local circumstances. + * <p> + * <strong>Example</strong> + * <p> + * If <code>location</code> points at the following rule set: + * + * <pre>{@code + * <enforcer> + * <rules> + * <bannedDependencies> + * <excludes> + * <exclude>com.google.code.findbugs:jsr305</exclude> + * <exclude>com.google.guava:listenablefuture</exclude> + * </excludes> + * </bannedDependencies> + * </rules> + * </enforcer> + * }</pre> + * + * And if <code>xsltLocation</code> points at the following transformation + * + * <pre>{@code + * <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + * <xsl:output omit-xml-declaration="yes"/> + * + * <!-- Copy everything unless there is a template with a more specific matcher --> + * <xsl:template match="node()|@*"> + * <xsl:copy> + * <xsl:apply-templates select="node()|@*"/> + * </xsl:copy> + * </xsl:template> + * + * <!-- An empty template will effectively remove the matching nodes --> + * <xsl:template match= + * "//bannedDependencies/excludes/exclude[contains(text(), 'com.google.code.findbugs:jsr305')]"/> + * </xsl:stylesheet> + * }</pre> + * + * Then the effective rule set will look like to following: + * + * <pre>{@code + * <enforcer> + * <rules> + * <bannedDependencies> + * <excludes> + * <exclude>com.google.guava:listenablefuture</exclude> + * </excludes> + * </bannedDependencies> + * </rules> + * </enforcer> + * }</pre> + * + * @since 3.6.0 + */ + private String xsltLocation; + private final MojoExecution mojoExecution; private final ExpressionEvaluator evaluator; @@ -65,10 +140,14 @@ public final class ExternalRules extends AbstractEnforcerRuleConfigProvider { this.location = location; } + public void setXsltLocation(String xsltLocation) { + this.xsltLocation = xsltLocation; + } + @Override public Xpp3Dom getRulesConfig() throws EnforcerRuleError { - try (InputStream descriptorStream = resolveDescriptor()) { + try (InputStream descriptorStream = transform(location, resolveDescriptor(location), xsltLocation)) { Xpp3Dom enforcerRules = Xpp3DomBuilder.build(descriptorStream, "UTF-8"); if (enforcerRules.getChildCount() == 1 && "enforcer".equals(enforcerRules.getName())) { return enforcerRules.getChild(0); @@ -80,11 +159,11 @@ public final class ExternalRules extends AbstractEnforcerRuleConfigProvider { } } - private InputStream resolveDescriptor() throws EnforcerRuleError { + private InputStream resolveDescriptor(String path) throws EnforcerRuleError { InputStream descriptorStream; - if (location != null) { - if (location.startsWith(LOCATION_PREFIX_CLASSPATH)) { - String classpathLocation = location.substring(LOCATION_PREFIX_CLASSPATH.length()); + if (path != null) { + if (path.startsWith(LOCATION_PREFIX_CLASSPATH)) { + String classpathLocation = path.substring(LOCATION_PREFIX_CLASSPATH.length()); getLog().debug("Read rules form classpath location: " + classpathLocation); ClassLoader classRealm = mojoExecution.getMojoDescriptor().getRealm(); descriptorStream = classRealm.getResourceAsStream(classpathLocation); @@ -92,7 +171,7 @@ public final class ExternalRules extends AbstractEnforcerRuleConfigProvider { throw new EnforcerRuleError("Location '" + classpathLocation + "' not found in classpath"); } } else { - File descriptorFile = evaluator.alignToBaseDirectory(new File(location)); + File descriptorFile = evaluator.alignToBaseDirectory(new File(path)); getLog().debug("Read rules form file location: " + descriptorFile); try { descriptorStream = Files.newInputStream(descriptorFile.toPath()); @@ -108,6 +187,29 @@ public final class ExternalRules extends AbstractEnforcerRuleConfigProvider { @Override public String toString() { - return String.format("ExternalRules[location=%s]", location); + return String.format("ExternalRules[location=%s, xsltLocation=%s]", location, xsltLocation); + } + + InputStream transform(String sourceLocation, InputStream sourceXml, String xsltLocation) { + if (xsltLocation == null || xsltLocation.trim().isEmpty()) { + return sourceXml; + } + + try (InputStream in = resolveDescriptor(xsltLocation); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + Transformer transformer = TransformerFactory.newInstance().newTransformer(new StreamSource(in)); + transformer.transform(new StreamSource(sourceXml), new StreamResult(baos)); + final byte[] bytes = baos.toByteArray(); + getLog().info(() -> (CharSequence) ("Rules transformed by " + xsltLocation + " from " + location + ":\n\n" + + new String(bytes, StandardCharsets.UTF_8))); + return new ByteArrayInputStream(bytes); + } catch (IOException + | EnforcerRuleException + | TransformerConfigurationException + | TransformerFactoryConfigurationError e) { + throw new RuntimeException("Could not open resource " + xsltLocation); + } catch (TransformerException e) { + throw new RuntimeException("Could not transform " + sourceLocation + " usinng XSLT " + xsltLocation); + } } } diff --git a/enforcer-rules/src/site/apt/externalRules.apt.vm b/enforcer-rules/src/site/apt/externalRules.apt.vm index fba231c..7fc1c95 100644 --- a/enforcer-rules/src/site/apt/externalRules.apt.vm +++ b/enforcer-rules/src/site/apt/externalRules.apt.vm @@ -99,3 +99,63 @@ The External Enforcer Rule Descriptor </rules> </enforcer> +---+ + +<<<xsltLocation>>>: Rule Descriptor Transformation (since Enforcer 3.6.0) + + In addition to <<<location>>> parameter, <<<externalRules>>> accepts another optional parameter <<<xsltLocation>>>. + + It is useful, when you want to consume rules defined in an external project, but you need to + remove or adapt some of those for the local circumstances. + + <<<xsltLocation>>> specifies the location of an XSLT file used to transform the rule document available via + <<<location>>> before it is applied. If it starts with <<<classpath:>>> the resource is read from the classpath. + Otherwise, it is handled as a filesystem path, either absolute, or relative to <<<$\{project.basedir\}>>>. + + Here is an example: If <<<location>>> points at the following rule set: + ++---+ +<enforcer> + <rules> + <bannedDependencies> + <excludes> + <exclude>com.google.code.findbugs:jsr305</exclude> + <exclude>com.google.guava:listenablefuture</exclude> + </excludes> + </bannedDependencies> + </rules> +</enforcer> ++---+ + + and if <<<xsltLocation>>> points at the following transformation + ++---+ +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:output omit-xml-declaration="yes"/> + + <!-- Copy everything unless there is a template with a more specific matcher --> + <xsl:template match="node()|@*"> + <xsl:copy> + <xsl:apply-templates select="node()|@*"/> + </xsl:copy> + </xsl:template> + + <!-- An empty template will effectively remove the matching nodes --> + <xsl:template match= +"//bannedDependencies/excludes/exclude[contains(text(), 'com.google.code.findbugs:jsr305')]"/> +</xsl:stylesheet> ++---+ + + Then the effective rule set will look like to following: + ++---+ +<enforcer> + <rules> + <bannedDependencies> + <excludes> + <exclude>com.google.guava:listenablefuture</exclude> + </excludes> + </bannedDependencies> + </rules> +</enforcer> ++---+ + diff --git a/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestExternalRules.java b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestExternalRules.java index 78af21b..53a27cd 100644 --- a/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestExternalRules.java +++ b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestExternalRules.java @@ -98,4 +98,25 @@ class TestExternalRules { assertNotNull(rulesConfig); assertEquals(2, rulesConfig.getChildCount()); } + + @Test + void shouldFilterRules() throws EnforcerRuleException { + MojoDescriptor mojoDescriptor = new MojoDescriptor(); + mojoDescriptor.setRealm(EnforcerTestUtils.getTestClassRealm()); + when(mojoExecution.getMojoDescriptor()).thenReturn(mojoDescriptor); + rule.setLocation("classpath:enforcer-rules/banned-dependencies.xml"); + rule.setXsltLocation("classpath:enforcer-rules/allow-findbugs.xsl"); + + Xpp3Dom rulesConfig = rule.getRulesConfig(); + assertNotNull(rulesConfig); + assertEquals(1, rulesConfig.getChildCount()); + assertEquals("bannedDependencies", rulesConfig.getChild(0).getName()); + assertEquals(1, rulesConfig.getChild(0).getChildCount()); + assertEquals("excludes", rulesConfig.getChild(0).getChild(0).getName()); + assertEquals(1, rulesConfig.getChild(0).getChild(0).getChildCount()); + assertEquals("exclude", rulesConfig.getChild(0).getChild(0).getChild(0).getName()); + assertEquals( + "com.google.guava:listenablefuture", + rulesConfig.getChild(0).getChild(0).getChild(0).getValue()); + } } diff --git a/enforcer-rules/src/test/resources/enforcer-rules/allow-findbugs.xsl b/enforcer-rules/src/test/resources/enforcer-rules/allow-findbugs.xsl new file mode 100644 index 0000000..af7192f --- /dev/null +++ b/enforcer-rules/src/test/resources/enforcer-rules/allow-findbugs.xsl @@ -0,0 +1,34 @@ +<?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. +--> + +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <xsl:output omit-xml-declaration="yes"/> + + <xsl:template match="node()|@*"> + <xsl:copy> + <xsl:apply-templates select="node()|@*"/> + </xsl:copy> + </xsl:template> + + <!-- An empty template will effectively remove the maching nodes --> + <xsl:template match="//bannedDependencies/excludes/exclude[contains(text(), 'com.google.code.findbugs:jsr305')]"/> +</xsl:stylesheet> diff --git a/enforcer-rules/src/test/resources/enforcer-rules/banned-dependencies.xml b/enforcer-rules/src/test/resources/enforcer-rules/banned-dependencies.xml new file mode 100644 index 0000000..ee5efd6 --- /dev/null +++ b/enforcer-rules/src/test/resources/enforcer-rules/banned-dependencies.xml @@ -0,0 +1,31 @@ +<?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. +--> + +<enforcer> + <rules> + <bannedDependencies> + <excludes> + <exclude>com.google.code.findbugs:jsr305</exclude> + <exclude>com.google.guava:listenablefuture</exclude> + </excludes> + </bannedDependencies> + </rules> +</enforcer> \ No newline at end of file