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:32 UTC

[maven-enforcer] branch MENFORCER-456 created (now 66661f4)

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

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


      at 66661f4  [MENFORCER-456] New Enforcer API - RuleConfigProvider

This branch includes the following new commits:

     new 66661f4  [MENFORCER-456] New Enforcer API - RuleConfigProvider

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

Posted by sj...@apache.org.
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);
+    }
 }