diff --git a/.gitignore b/.gitignore
index 41ea5492f..a7ae941f6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,4 @@ target
.svn
*.iml
.checkstyle
-
+.DS_Store
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java
new file mode 100644
index 000000000..cb6d9f1d6
--- /dev/null
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java
@@ -0,0 +1,42 @@
+package org.apache.maven.plugins.enforcer;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.enforcer.rule.api.EnforcerRule;
+
+/**
+ * An enforcer rules descriptor used by {@link ExternalRules}
+ *
+ * @author George Gastaldi
+ */
+public class EnforcerDescriptor
+{
+ EnforcerRule[] rules;
+
+ public EnforcerRule[] getRules()
+ {
+ return rules;
+ }
+
+ public void setRules( EnforcerRule[] rules )
+ {
+ this.rules = rules;
+ }
+}
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/ExternalRules.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/ExternalRules.java
new file mode 100644
index 000000000..ad0e7bc12
--- /dev/null
+++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/ExternalRules.java
@@ -0,0 +1,152 @@
+package org.apache.maven.plugins.enforcer;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.enforcer.rule.api.EnforcerRule;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
+import org.apache.maven.plugin.MojoExecution;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.component.configurator.ComponentConfigurator;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.configuration.PlexusConfiguration;
+import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
+import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+
+/**
+ * An enforcer rule that will invoke rules from an external resource
+ *
+ * @author George Gastaldi
+ */
+public class ExternalRules extends AbstractNonCacheableEnforcerRule
+{
+ private static final String LOCATION_PREFIX_CLASSPATH = "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}
+ */
+ String location;
+
+ public ExternalRules()
+ {
+ }
+
+ public ExternalRules( String location )
+ {
+ this.location = location;
+ }
+
+ @Override
+ public void execute( EnforcerRuleHelper helper ) throws EnforcerRuleException
+ {
+ // Find descriptor
+ EnforcerDescriptor enforcerDescriptor = getEnforcerDescriptor( helper );
+ for ( EnforcerRule rule : enforcerDescriptor.getRules() )
+ {
+ rule.execute( helper );
+ }
+ }
+
+ /**
+ * Resolve the {@link EnforcerDescriptor} based on the provided {@link #descriptor} or {@link #descriptorRef}
+ *
+ * @param helper used to build the {@link EnforcerDescriptor}
+ * @return an {@link EnforcerDescriptor} for this rule
+ * @throws EnforcerRuleException if any failure happens while reading the descriptor
+ */
+ EnforcerDescriptor getEnforcerDescriptor( EnforcerRuleHelper helper )
+ throws EnforcerRuleException
+ {
+ try ( InputStream descriptorStream = resolveDescriptor( helper ) )
+ {
+ EnforcerDescriptor descriptor = new EnforcerDescriptor();
+ // To get configuration from the enforcer-plugin mojo do:
+ //helper.evaluate(helper.getComponent(MojoExecution.class).getConfiguration().getChild("fail").getValue())
+ // Configure EnforcerDescriptor from the XML
+ ComponentConfigurator configurator = helper.getComponent( ComponentConfigurator.class, "basic" );
+ configurator.configureComponent( descriptor, toPlexusConfiguration( descriptorStream ), helper,
+ getClassRealm( helper ) );
+ return descriptor;
+ }
+ catch ( EnforcerRuleException e )
+ {
+ throw e;
+ }
+ catch ( Exception e )
+ {
+ throw new EnforcerRuleException( "Error while enforcing rules", e );
+ }
+ }
+
+ private InputStream resolveDescriptor( EnforcerRuleHelper helper )
+ throws ComponentLookupException, EnforcerRuleException
+ {
+ InputStream descriptorStream;
+ if ( location != null )
+ {
+ if ( location.startsWith( LOCATION_PREFIX_CLASSPATH ) )
+ {
+ String classpathLocation = location.substring( LOCATION_PREFIX_CLASSPATH.length() );
+ ClassLoader classRealm = getClassRealm( helper );
+ descriptorStream = classRealm.getResourceAsStream( classpathLocation );
+ if ( descriptorStream == null )
+ {
+ throw new EnforcerRuleException( "Location '" + classpathLocation + "' not found in classpath" );
+ }
+ }
+ else
+ {
+ File descriptorFile = helper.alignToBaseDirectory( new File( location ) );
+ try
+ {
+ descriptorStream = Files.newInputStream( descriptorFile.toPath() );
+ }
+ catch ( IOException e )
+ {
+ throw new EnforcerRuleException( "Could not read descriptor in " + descriptorFile, e );
+ }
+ }
+ }
+ else
+ {
+ throw new EnforcerRuleException( "No location provided" );
+ }
+ return descriptorStream;
+ }
+
+ private static PlexusConfiguration toPlexusConfiguration( InputStream descriptorStream )
+ throws XmlPullParserException, IOException
+ {
+ return new XmlPlexusConfiguration( Xpp3DomBuilder.build( descriptorStream, "UTF-8" ) );
+ }
+
+ private ClassRealm getClassRealm( EnforcerRuleHelper helper ) throws ComponentLookupException
+ {
+ return helper.getComponent( MojoExecution.class ).getMojoDescriptor().getRealm();
+ }
+
+}
\ No newline at end of file
diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java
index bcbcccc21..afe287c1c 100644
--- a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java
+++ b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java
@@ -38,6 +38,7 @@
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.logging.SystemStreamLog;
import org.apache.maven.plugins.enforcer.utils.MockEnforcerExpressionEvaluator;
import org.apache.maven.project.MavenProject;
@@ -45,6 +46,7 @@
import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.mockito.Mockito;
@@ -121,6 +123,7 @@ public static EnforcerRuleHelper getHelper( MavenProject project )
public static EnforcerRuleHelper getHelper( MavenProject project, boolean mockExpression )
{
MavenSession session = getMavenSession();
+ MojoExecution mockExecution = mock( MojoExecution.class );
ExpressionEvaluator eval;
if ( mockExpression )
{
@@ -128,7 +131,6 @@ public static EnforcerRuleHelper getHelper( MavenProject project, boolean mockEx
}
else
{
- MojoExecution mockExecution = mock( MojoExecution.class );
session.setCurrentProject( project );
eval = new PluginParameterExpressionEvaluator( session, mockExecution );
}
@@ -154,6 +156,18 @@ public static EnforcerRuleHelper getHelper( MavenProject project, boolean mockEx
{
// test will fail
}
+ ClassWorld classWorld = new ClassWorld( "test", EnforcerTestUtils.class.getClassLoader() );
+ MojoDescriptor mojoDescriptor = new MojoDescriptor();
+ mojoDescriptor.setRealm( classWorld.getClassRealm( "test" ) );
+ when( mockExecution.getMojoDescriptor() ).thenReturn( mojoDescriptor );
+ try
+ {
+ when( container.lookup( MojoExecution.class ) ).thenReturn( mockExecution );
+ }
+ catch ( ComponentLookupException e )
+ {
+ // test will fail
+ }
return new DefaultEnforcementRuleHelper( session, eval, new SystemStreamLog(), container );
}
diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestExternalRules.java b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestExternalRules.java
new file mode 100644
index 000000000..2a3efe21a
--- /dev/null
+++ b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestExternalRules.java
@@ -0,0 +1,56 @@
+package org.apache.maven.plugins.enforcer;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+public class TestExternalRules
+{
+ @Test
+ void shouldFailIfNoLocationIsSet()
+ {
+ ExternalRules rule = new ExternalRules();
+ EnforcerRuleHelper helper = EnforcerTestUtils.getHelper();
+ assertThatExceptionOfType( EnforcerRuleException.class ).isThrownBy( () -> rule.execute( helper ) )
+ .withMessage( "No location provided" );
+ }
+
+ @Test
+ void shouldFailIfClasspathLocationIsNotFound()
+ {
+ ExternalRules rule = new ExternalRules("classpath:foo");
+ EnforcerRuleHelper helper = EnforcerTestUtils.getHelper();
+ assertThatExceptionOfType( EnforcerRuleException.class ).isThrownBy( () -> rule.execute( helper ) )
+ .withMessage( "Location 'foo' not found in classpath" );
+ }
+
+ @Test
+ void shouldFailIfFileLocationIsNotFound()
+ {
+ ExternalRules rule = new ExternalRules("blah.xml");
+ EnforcerRuleHelper helper = EnforcerTestUtils.getHelper();
+ assertThatExceptionOfType( EnforcerRuleException.class ).isThrownBy( () -> rule.execute( helper ) )
+ .withMessageMatching( "Could not read descriptor in .*blah.xml" );
+ }
+}
diff --git a/enforcer-rules/src/test/resources/enforcer-rules/pass.xml b/enforcer-rules/src/test/resources/enforcer-rules/pass.xml
new file mode 100644
index 000000000..9f9de4ec7
--- /dev/null
+++ b/enforcer-rules/src/test/resources/enforcer-rules/pass.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/enforcer-rules.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/enforcer-rules.xml
new file mode 100644
index 000000000..16c0f97b5
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/enforcer-rules.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/invoker.properties b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/invoker.properties
new file mode 100644
index 000000000..58b6526ec
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/invoker.properties
@@ -0,0 +1,18 @@
+# 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.
+
+invoker.buildResult = failure
diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/pom.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/pom.xml
new file mode 100644
index 000000000..79b09ff41
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.enforcer
+ test
+ 1.0
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ @project.version@
+
+
+ test
+
+ enforce
+
+
+
+
+ enforcer-rules.xml
+
+
+
+
+
+
+
+
+
diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/enforcer-rules.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/enforcer-rules.xml
new file mode 100644
index 000000000..9f9de4ec7
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/enforcer-rules.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/pom.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/pom.xml
new file mode 100644
index 000000000..79b09ff41
--- /dev/null
+++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.its.enforcer
+ test
+ 1.0
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ @project.version@
+
+
+ test
+
+ enforce
+
+
+
+
+ enforcer-rules.xml
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index b3fa556bf..5d880f7ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -193,6 +193,10 @@
wangyf2010@gmail.com
eBay Inc.
+
+ George Gastaldi
+ gastaldi@apache.org
+