You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sj...@apache.org on 2023/01/06 16:09:33 UTC

[maven-enforcer] 01/01: [MENFORCER-456] New Enforcer API - RuleConfigProvider

This is an automated email from the ASF dual-hosted git repository.

sjaranowski pushed a commit to branch MENFORCER-456
in repository https://gitbox.apache.org/repos/asf/maven-enforcer.git

commit 66661f429633a2d1c7ce703557a962a616f60fd5
Author: Slawomir Jaranowski <s....@gmail.com>
AuthorDate: Fri Jan 6 17:08:31 2023 +0100

    [MENFORCER-456] New Enforcer API - RuleConfigProvider
---
 enforcer-api/pom.xml                               |   5 +
 .../enforcer/rule/api/AbstractEnforcerRule.java    |  25 +----
 ...uleError.java => AbstractEnforcerRuleBase.java} |  30 +++--
 ...ava => AbstractEnforcerRuleConfigProvider.java} |  33 ++++--
 .../maven/enforcer/rule/api/EnforcerRuleError.java |   4 +
 .../enforcer/rule/api/EnforcerRuleException.java   |  12 +-
 .../rules/AbstractStandardEnforcerRule.java}       |  22 ++--
 .../apache/maven/enforcer/rules/AlwaysFail.java    |  11 +-
 .../enforcer => enforcer/rules}/AlwaysPass.java    |  24 ++--
 .../apache/maven/enforcer/rules/ExternalRules.java | 113 +++++++++++++++++++
 .../maven/enforcer/rules/RequireJavaVendor.java    |  16 +--
 .../enforcer/rules/RequireNoRepositories.java      |  13 +--
 .../enforcer/rules/utils/ExpressionEvaluator.java  |  26 +++--
 .../maven/plugins/enforcer/ExternalRules.java      | 124 ---------------------
 enforcer-rules/src/site/apt/externalRules.apt.vm   |  11 +-
 .../maven/enforcer/rules/TestAlwaysFail.java       |  17 +--
 .../rules}/TestAlwaysPass.java                     |  16 ++-
 .../maven/enforcer/rules/TestExternalRules.java    | 102 +++++++++++++++++
 .../maven/plugins/enforcer/EnforcerTestUtils.java  |   9 +-
 .../maven/plugins/enforcer/TestExternalRules.java  |  54 ---------
 .../src/test/resources/enforcer-rules/pass.xml     |   1 +
 .../src/it/projects/always-pass/verify.groovy      |   2 +-
 .../external-rules-always-fail/enforcer-rules.xml  |   1 +
 .../verify.groovy                                  |   8 +-
 .../external-rules-always-pass/enforcer-rules.xml  |   1 +
 .../verify.groovy                                  |   8 +-
 .../apache/maven/plugins/enforcer/EnforceMojo.java | 120 ++++++++++----------
 .../enforcer/internal/EnforcerRuleDesc.java        |  27 ++++-
 .../enforcer/internal/EnforcerRuleManager.java     |  43 ++++++-
 .../internal}/EnforcerRuleManagerException.java    |   4 +-
 .../maven/plugins/enforcer/TestEnforceMojo.java    |  26 ++---
 .../enforcer/internal/EnforcerRuleManagerTest.java |  37 ++++--
 32 files changed, 541 insertions(+), 404 deletions(-)

diff --git a/enforcer-api/pom.xml b/enforcer-api/pom.xml
index ff3c41b..03ba97c 100644
--- a/enforcer-api/pom.xml
+++ b/enforcer-api/pom.xml
@@ -38,6 +38,11 @@
       <groupId>org.apache.maven</groupId>
       <artifactId>maven-plugin-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-utils</artifactId>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>com.google.code.findbugs</groupId>
       <artifactId>jsr305</artifactId>
diff --git a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRule.java b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRule.java
index 34c1c28..f6d6b05 100644
--- a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRule.java
+++ b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRule.java
@@ -27,36 +27,13 @@ package org.apache.maven.enforcer.rule.api;
  * @author Slawomir Jaranowski
  * @since 3.2.0
  */
-public abstract class AbstractEnforcerRule implements EnforcerRuleBase {
-
-    /**
-     * EnforcerLogger instance
-     */
-    private EnforcerLogger log;
+public abstract class AbstractEnforcerRule extends AbstractEnforcerRuleBase {
 
     /**
      * Enforcer Rule execution level
      */
     private EnforcerLevel level = EnforcerLevel.ERROR;
 
-    /**
-     * Used by {@code EnforcerMojo} to inject logger instance
-     *
-     * @param log an {@link EnforcerLogger} instance
-     */
-    public void setLog(EnforcerLogger log) {
-        this.log = log;
-    }
-
-    /**
-     * Provide an {@link  EnforcerLogger} instance for Rule
-     *
-     * @return an {@link EnforcerLogger} instance
-     */
-    public EnforcerLogger getLog() {
-        return log;
-    }
-
     /**
      * Current Enforcer execution level
      *
diff --git a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRuleBase.java
similarity index 59%
copy from enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
copy to enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRuleBase.java
index b7d7efc..57ceb74 100644
--- a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
+++ b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRuleBase.java
@@ -19,21 +19,35 @@
 package org.apache.maven.enforcer.rule.api;
 
 /**
- * An error occurring during the execution of a rule.
- * Rule can inform Enforcer plugin about critical state.
+ * Base rule implementation for new API.
  * <p>
- * This exception break a build immediate.
+ * Used for internal purpose.
  *
  * @author Slawomir Jaranowski
  * @since 3.2.0
  */
-public class EnforcerRuleError extends EnforcerRuleException {
+public abstract class AbstractEnforcerRuleBase implements EnforcerRuleBase {
 
-    public EnforcerRuleError(String message, Throwable cause) {
-        super(message, cause);
+    /**
+     * EnforcerLogger instance
+     */
+    private EnforcerLogger log;
+
+    /**
+     * Used by {@code EnforcerMojo} to inject logger instance
+     *
+     * @param log an {@link EnforcerLogger} instance
+     */
+    public void setLog(EnforcerLogger log) {
+        this.log = log;
     }
 
-    public EnforcerRuleError(String message) {
-        super(message);
+    /**
+     * Provide an {@link  EnforcerLogger} instance for Rule
+     *
+     * @return an {@link EnforcerLogger} instance
+     */
+    public EnforcerLogger getLog() {
+        return log;
     }
 }
diff --git a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRuleConfigProvider.java
similarity index 51%
copy from enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
copy to enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRuleConfigProvider.java
index b7d7efc..4cf0401 100644
--- a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
+++ b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/AbstractEnforcerRuleConfigProvider.java
@@ -18,22 +18,33 @@
  */
 package org.apache.maven.enforcer.rule.api;
 
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
 /**
- * An error occurring during the execution of a rule.
- * Rule can inform Enforcer plugin about critical state.
+ * Entry point for custom {@code Enforcer Rule} which provide additional rules configuration.
  * <p>
- * This exception break a build immediate.
+ * Provided configuration will be added to current rules list by {@code Enforcer Mojo}
  *
  * @author Slawomir Jaranowski
  * @since 3.2.0
  */
-public class EnforcerRuleError extends EnforcerRuleException {
-
-    public EnforcerRuleError(String message, Throwable cause) {
-        super(message, cause);
-    }
+public abstract class AbstractEnforcerRuleConfigProvider extends AbstractEnforcerRuleBase {
 
-    public EnforcerRuleError(String message) {
-        super(message);
-    }
+    /**
+     * Produce rule configuration.
+     * <p>
+     * Returned configuration must contain rules configuration as in example:
+     * <pre>
+     *     &lt;rules&gt;
+     *         &lt;ruleName/&gt;
+     *         &lt;ruleName&gt;
+     *             &lt;ruleConfig&gt;config value&lt;/ruleConfig&gt;
+     *         &lt;/ruleName&gt;
+     *     &lt;/rules&gt;
+     * </pre>
+     *
+     * @return a rules configuration
+     * @throws EnforcerRuleError the error during executing
+     */
+    public abstract Xpp3Dom getRulesConfig() throws EnforcerRuleError;
 }
diff --git a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
index b7d7efc..75962cd 100644
--- a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
+++ b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
@@ -36,4 +36,8 @@ public class EnforcerRuleError extends EnforcerRuleException {
     public EnforcerRuleError(String message) {
         super(message);
     }
+
+    public EnforcerRuleError(Throwable cause) {
+        super(cause);
+    }
 }
diff --git a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleException.java b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleException.java
index a8285a5..041e6d0 100644
--- a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleException.java
+++ b/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleException.java
@@ -81,7 +81,7 @@ public class EnforcerRuleException extends Exception {
      * <code>message</code>.
      *
      * @param message the message
-     * @param cause the cause
+     * @param cause   the cause
      */
     public EnforcerRuleException(String message, Exception cause) {
         super(message, cause);
@@ -108,4 +108,14 @@ public class EnforcerRuleException extends Exception {
     public EnforcerRuleException(String message) {
         super(message);
     }
+
+    /**
+     * Construct a new <code>EnforcerRuleException</code> exception wrapping
+     * an underlying <code>Throwable</code>.
+     *
+     * @param cause the cause
+     */
+    public EnforcerRuleException(Throwable cause) {
+        super(cause);
+    }
 }
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AbstractStandardEnforcerRule.java
similarity index 66%
rename from enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java
rename to enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AbstractStandardEnforcerRule.java
index b798206..8a2ae95 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AbstractStandardEnforcerRule.java
@@ -16,23 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.plugins.enforcer;
+package org.apache.maven.enforcer.rules;
 
-import org.apache.maven.enforcer.rule.api.EnforcerRule;
+import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
 
 /**
- * An enforcer rules descriptor used by {@link ExternalRules}
+ * Abstract help rule.
  *
- * @author <a href="mailto:gastaldi@apache.org">George Gastaldi</a>
+ * @author Slawomir Jaranowski
+ * @since 3.2.0
  */
-public class EnforcerDescriptor {
-    EnforcerRule[] rules;
+abstract class AbstractStandardEnforcerRule extends AbstractEnforcerRule {
 
-    public EnforcerRule[] getRules() {
-        return rules;
+    private String message;
+
+    public String getMessage() {
+        return message;
     }
 
-    public void setRules(EnforcerRule[] rules) {
-        this.rules = rules;
+    public void setMessage(String message) {
+        this.message = message;
     }
 }
diff --git a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AlwaysFail.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AlwaysFail.java
index 9ddbe41..1e1f8cf 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AlwaysFail.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AlwaysFail.java
@@ -20,7 +20,6 @@ package org.apache.maven.enforcer.rules;
 
 import javax.inject.Named;
 
-import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
 
 /**
@@ -29,16 +28,14 @@ import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
  * @author Ben Lidgey
  */
 @Named("alwaysFail")
-public final class AlwaysFail extends AbstractEnforcerRule {
-
-    private String message;
+public final class AlwaysFail extends AbstractStandardEnforcerRule {
 
     @Override
     public void execute() throws EnforcerRuleException {
 
         StringBuilder buf = new StringBuilder();
-        if (message != null) {
-            buf.append(message).append(System.lineSeparator());
+        if (getMessage() != null) {
+            buf.append(getMessage()).append(System.lineSeparator());
         }
         buf.append("Always fails!");
         throw new EnforcerRuleException(buf.toString());
@@ -46,6 +43,6 @@ public final class AlwaysFail extends AbstractEnforcerRule {
 
     @Override
     public String toString() {
-        return String.format("AlwaysFail[level=%s, message=%s]", getLevel(), message);
+        return String.format("AlwaysFail[level=%s, message=%s]", getLevel(), getMessage());
     }
 }
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/AlwaysPass.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AlwaysPass.java
similarity index 68%
rename from enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/AlwaysPass.java
rename to enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AlwaysPass.java
index 4ffca41..38f0daa 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/AlwaysPass.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/AlwaysPass.java
@@ -16,28 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.plugins.enforcer;
+package org.apache.maven.enforcer.rules;
 
-import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
-import org.apache.maven.plugin.logging.Log;
+import javax.inject.Named;
 
 /**
  * Always pass. This rule is useful for testing the Enforcer configuration.
  * @author Ben Lidgey
  */
-public class AlwaysPass extends AbstractNonCacheableEnforcerRule {
+@Named("alwaysPass")
+public final class AlwaysPass extends AbstractStandardEnforcerRule {
 
     @Override
-    public void execute(EnforcerRuleHelper helper) {
-        final Log log = helper.getLog();
-
-        String message = getMessage();
+    public void execute() {
 
         StringBuilder buf = new StringBuilder();
-        if (message != null) {
-            buf.append(message).append(System.lineSeparator());
+        if (getMessage() != null) {
+            buf.append(getMessage()).append(System.lineSeparator());
         }
         buf.append("Always pass!");
-        log.info(buf.toString());
+        getLog().info(buf.toString());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("AlwaysPass[level=%s, message=%s]", getLevel(), getMessage());
     }
 }
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
new file mode 100644
index 0000000..4a436cb
--- /dev/null
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/ExternalRules.java
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+package org.apache.maven.enforcer.rules;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+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.rules.utils.ExpressionEvaluator;
+import org.apache.maven.plugin.MojoExecution;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+/**
+ * An enforcer rule that will provide rules configuration from an external resource.
+ *
+ * @author <a href="mailto:gastaldi@apache.org">George Gastaldi</a>
+ * @since 3.2.0
+ */
+@Named("externalRules")
+public 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.
+     * Otherwise, it is handled as a filesystem path, either absolute, or relative to <code>${project.basedir}</code>
+     */
+    private String location;
+
+    private final MojoExecution mojoExecution;
+
+    private final ExpressionEvaluator evaluator;
+
+    @Inject
+    public ExternalRules(MojoExecution mojoExecution, ExpressionEvaluator evaluator) {
+        this.mojoExecution = Objects.requireNonNull(mojoExecution);
+        this.evaluator = Objects.requireNonNull(evaluator);
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    @Override
+    public Xpp3Dom getRulesConfig() throws EnforcerRuleError {
+
+        try (InputStream descriptorStream = resolveDescriptor()) {
+            Xpp3Dom enforcerRules = Xpp3DomBuilder.build(descriptorStream, "UTF-8");
+            if (enforcerRules.getChildCount() == 1 && "enforcer".equals(enforcerRules.getName())) {
+                return enforcerRules.getChild(0);
+            } else {
+                throw new EnforcerRuleError("Enforcer rules configuration not found in: " + location);
+            }
+        } catch (IOException | XmlPullParserException e) {
+            throw new EnforcerRuleError(e);
+        }
+    }
+
+    private InputStream resolveDescriptor() throws EnforcerRuleError {
+        InputStream descriptorStream;
+        if (location != null) {
+            if (location.startsWith(LOCATION_PREFIX_CLASSPATH)) {
+                String classpathLocation = location.substring(LOCATION_PREFIX_CLASSPATH.length());
+                getLog().debug("Read rules form classpath location: " + classpathLocation);
+                ClassLoader classRealm = mojoExecution.getMojoDescriptor().getRealm();
+                descriptorStream = classRealm.getResourceAsStream(classpathLocation);
+                if (descriptorStream == null) {
+                    throw new EnforcerRuleError("Location '" + classpathLocation + "' not found in classpath");
+                }
+            } else {
+                File descriptorFile = evaluator.alignToBaseDirectory(new File(location));
+                getLog().debug("Read rules form file location: " + descriptorFile);
+                try {
+                    descriptorStream = Files.newInputStream(descriptorFile.toPath());
+                } catch (IOException e) {
+                    throw new EnforcerRuleError("Could not read descriptor in " + descriptorFile, e);
+                }
+            }
+        } else {
+            throw new EnforcerRuleError("No location provided");
+        }
+        return descriptorStream;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ExternalRules[location=%s]", location);
+    }
+}
diff --git a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireJavaVendor.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireJavaVendor.java
index 805a48e..dc94632 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireJavaVendor.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireJavaVendor.java
@@ -23,7 +23,6 @@ import javax.inject.Named;
 import java.util.List;
 
 import org.apache.commons.lang3.SystemUtils;
-import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
 
 /**
@@ -34,7 +33,7 @@ import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
  * @since 3.0.0
  */
 @Named("requireJavaVendor")
-public final class RequireJavaVendor extends AbstractEnforcerRule {
+public final class RequireJavaVendor extends AbstractStandardEnforcerRule {
     /**
      * Java vendors to include. If none is defined, all are included.
      *
@@ -46,11 +45,6 @@ public final class RequireJavaVendor extends AbstractEnforcerRule {
      */
     private List<String> excludes;
 
-    /**
-     * A message used if the rule fails.
-     */
-    private String message;
-
     /**
      * The Java Vendor not changed during one Maven session,
      * so can be cached.
@@ -69,8 +63,8 @@ public final class RequireJavaVendor extends AbstractEnforcerRule {
             result += "" + excludes.hashCode();
         }
 
-        if (message != null) {
-            result += "" + message.hashCode();
+        if (getMessage() != null) {
+            result += "" + getMessage().hashCode();
         }
 
         return result;
@@ -79,6 +73,7 @@ public final class RequireJavaVendor extends AbstractEnforcerRule {
     @Override
     public void execute() throws EnforcerRuleException {
         if (excludes != null && excludes.contains(SystemUtils.JAVA_VENDOR)) {
+            String message = getMessage();
             if (message == null) {
                 message = String.format(
                         "%s is an excluded Required Java Vendor (JAVA_HOME=%s)",
@@ -86,6 +81,7 @@ public final class RequireJavaVendor extends AbstractEnforcerRule {
             }
             throw new EnforcerRuleException(message);
         } else if (includes != null && !includes.contains(SystemUtils.JAVA_VENDOR)) {
+            String message = getMessage();
             if (message == null) {
                 message = String.format(
                         "%s is not an included Required Java Vendor (JAVA_HOME=%s)",
@@ -130,6 +126,6 @@ public final class RequireJavaVendor extends AbstractEnforcerRule {
     public String toString() {
         return String.format(
                 "RequireJavaVendor[level=%s, message=%s, includes=%s, excludes=%s]",
-                getLevel(), message, includes, excludes);
+                getLevel(), getMessage(), includes, excludes);
     }
 }
diff --git a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireNoRepositories.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireNoRepositories.java
index 4c63af5..878f7c5 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireNoRepositories.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/RequireNoRepositories.java
@@ -26,7 +26,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
-import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.model.Model;
@@ -40,7 +39,7 @@ import org.codehaus.plexus.util.StringUtils;
  * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
  */
 @Named("requireNoRepositories")
-public final class RequireNoRepositories extends AbstractEnforcerRule {
+public final class RequireNoRepositories extends AbstractStandardEnforcerRule {
 
     private static final String VERSION = " version:";
 
@@ -86,8 +85,6 @@ public final class RequireNoRepositories extends AbstractEnforcerRule {
      */
     private boolean allowSnapshotPluginRepositories = false;
 
-    private String message;
-
     private final MavenSession session;
 
     @Inject
@@ -95,10 +92,6 @@ public final class RequireNoRepositories extends AbstractEnforcerRule {
         this.session = Objects.requireNonNull(session);
     }
 
-    public void setMessage(String message) {
-        this.message = message;
-    }
-
     public void setBanRepositories(boolean banRepositories) {
         this.banRepositories = banRepositories;
     }
@@ -170,8 +163,8 @@ public final class RequireNoRepositories extends AbstractEnforcerRule {
         // if anything was found, log it then append the
         // optional message.
         if (!badModels.isEmpty()) {
-            if (StringUtils.isNotEmpty(message)) {
-                newMsg.append(message);
+            if (StringUtils.isNotEmpty(getMessage())) {
+                newMsg.append(getMessage());
             }
 
             throw new EnforcerRuleException(newMsg.toString());
diff --git a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/utils/ExpressionEvaluator.java
similarity index 61%
copy from enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
copy to enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/utils/ExpressionEvaluator.java
index b7d7efc..efee10c 100644
--- a/enforcer-api/src/main/java/org/apache/maven/enforcer/rule/api/EnforcerRuleError.java
+++ b/enforcer-rules/src/main/java/org/apache/maven/enforcer/rules/utils/ExpressionEvaluator.java
@@ -16,24 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.enforcer.rule.api;
+package org.apache.maven.enforcer.rules.utils;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
 
 /**
- * An error occurring during the execution of a rule.
- * Rule can inform Enforcer plugin about critical state.
- * <p>
- * This exception break a build immediate.
+ * A configured {@code ExpressionEvaluator} used by rules.
  *
  * @author Slawomir Jaranowski
  * @since 3.2.0
  */
-public class EnforcerRuleError extends EnforcerRuleException {
-
-    public EnforcerRuleError(String message, Throwable cause) {
-        super(message, cause);
-    }
+@Named
+public class ExpressionEvaluator extends PluginParameterExpressionEvaluator {
 
-    public EnforcerRuleError(String message) {
-        super(message);
+    @Inject
+    public ExpressionEvaluator(MavenSession session, MojoExecution mojoExecution) {
+        super(session, mojoExecution);
     }
 }
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
deleted file mode 100644
index b6e39e4..0000000
--- a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/ExternalRules.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.
- */
-package org.apache.maven.plugins.enforcer;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-
-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;
-
-/**
- * An enforcer rule that will invoke rules from an external resource
- *
- * @author <a href="mailto:gastaldi@apache.org">George Gastaldi</a>
- */
-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 <code>${project.basedir}</code>
-     */
-    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();
-    }
-}
diff --git a/enforcer-rules/src/site/apt/externalRules.apt.vm b/enforcer-rules/src/site/apt/externalRules.apt.vm
index 77468ae..b5a256f 100644
--- a/enforcer-rules/src/site/apt/externalRules.apt.vm
+++ b/enforcer-rules/src/site/apt/externalRules.apt.vm
@@ -25,8 +25,9 @@
 
 External Rules
 
-    This rule will evaluate rules from an external resource. It can be a classpath resource present in a <<<maven-enforcer-plugin>>> dependency or a local file.
+    This rule will provide rules configuration from an external resource.
 
+    It can be a classpath resource present in a <<<maven-enforcer-plugin>>> dependency or a local file.
 
     Sample Plugin Configuration:
 
@@ -55,17 +56,17 @@ External Rules
                         </goals>
                         <configuration>
                             <rules>
-                                <ExternalRules>
+                                <externalRules>
                                     <!-- enforcer/rules.xml is supposed to be a classpath resource present -->
                                     <!-- in org.foo:foobar-rules dependency of maven-enforcer-plugin defined above -->
                                     <location>classpath:enforcer/rules.xml</location>
-                                </ExternalRules>
-                                <ExternalRules>
+                                </externalRules>
+                                <externalRules>
                                     <!-- You can add multiple <ExternalRules> elements if you need to enforce -->
                                     <!-- rules from multiple resources. -->
                                     <!-- src/build/rules.xml is a local file path relative to ${project.basedir} -->
                                     <location>src/build/rules.xml</location>
-                                </ExternalRules>
+                                </externalRules>
                             </rules>
                             <fail>true</fail>
                         </configuration>
diff --git a/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestAlwaysFail.java b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestAlwaysFail.java
index 6f2d245..e71ac46 100644
--- a/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestAlwaysFail.java
+++ b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestAlwaysFail.java
@@ -21,8 +21,7 @@ package org.apache.maven.enforcer.rules;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
 import org.junit.jupiter.api.Test;
 
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.assertj.core.api.Assertions.assertThatCode;
 
 /**
  * Test AlwaysFail rule.
@@ -30,16 +29,10 @@ import static org.junit.jupiter.api.Assertions.fail;
  * @author Ben Lidgey
  * @see AlwaysFail
  */
-public class TestAlwaysFail {
+class TestAlwaysFail {
     @Test
-    public void testExecute() {
-        final AlwaysFail rule = new AlwaysFail();
-        try {
-            // execute rule -- should throw EnforcerRuleException
-            rule.execute();
-            fail("Should throw EnforcerRuleException");
-        } catch (EnforcerRuleException e) {
-            assertNotNull(e.getMessage());
-        }
+    void testExecute() {
+        AlwaysFail rule = new AlwaysFail();
+        assertThatCode(rule::execute).isInstanceOf(EnforcerRuleException.class).hasMessage("Always fails!");
     }
 }
diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestAlwaysPass.java b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestAlwaysPass.java
similarity index 72%
rename from enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestAlwaysPass.java
rename to enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestAlwaysPass.java
index 9e90839..f4e0cff 100644
--- a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestAlwaysPass.java
+++ b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestAlwaysPass.java
@@ -16,21 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.plugins.enforcer;
+package org.apache.maven.enforcer.rules;
 
+import org.apache.maven.enforcer.rule.api.EnforcerLogger;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
 import org.junit.jupiter.api.Test;
 
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.Mockito.mock;
+
 /**
  * Test AlwaysPass rule.
  *
  * @author Ben Lidgey
  * @see AlwaysPass
  */
-public class TestAlwaysPass {
+class TestAlwaysPass {
+
     @Test
-    public void testExecute() throws EnforcerRuleException {
+    void testExecute() throws EnforcerRuleException {
+
         AlwaysPass rule = new AlwaysPass();
-        rule.execute(EnforcerTestUtils.getHelper());
+        rule.setLog(mock(EnforcerLogger.class));
+
+        assertThatCode(rule::execute).doesNotThrowAnyException();
     }
 }
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
new file mode 100644
index 0000000..ea6cf7b
--- /dev/null
+++ b/enforcer-rules/src/test/java/org/apache/maven/enforcer/rules/TestExternalRules.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+package org.apache.maven.enforcer.rules;
+
+import org.apache.maven.enforcer.rule.api.EnforcerLogger;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.enforcer.rules.utils.ExpressionEvaluator;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugins.enforcer.EnforcerTestUtils;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class TestExternalRules {
+
+    @Mock
+    private MojoExecution mojoExecution;
+
+    @Mock
+    private ExpressionEvaluator evaluator;
+
+    @Mock
+    private EnforcerLogger logger;
+
+    @InjectMocks
+    private ExternalRules rule;
+
+    @BeforeEach
+    void setup() {
+        rule.setLog(logger);
+    }
+
+    @Test
+    void shouldFailIfNoLocationIsSet() {
+        assertThatExceptionOfType(EnforcerRuleException.class)
+                .isThrownBy(() -> rule.getRulesConfig())
+                .withMessage("No location provided");
+    }
+
+    @Test
+    void shouldFailIfClasspathLocationIsNotFound() {
+
+        MojoDescriptor mojoDescriptor = new MojoDescriptor();
+        mojoDescriptor.setRealm(EnforcerTestUtils.getTestClassRealm());
+        when(mojoExecution.getMojoDescriptor()).thenReturn(mojoDescriptor);
+        rule.setLocation("classpath:foo");
+
+        assertThatExceptionOfType(EnforcerRuleException.class)
+                .isThrownBy(() -> rule.getRulesConfig())
+                .withMessage("Location 'foo' not found in classpath");
+    }
+
+    @Test
+    void shouldFailIfFileLocationIsNotFound() {
+        when(evaluator.alignToBaseDirectory(any())).thenAnswer(i -> i.getArgument(0));
+
+        rule.setLocation("blah.xml");
+        assertThatExceptionOfType(EnforcerRuleException.class)
+                .isThrownBy(() -> rule.getRulesConfig())
+                .withMessageMatching("Could not read descriptor in .*blah.xml");
+    }
+
+    @Test
+    void shouldLoadRulesFromClassPath() throws EnforcerRuleException {
+        MojoDescriptor mojoDescriptor = new MojoDescriptor();
+        mojoDescriptor.setRealm(EnforcerTestUtils.getTestClassRealm());
+        when(mojoExecution.getMojoDescriptor()).thenReturn(mojoDescriptor);
+        rule.setLocation("classpath:enforcer-rules/pass.xml");
+
+        Xpp3Dom rulesConfig = rule.getRulesConfig();
+        assertNotNull(rulesConfig);
+        assertEquals(2, rulesConfig.getChildCount());
+    }
+}
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 aa3d26d..cd4d953 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
@@ -37,6 +37,7 @@ import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.ProjectBuildingRequest;
 import org.codehaus.plexus.PlexusContainer;
 import org.codehaus.plexus.classworlds.ClassWorld;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
 import org.eclipse.aether.DefaultRepositorySystemSession;
 import org.eclipse.aether.RepositorySystem;
@@ -147,9 +148,8 @@ public final class EnforcerTestUtils {
             when(container.lookup(RepositorySystem.class)).thenReturn(REPOSITORY_SYSTEM);
             provideCollectDependencies();
 
-            ClassWorld classWorld = new ClassWorld("test", EnforcerTestUtils.class.getClassLoader());
             MojoDescriptor mojoDescriptor = new MojoDescriptor();
-            mojoDescriptor.setRealm(classWorld.getClassRealm("test"));
+            mojoDescriptor.setRealm(getTestClassRealm());
             when(mockExecution.getMojoDescriptor()).thenReturn(mojoDescriptor);
             when(container.lookup(MojoExecution.class)).thenReturn(mockExecution);
             return new DefaultEnforcementRuleHelper(session, eval, new SystemStreamLog(), container);
@@ -241,4 +241,9 @@ public final class EnforcerTestUtils {
                         .build())
                 .build();
     }
+
+    public static ClassRealm getTestClassRealm() {
+        ClassWorld classWorld = new ClassWorld("test", EnforcerTestUtils.class.getClassLoader());
+        return classWorld.getClassRealm("test");
+    }
 }
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
deleted file mode 100644
index fae828d..0000000
--- a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestExternalRules.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- */
-package org.apache.maven.plugins.enforcer;
-
-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
index 9f9de4e..5920220 100644
--- a/enforcer-rules/src/test/resources/enforcer-rules/pass.xml
+++ b/enforcer-rules/src/test/resources/enforcer-rules/pass.xml
@@ -22,5 +22,6 @@ under the License.
 <enforcer>
     <rules>
         <AlwaysPass/>
+        <AlwaysPass/>
     </rules>
 </enforcer>
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy b/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy
index 4079a1c..ee55f39 100644
--- a/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy
+++ b/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy
@@ -17,7 +17,7 @@
  * under the License.
  */
 File buildLog = new File( basedir, 'build.log' )
-assert buildLog.text.contains( '[INFO] Rule 0: org.apache.maven.plugins.enforcer.AlwaysPass executed' )
+assert buildLog.text.contains( '[INFO] Rule 0: org.apache.maven.enforcer.rules.AlwaysPass executed' )
 
 
  
\ 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
index 16c0f97..bcff56f 100644
--- 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
@@ -22,5 +22,6 @@ under the License.
 <enforcer>
     <rules>
         <AlwaysFail/>
+        <AlwaysFail/>
     </rules>
 </enforcer>
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/verify.groovy
similarity index 67%
copy from maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy
copy to maven-enforcer-plugin/src/it/projects/external-rules-always-fail/verify.groovy
index 4079a1c..fd3ccbd 100644
--- a/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy
+++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/verify.groovy
@@ -16,8 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-File buildLog = new File( basedir, 'build.log' )
-assert buildLog.text.contains( '[INFO] Rule 0: org.apache.maven.plugins.enforcer.AlwaysPass executed' )
 
-
- 
\ No newline at end of file
+File buildLog = new File(basedir, 'build.log')
+assert buildLog.text.contains('[INFO] Rule Config Provider org.apache.maven.enforcer.rules.ExternalRules executed')
+assert buildLog.text.contains('[ERROR] Rule 0: org.apache.maven.enforcer.rules.AlwaysFail failed with message:')
+assert buildLog.text.contains('[ERROR] Rule 1: org.apache.maven.enforcer.rules.AlwaysFail failed with message:')
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
index 9f9de4e..5920220 100644
--- 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
@@ -22,5 +22,6 @@ under the License.
 <enforcer>
     <rules>
         <AlwaysPass/>
+        <AlwaysPass/>
     </rules>
 </enforcer>
\ No newline at end of file
diff --git a/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/verify.groovy
similarity index 69%
copy from maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy
copy to maven-enforcer-plugin/src/it/projects/external-rules-always-pass/verify.groovy
index 4079a1c..67f16ae 100644
--- a/maven-enforcer-plugin/src/it/projects/always-pass/verify.groovy
+++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/verify.groovy
@@ -16,8 +16,8 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-File buildLog = new File( basedir, 'build.log' )
-assert buildLog.text.contains( '[INFO] Rule 0: org.apache.maven.plugins.enforcer.AlwaysPass executed' )
 
-
- 
\ No newline at end of file
+File buildLog = new File(basedir, 'build.log')
+assert buildLog.text.contains('[INFO] Rule Config Provider org.apache.maven.enforcer.rules.ExternalRules executed')
+assert buildLog.text.contains('[INFO] Rule 0: org.apache.maven.enforcer.rules.AlwaysPass executed')
+assert buildLog.text.contains('[INFO] Rule 1: org.apache.maven.enforcer.rules.AlwaysPass executed')
diff --git a/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/EnforceMojo.java b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/EnforceMojo.java
index ad2a8f7..e3b4113 100644
--- a/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/EnforceMojo.java
+++ b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/EnforceMojo.java
@@ -22,14 +22,14 @@ import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Collectors;
 
 import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
+import org.apache.maven.enforcer.rule.api.AbstractEnforcerRuleConfigProvider;
 import org.apache.maven.enforcer.rule.api.EnforcerLevel;
-import org.apache.maven.enforcer.rule.api.EnforcerLogger;
 import org.apache.maven.enforcer.rule.api.EnforcerRule;
-import org.apache.maven.enforcer.rule.api.EnforcerRule2;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleBase;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleError;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
@@ -45,15 +45,15 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
-import org.apache.maven.plugins.enforcer.internal.EnforcerLoggerError;
-import org.apache.maven.plugins.enforcer.internal.EnforcerLoggerWarn;
 import org.apache.maven.plugins.enforcer.internal.EnforcerRuleCache;
 import org.apache.maven.plugins.enforcer.internal.EnforcerRuleDesc;
 import org.apache.maven.plugins.enforcer.internal.EnforcerRuleManager;
+import org.apache.maven.plugins.enforcer.internal.EnforcerRuleManagerException;
 import org.apache.maven.project.MavenProject;
 import org.codehaus.plexus.PlexusContainer;
 import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
 import org.codehaus.plexus.configuration.PlexusConfiguration;
+import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
 
 /**
  * This goal executes the defined enforcer-rules once per module.
@@ -192,10 +192,6 @@ public class EnforceMojo extends AbstractMojo {
         setRulesToExecute(rulesToExecute);
     }
 
-    private EnforcerLogger enforcerLoggerError;
-
-    private EnforcerLogger enforcerLoggerWarn;
-
     @Override
     public void execute() throws MojoExecutionException {
         Log log = this.getLog();
@@ -207,13 +203,14 @@ public class EnforceMojo extends AbstractMojo {
 
         Optional<PlexusConfiguration> rulesFromCommandLine = createRulesFromCommandLineOptions();
         List<EnforcerRuleDesc> rulesList;
-        try {
-            // current behavior - rules from command line override all other configured rules.
-            List<EnforcerRuleDesc> allRules = enforcerRuleManager.createRules(rulesFromCommandLine.orElse(rules));
-            rulesList = filterOutSkippedRules(allRules);
-        } catch (EnforcerRuleManagerException e) {
-            throw new MojoExecutionException(e.getMessage(), e);
-        }
+
+        // current behavior - rules from command line override all other configured rules.
+        List<EnforcerRuleDesc> allRules = enforcerRuleManager.createRules(rulesFromCommandLine.orElse(rules), log);
+        rulesList = filterOutSkippedRules(allRules);
+
+        List<EnforcerRuleDesc> additionalRules = processRuleConfigProviders(rulesList);
+        rulesList = filterOutRuleConfigProviders(rulesList);
+        rulesList.addAll(additionalRules);
 
         if (rulesList.isEmpty()) {
             if (isFailIfNoRules()) {
@@ -225,9 +222,6 @@ public class EnforceMojo extends AbstractMojo {
             }
         }
 
-        enforcerLoggerError = new EnforcerLoggerError(log);
-        enforcerLoggerWarn = new EnforcerLoggerWarn(log);
-
         // messages with warn/error flag
         Map<String, Boolean> messages = new LinkedHashMap<>();
 
@@ -246,30 +240,26 @@ public class EnforceMojo extends AbstractMojo {
         // go through each rule
         for (int ruleIndex = 0; ruleIndex < rulesList.size(); ruleIndex++) {
 
-            // prevent against empty rules
             EnforcerRuleDesc ruleDesc = rulesList.get(ruleIndex);
-            if (ruleDesc != null) {
-                EnforcerRuleBase rule = ruleDesc.getRule();
-                EnforcerLevel level = getLevel(rule);
-                try {
-                    executeRule(ruleIndex, ruleDesc, helper);
-                } catch (EnforcerRuleError e) {
-                    String ruleMessage = createRuleMessage(ruleIndex, ruleDesc, EnforcerLevel.ERROR, e);
-                    throw new MojoExecutionException(ruleMessage, e);
-                } catch (EnforcerRuleException e) {
+            EnforcerLevel level = ruleDesc.getLevel();
+            try {
+                executeRule(ruleIndex, ruleDesc, helper);
+            } catch (EnforcerRuleError e) {
+                String ruleMessage = createRuleMessage(ruleIndex, ruleDesc, EnforcerLevel.ERROR, e);
+                throw new MojoExecutionException(ruleMessage, e);
+            } catch (EnforcerRuleException e) {
 
-                    String ruleMessage = createRuleMessage(ruleIndex, ruleDesc, level, e);
+                String ruleMessage = createRuleMessage(ruleIndex, ruleDesc, level, e);
 
-                    if (failFast && level == EnforcerLevel.ERROR) {
-                        throw new MojoExecutionException(ruleMessage, e);
-                    }
+                if (failFast && level == EnforcerLevel.ERROR) {
+                    throw new MojoExecutionException(ruleMessage, e);
+                }
 
-                    if (level == EnforcerLevel.ERROR) {
-                        hasErrors = true;
-                        messages.put(ruleMessage, true);
-                    } else {
-                        messages.put(ruleMessage, false);
-                    }
+                if (level == EnforcerLevel.ERROR) {
+                    hasErrors = true;
+                    messages.put(ruleMessage, true);
+                } else {
+                    messages.put(ruleMessage, false);
                 }
             }
         }
@@ -289,11 +279,45 @@ public class EnforceMojo extends AbstractMojo {
         }
     }
 
+    private List<EnforcerRuleDesc> processRuleConfigProviders(List<EnforcerRuleDesc> rulesList) {
+        return rulesList.stream()
+                .filter(Objects::nonNull)
+                .filter(rd -> rd.getRule() instanceof AbstractEnforcerRuleConfigProvider)
+                .map(this::executeRuleConfigProvider)
+                .flatMap(xml -> enforcerRuleManager.createRules(xml, getLog()).stream())
+                .collect(Collectors.toList());
+    }
+
+    private List<EnforcerRuleDesc> filterOutRuleConfigProviders(List<EnforcerRuleDesc> rulesList) {
+        return rulesList.stream()
+                .filter(Objects::nonNull)
+                .filter(rd -> !(rd.getRule() instanceof AbstractEnforcerRuleConfigProvider))
+                .collect(Collectors.toList());
+    }
+
+    private XmlPlexusConfiguration executeRuleConfigProvider(EnforcerRuleDesc ruleDesc) {
+        AbstractEnforcerRuleConfigProvider ruleProducer = (AbstractEnforcerRuleConfigProvider) ruleDesc.getRule();
+
+        if (getLog().isDebugEnabled()) {
+            getLog().debug(String.format("Executing Rule Config Provider %s", ruleDesc.getRule()));
+        }
+
+        XmlPlexusConfiguration configuration = null;
+        try {
+            configuration = new XmlPlexusConfiguration(ruleProducer.getRulesConfig());
+        } catch (EnforcerRuleException e) {
+            throw new EnforcerRuleManagerException("Rules Provider error for: " + getRuleName(ruleDesc), e);
+        }
+        getLog().info(String.format("Rule Config Provider %s executed", getRuleName(ruleDesc)));
+
+        return configuration;
+    }
+
     private void executeRule(int ruleIndex, EnforcerRuleDesc ruleDesc, EnforcerRuleHelper helper)
             throws EnforcerRuleException {
 
         if (getLog().isDebugEnabled()) {
-            getLog().debug(String.format("Executing Rule %d: %s", ruleIndex, ruleDesc.getRule()));
+            getLog().debug(String.format("Executing Rule %d: %s", ruleIndex, ruleDesc));
         }
 
         long startTime = System.currentTimeMillis();
@@ -327,8 +351,6 @@ public class EnforceMojo extends AbstractMojo {
     private void executeRuleNew(int ruleIndex, EnforcerRuleDesc ruleDesc) throws EnforcerRuleException {
 
         AbstractEnforcerRule rule = (AbstractEnforcerRule) ruleDesc.getRule();
-        rule.setLog(rule.getLevel() == EnforcerLevel.ERROR ? enforcerLoggerError : enforcerLoggerWarn);
-
         if (ignoreCache || !ruleCache.isCached(rule)) {
             rule.execute();
             getLog().info(String.format("Rule %d: %s executed", ruleIndex, getRuleName(ruleDesc)));
@@ -453,22 +475,6 @@ public class EnforceMojo extends AbstractMojo {
         return ruleName;
     }
 
-    /**
-     * Returns the level of the rule, defaults to {@link EnforcerLevel#ERROR} for backwards compatibility.
-     *
-     * @param rule might be of type {{@link AbstractEnforcerRule} or {@link EnforcerRule2}
-     * @return level of the rule.
-     */
-    private EnforcerLevel getLevel(EnforcerRuleBase rule) {
-        if (rule instanceof AbstractEnforcerRule) {
-            return ((AbstractEnforcerRule) rule).getLevel();
-        } else if (rule instanceof EnforcerRule2) {
-            return ((EnforcerRule2) rule).getLevel();
-        } else {
-            return EnforcerLevel.ERROR;
-        }
-    }
-
     /**
      * @return the skip
      */
diff --git a/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleDesc.java b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleDesc.java
index ec0f8ed..f7ae92b 100644
--- a/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleDesc.java
+++ b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleDesc.java
@@ -18,6 +18,7 @@
  */
 package org.apache.maven.plugins.enforcer.internal;
 
+import org.apache.maven.enforcer.rule.api.EnforcerLevel;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleBase;
 
 /**
@@ -32,15 +33,31 @@ public class EnforcerRuleDesc {
 
     private final EnforcerRuleBase rule;
 
+    private final EnforcerLevel level;
+
     /**
      * Create a new Rule Description
      *
-     * @param name a rule name
-     * @param rule a rule instance
+     * @param name  a rule name
+     * @param rule  a rule instance
      */
     public EnforcerRuleDesc(String name, EnforcerRuleBase rule) {
         this.name = name;
         this.rule = rule;
+        this.level = EnforcerLevel.ERROR;
+    }
+
+    /**
+     * Create a new Rule Description
+     *
+     * @param name  a rule name
+     * @param rule  a rule instance
+     * @param level a rule level
+     */
+    public EnforcerRuleDesc(String name, EnforcerRuleBase rule, EnforcerLevel level) {
+        this.name = name;
+        this.rule = rule;
+        this.level = level;
     }
 
     public String getName() {
@@ -51,8 +68,12 @@ public class EnforcerRuleDesc {
         return rule;
     }
 
+    public EnforcerLevel getLevel() {
+        return level;
+    }
+
     @Override
     public String toString() {
-        return "EnforcerRuleDesc[name=" + name + ", rule=" + rule + "]";
+        return String.format("EnforcerRuleDesc[name=%s, rule=%s, level=%s", name, rule, level);
     }
 }
diff --git a/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManager.java b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManager.java
index 0b2cd72..1223b30 100644
--- a/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManager.java
+++ b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManager.java
@@ -26,12 +26,16 @@ import javax.inject.Singleton;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 
+import org.apache.maven.enforcer.rule.api.AbstractEnforcerRuleBase;
+import org.apache.maven.enforcer.rule.api.EnforcerLevel;
+import org.apache.maven.enforcer.rule.api.EnforcerLogger;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleBase;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.MojoExecution;
 import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
-import org.apache.maven.plugins.enforcer.EnforcerRuleManagerException;
+import org.apache.maven.plugin.logging.Log;
 import org.codehaus.plexus.PlexusContainer;
 import org.codehaus.plexus.classworlds.realm.ClassRealm;
 import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
@@ -76,10 +80,11 @@ public class EnforcerRuleManager {
      * Create enforcer rules based on xml configuration.
      *
      * @param rules a rules configuration
+     * @param log   a Mojo logger
      * @return List of rule instances
      * @throws EnforcerRuleManagerException report a problem during rules creating
      */
-    public List<EnforcerRuleDesc> createRules(PlexusConfiguration rules) throws EnforcerRuleManagerException {
+    public List<EnforcerRuleDesc> createRules(PlexusConfiguration rules, Log log) throws EnforcerRuleManagerException {
 
         List<EnforcerRuleDesc> result = new ArrayList<>();
 
@@ -96,8 +101,17 @@ public class EnforcerRuleManager {
         ExpressionEvaluator evaluator =
                 new PluginParameterExpressionEvaluator(sessionProvider.get(), mojoExecutionProvider.get());
 
+        EnforcerLogger enforcerLoggerError = new EnforcerLoggerError(log);
+        EnforcerLogger enforcerLoggerWarn = new EnforcerLoggerWarn(log);
+
         for (PlexusConfiguration ruleConfig : rules.getChildren()) {
-            EnforcerRuleDesc ruleDesc = createRuleDesc(ruleConfig.getName(), ruleConfig.getAttribute("implementation"));
+            // we need rule level before configuration in order to proper set logger and RuleDesc
+            EnforcerLevel ruleLevel = getRuleLevelFromConfig(ruleConfig);
+
+            EnforcerRuleDesc ruleDesc =
+                    createRuleDesc(ruleConfig.getName(), ruleConfig.getAttribute("implementation"), ruleLevel);
+            // setup logger before rule configuration
+            setupLoggerForRule(ruleDesc, ruleLevel == EnforcerLevel.ERROR ? enforcerLoggerError : enforcerLoggerWarn);
             if (ruleConfig.getChildCount() > 0) {
                 try {
                     componentConfigurator.configureComponent(ruleDesc.getRule(), ruleConfig, evaluator, classRealm);
@@ -110,13 +124,30 @@ public class EnforcerRuleManager {
         return result;
     }
 
-    private EnforcerRuleDesc createRuleDesc(String name, String implementation) throws EnforcerRuleManagerException {
+    private EnforcerLevel getRuleLevelFromConfig(PlexusConfiguration ruleConfig) {
+        PlexusConfiguration levelConfig = ruleConfig.getChild("level", false);
+        String level = Optional.ofNullable(levelConfig)
+                .map(PlexusConfiguration::getValue)
+                .orElse(EnforcerLevel.ERROR.name());
+        return EnforcerLevel.valueOf(level);
+    }
+
+    private void setupLoggerForRule(EnforcerRuleDesc ruleDesc, EnforcerLogger logger) {
+        EnforcerRuleBase rule = ruleDesc.getRule();
+        if (rule instanceof AbstractEnforcerRuleBase) {
+            AbstractEnforcerRuleBase ruleBase = (AbstractEnforcerRuleBase) rule;
+            ruleBase.setLog(logger);
+        }
+    }
+
+    private EnforcerRuleDesc createRuleDesc(String name, String implementation, EnforcerLevel ruleLevel)
+            throws EnforcerRuleManagerException {
 
         // component name should always start at lowercase character
         String ruleName = Character.toLowerCase(name.charAt(0)) + name.substring(1);
 
         try {
-            return new EnforcerRuleDesc(ruleName, plexusContainer.lookup(EnforcerRuleBase.class, ruleName));
+            return new EnforcerRuleDesc(ruleName, plexusContainer.lookup(EnforcerRuleBase.class, ruleName), ruleLevel);
         } catch (ComponentLookupException e) {
             // no component for rule
             // process old way, by  class name
@@ -136,7 +167,7 @@ public class EnforcerRuleManager {
 
         try {
             return new EnforcerRuleDesc(
-                    name, (EnforcerRuleBase) Class.forName(ruleClass).newInstance());
+                    name, (EnforcerRuleBase) Class.forName(ruleClass).newInstance(), ruleLevel);
         } catch (Exception e) {
             throw new EnforcerRuleManagerException(
                     "Failed to create enforcer rules with name: " + ruleName + " or for class: " + ruleClass, e);
diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerRuleManagerException.java b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManagerException.java
similarity index 90%
rename from enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerRuleManagerException.java
rename to maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManagerException.java
index 29e20af..a7b2374 100644
--- a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerRuleManagerException.java
+++ b/maven-enforcer-plugin/src/main/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManagerException.java
@@ -16,12 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.plugins.enforcer;
+package org.apache.maven.plugins.enforcer.internal;
 
 /**
  * Problems reported by EnforcerRuleManager.
  */
-public class EnforcerRuleManagerException extends Exception {
+public class EnforcerRuleManagerException extends RuntimeException {
 
     private static final long serialVersionUID = -7559335919839629986L;
 
diff --git a/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/TestEnforceMojo.java b/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/TestEnforceMojo.java
index bead244..634b2ba 100644
--- a/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/TestEnforceMojo.java
+++ b/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/TestEnforceMojo.java
@@ -89,7 +89,7 @@ class TestEnforceMojo {
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(true));
 
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         mojo.execute();
 
@@ -117,7 +117,7 @@ class TestEnforceMojo {
         rules[1] = new EnforcerRuleDesc("ruleBreakBuild", ruleBreakBuild);
         rules[2] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false));
 
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         Assertions.assertThatCode(() -> mojo.execute())
                 .isInstanceOf(MojoExecutionException.class)
@@ -137,7 +137,7 @@ class TestEnforceMojo {
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(true));
 
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         try {
             mojo.setFailFast(false);
@@ -171,7 +171,7 @@ class TestEnforceMojo {
         // check that basic caching works.
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         EnforceMojo.cache.clear();
         mojo.execute();
@@ -182,7 +182,7 @@ class TestEnforceMojo {
         // check that skip caching works.
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         EnforceMojo.cache.clear();
         mojo.ignoreCache = true;
@@ -197,7 +197,7 @@ class TestEnforceMojo {
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "1", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "2", true, true));
         rules[2] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "2", true, true));
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         EnforceMojo.cache.clear();
         mojo.execute();
@@ -210,7 +210,7 @@ class TestEnforceMojo {
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "1", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "1", false, true));
         rules[2] = null;
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         EnforceMojo.cache.clear();
         mojo.execute();
@@ -222,7 +222,7 @@ class TestEnforceMojo {
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "1", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "1", true, false));
         rules[2] = null;
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         EnforceMojo.cache.clear();
         mojo.execute();
@@ -240,7 +240,7 @@ class TestEnforceMojo {
         // check that basic caching works.
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         EnforceMojo.cache.clear();
         mojo.execute();
@@ -258,7 +258,7 @@ class TestEnforceMojo {
         // check that basic caching works.
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         mojo.execute();
 
@@ -282,7 +282,7 @@ class TestEnforceMojo {
         // check that basic caching works.
         rules[0] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
         rules[1] = new EnforcerRuleDesc("mockEnforcerRule", new MockEnforcerRule(false, "", true, true));
-        when(ruleManager.createRules(any())).thenReturn(Arrays.asList(rules));
+        when(ruleManager.createRules(any(), any())).thenReturn(Arrays.asList(rules));
 
         mojo.execute();
 
@@ -300,7 +300,7 @@ class TestEnforceMojo {
 
         EnforcerRule ruleMock = Mockito.mock(EnforcerRule.class);
         Mockito.doThrow(ruleException).when(ruleMock).execute(any(EnforcerRuleHelper.class));
-        when(ruleManager.createRules(any()))
+        when(ruleManager.createRules(any(), any()))
                 .thenReturn(Collections.singletonList(new EnforcerRuleDesc("mock", ruleMock)));
 
         Log logSpy = setupLogSpy();
@@ -325,7 +325,7 @@ class TestEnforceMojo {
         EnforcerRule ruleMock = Mockito.mock(EnforcerRule.class);
         Mockito.doThrow(enforcerRuleException).when(ruleMock).execute(any(EnforcerRuleHelper.class));
 
-        when(ruleManager.createRules(any()))
+        when(ruleManager.createRules(any(), any()))
                 .thenReturn(Collections.singletonList(new EnforcerRuleDesc("mock", ruleMock)));
 
         Log logSpy = setupLogSpy();
diff --git a/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManagerTest.java b/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManagerTest.java
index a068e50..8ee5318 100644
--- a/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManagerTest.java
+++ b/maven-enforcer-plugin/src/test/java/org/apache/maven/plugins/enforcer/internal/EnforcerRuleManagerTest.java
@@ -22,13 +22,14 @@ import javax.inject.Provider;
 
 import java.util.List;
 
+import org.apache.maven.enforcer.rule.api.EnforcerLevel;
 import org.apache.maven.enforcer.rule.api.EnforcerRule;
 import org.apache.maven.enforcer.rule.api.EnforcerRuleBase;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.MojoExecution;
 import org.apache.maven.plugin.descriptor.MojoDescriptor;
 import org.apache.maven.plugin.descriptor.PluginDescriptor;
-import org.apache.maven.plugins.enforcer.EnforcerRuleManagerException;
+import org.apache.maven.plugin.logging.Log;
 import org.apache.maven.plugins.enforcer.TestRule1;
 import org.apache.maven.plugins.enforcer.TestRule2;
 import org.codehaus.plexus.PlexusContainer;
@@ -69,6 +70,9 @@ class EnforcerRuleManagerTest {
     @Mock
     private PlexusContainer plexusContainer;
 
+    @Mock
+    private Log mojoLog;
+
     private EnforcerRuleManager enforcerRuleManager;
 
     @BeforeEach
@@ -94,7 +98,7 @@ class EnforcerRuleManagerTest {
     @Test
     void nullConfigReturnEmptyRules() throws Exception {
 
-        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(null);
+        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(null, mojoLog);
 
         assertThat(rules).isEmpty();
     }
@@ -102,7 +106,8 @@ class EnforcerRuleManagerTest {
     @Test
     void emptyConfigReturnEmptyRules() throws Exception {
 
-        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(new DefaultPlexusConfiguration("rules"));
+        List<EnforcerRuleDesc> rules =
+                enforcerRuleManager.createRules(new DefaultPlexusConfiguration("rules"), mojoLog);
 
         assertThat(rules).isEmpty();
     }
@@ -114,7 +119,7 @@ class EnforcerRuleManagerTest {
 
         PlexusConfiguration configuration = new DefaultPlexusConfiguration("rules").addChild("UnKnowRule", null);
 
-        assertThatCode(() -> enforcerRuleManager.createRules(configuration))
+        assertThatCode(() -> enforcerRuleManager.createRules(configuration, mojoLog))
                 .isInstanceOf(EnforcerRuleManagerException.class)
                 .hasMessage(
                         "Failed to create enforcer rules with name: unKnowRule or for class: org.apache.maven.plugins.enforcer.UnKnowRule")
@@ -135,7 +140,7 @@ class EnforcerRuleManagerTest {
                 .when(componentConfigurator)
                 .configureComponent(any(), any(), any(), any());
 
-        assertThatCode(() -> enforcerRuleManager.createRules(configuration))
+        assertThatCode(() -> enforcerRuleManager.createRules(configuration, mojoLog))
                 .isInstanceOf(EnforcerRuleManagerException.class)
                 .hasCauseInstanceOf(ComponentConfigurationException.class);
     }
@@ -149,7 +154,7 @@ class EnforcerRuleManagerTest {
                 .addChild("TestRule1", null)
                 .addChild("testRule2", null);
 
-        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration);
+        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration, mojoLog);
 
         assertThat(rules)
                 .hasSize(2)
@@ -170,7 +175,7 @@ class EnforcerRuleManagerTest {
                 .addChild("TestRule1", null)
                 .addChild("testRule2", null);
 
-        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration);
+        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration, mojoLog);
 
         assertThat(rules)
                 .hasSize(2)
@@ -191,7 +196,7 @@ class EnforcerRuleManagerTest {
         PlexusConfiguration configuration = new DefaultPlexusConfiguration("rules");
         configuration.addChild(ruleConfig);
 
-        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration);
+        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration, mojoLog);
 
         assertThat(rules).hasSize(1).map(EnforcerRuleDesc::getRule).hasExactlyElementsOfTypes(TestRule1.class);
 
@@ -208,7 +213,7 @@ class EnforcerRuleManagerTest {
         PlexusConfiguration configuration = new DefaultPlexusConfiguration("rules");
         configuration.addChild(ruleConfig);
 
-        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration);
+        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration, mock(Log.class));
         assertThat(rules).hasSize(1);
 
         ArgumentCaptor<EnforcerRule> ruleCaptor = ArgumentCaptor.forClass(EnforcerRule.class);
@@ -220,4 +225,18 @@ class EnforcerRuleManagerTest {
         assertThat(ruleCaptor.getValue()).isInstanceOf(TestRule1.class);
         assertThat(configurationCaptor.getValue()).isSameAs(ruleConfig);
     }
+
+    @Test
+    void ruleLevelShouldBeDisoveredFromConfigured() throws Exception {
+
+        setupMocks();
+
+        PlexusConfiguration ruleConfig = new DefaultPlexusConfiguration("testRule1").addChild("level", "WARN");
+        PlexusConfiguration configuration = new DefaultPlexusConfiguration("rules");
+        configuration.addChild(ruleConfig);
+
+        List<EnforcerRuleDesc> rules = enforcerRuleManager.createRules(configuration, mock(Log.class));
+        assertThat(rules).hasSize(1);
+        assertThat(rules.get(0).getLevel()).isEqualTo(EnforcerLevel.WARN);
+    }
 }