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 35fdb4a0..52dd8f29 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 @@ 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 classpath: the resource is read from the classpath. * Otherwise, it is handled as a filesystem path, either absolute, or relative to ${project.basedir} + * + * @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 classpath: the resource is read from the classpath. + * Otherwise, it is handled as a filesystem path, either absolute, or relative to ${project.basedir} + *

+ * 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. + *

+ * Example + *

+ * If location points at the following rule set: + * + *

{@code
+     * 
+     *   
+     *     
+     *        
+     *          com.google.code.findbugs:jsr305
+     *          com.google.guava:listenablefuture
+     *        
+     *     
+     *   
+     * 
+     * }
+ * + * And if xsltLocation points at the following transformation + * + *
{@code
+     * 
+     *   
+     *
+     *   
+     *   
+     *     
+     *       
+     *     
+     *   
+     *
+     *   
+     *   
+     * 
+     * }
+ * + * Then the effective rule set will look like to following: + * + *
{@code
+     * 
+     *   
+     *     
+     *        
+     *          com.google.guava:listenablefuture
+     *        
+     *     
+     *   
+     * 
+     * }
+ * + * @since 3.6.0 + */ + private String xsltLocation; + private final MojoExecution mojoExecution; private final ExpressionEvaluator evaluator; @@ -65,10 +140,14 @@ public void setLocation(String location) { 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 Xpp3Dom getRulesConfig() throws EnforcerRuleError { } } - 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 @@ private InputStream resolveDescriptor() throws EnforcerRuleError { 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 @@ private InputStream resolveDescriptor() throws EnforcerRuleError { @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/test/java/org/apache/maven/enforcer/rules/TestExternalRules.java b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestExternalRules.java index 78af21ba..53a27cd4 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 @@ void shouldLoadRulesFromClassPath() throws EnforcerRuleException { 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 00000000..af7192f6 --- /dev/null +++ b/enforcer-rules/src/test/resources/enforcer-rules/allow-findbugs.xsl @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + 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 00000000..ee5efd65 --- /dev/null +++ b/enforcer-rules/src/test/resources/enforcer-rules/banned-dependencies.xml @@ -0,0 +1,31 @@ + + + + + + + + + com.google.code.findbugs:jsr305 + com.google.guava:listenablefuture + + + + \ No newline at end of file