SLING-10763 : Provide a framework to check artifact versions
diff --git a/README.md b/README.md
index feac0d4..aacdb44 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,8 @@
   * `strict` : By default the analyser issues warnings. If this is set to `true` errors are issued instead.
   * `removal-period` : If deprecated api is used and that api has a `for-removal` information with a date set, then this configuration can be used to issue an error instead of a warning if the removal date is less than the configured number of days away. For example setting this to 28 will result in errors being generated four weeks ahead of the removal date.
 
+* `artifact-rules` : This analyser validates the artifacts (bundles) against rules in the feature model. 
+
 ## Extensions
 
 The following extensions are registered via the ServiceLoader mechanism:
@@ -72,7 +74,8 @@
 Merge handlers are called when features are merged during the aggregation process:
 
 * `APIRegionMergeHandler` - This handler knows how to merge API Regions extensions
-* `ConfigurationApiMergeHandler` - This handlers knows how to merge Configuration API extensions
+* `ConfigurationApiMergeHandler` - This handler knows how to merge Configuration API extensions
+* `ArtifactRulesMergeHandler` - This handler merges artifact rules
 
 # Additional Extensions
 
diff --git a/docs/api-regions.md b/docs/api-regions.md
index ffab4a2..ea23e75 100644
--- a/docs/api-regions.md
+++ b/docs/api-regions.md
@@ -1,6 +1,6 @@
 # API Regions for the Feature Model
 
-If you're assembling a platform (in contrast to a final application) out of several features and provide this platform for customers to build their application on top of it, additional control of the API provided by the platform is needed. The bundles within the features provide all kinds of APIs but you might not want to expose all of these as extension points. You would rather want to use some of them internally within either a single feature or share within your platform features.
+If you are assembling a platform (in contrast to a final application) out of several features and provide this platform for customers to build their application on top of it, additional control of the API provided by the platform is needed. The bundles within the features provide all kinds of APIs but you might not want to expose all of these as extension points. You would rather want to use some of them internally within either a single feature or share within your platform features.
 
 ## Visibility of Java API
 
@@ -358,3 +358,23 @@
 ```
 
 When two features are aggregated, the resulting feature is only in the internal region if both source features are in the internal region. Otherwise, the resulting aggregate is always in the global region.
+
+## Artifact Rules
+
+The artifact rules extension allows to specify version rules for bundles. For an artifact identity allowed and denied version ranges can be specified. A version range follows the OSGi version range syntax. If no ranges are specified, the artifact is not allowed. An artifact version must match at least one allowed version range and must not match any denied version range (if specified).
+
+``` json
+"artifact-rules:JSON|optional" : {
+  "mode" : "INTERNAL",
+  "bundle-version-rules":[
+      {
+          "artifact-id" : "g:a:1", # version does not matter
+          "msg":"Use at least version 2.0.4 but avoid 2.1.1",
+          "allowed-version-ranges":["[2.0.4,3)"],
+          "denied-version-ranges":["[2.1.1,2.1.1]]
+      }
+  ]
+}
+```
+
+The mode, either LENIENT or STRICT (default) can be used to decide whether a warning or an error should be emitted.
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRulesTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRulesTest.java
index d769ac9..293fb29 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRulesTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRulesTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.io.IOException;
+
 import javax.json.Json;
 
 import org.apache.sling.feature.ArtifactId;
@@ -29,6 +31,7 @@
 import org.apache.sling.feature.ExtensionType;
 import org.apache.sling.feature.Feature;
 import org.junit.Test;
+import org.osgi.framework.VersionRange;
 
 public class ArtifactRulesTest {
 
@@ -74,4 +77,33 @@
         assertTrue(entity.getBundleVersionRules().isEmpty());
         assertEquals(Mode.STRICT, entity.getMode());
     }
+
+    @Test public void testFromJSONObject() throws IOException {
+        final Extension ext = new Extension(ExtensionType.JSON, ArtifactRules.EXTENSION_NAME, ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"mode\" : \"LENIENT\", \"bundle-version-rules\":[{"+
+                "\"artifact-id\":\"g:a:1\",\"allowed-version-ranges\":[\"1.0.0\"]}]}");
+
+        final ArtifactRules entity = new ArtifactRules();
+        entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+        assertEquals(Mode.LENIENT, entity.getMode());
+        assertEquals(1, entity.getBundleVersionRules().size());
+        assertEquals(ArtifactId.parse("g:a:1"), entity.getBundleVersionRules().get(0).getArtifactId());
+        assertEquals(1, entity.getBundleVersionRules().get(0).getAllowedVersionRanges().length);
+        assertEquals(new VersionRange("1.0.0"), entity.getBundleVersionRules().get(0).getAllowedVersionRanges()[0]);
+    }
+
+    @Test public void testToJSONObject() throws IOException {
+        final ArtifactRules entity = new ArtifactRules();
+        entity.setMode(Mode.LENIENT);
+        final VersionRule rule = new VersionRule();
+        rule.setArtifactId(ArtifactId.parse("g:a:1"));
+        rule.setAllowedVersionRanges(new VersionRange[] {new VersionRange("1.0.0")});
+        entity.getBundleVersionRules().add(rule);
+
+        final Extension ext = new Extension(ExtensionType.JSON, ArtifactRules.EXTENSION_NAME, ExtensionState.OPTIONAL);
+        ext.setJSON("{ \"mode\" : \"LENIENT\", \"bundle-version-rules\":[{"+
+                "\"artifact-id\":\"g:a:1\",\"allowed-version-ranges\":[\"1.0.0\"]}]}");
+
+        assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+    }
 }