You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2020/11/02 07:44:28 UTC

[sling-org-apache-sling-feature-extension-apiregions] branch SLING-9867 updated: SLING-9867 : Add extension to provide metadata about configuration api

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

cziegeler pushed a commit to branch SLING-9867
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-extension-apiregions.git


The following commit(s) were added to refs/heads/SLING-9867 by this push:
     new 3ae2797  SLING-9867 : Add extension to provide metadata about configuration api
3ae2797 is described below

commit 3ae2797bde1d7f7de7884a39e490ffa9adfa73f0
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Nov 2 08:44:02 2020 +0100

    SLING-9867 : Add extension to provide metadata about configuration api
---
 .gitignore                                         |   1 +
 .../apiregions/api/config/AttributeableEntity.java | 149 +++++++++
 .../apiregions/api/config/ConfigurableEntity.java  |  94 ++++++
 .../apiregions/api/config/Configuration.java       |  21 ++
 .../apiregions/api/config/ConfigurationApi.java    | 276 +++++++++++++++++
 .../extension/apiregions/api/config/Constants.java |  65 ++++
 .../apiregions/api/config/DescribableEntity.java   | 126 ++++++++
 .../api/config/FactoryConfiguration.java           | 120 ++++++++
 .../apiregions/api/config/FrameworkProperty.java   |  24 ++
 .../extension/apiregions/api/config/Operation.java |  26 ++
 .../extension/apiregions/api/config/Option.java    |  82 +++++
 .../extension/apiregions/api/config/Property.java  | 301 ++++++++++++++++++
 .../apiregions/api/config/PropertyType.java        |  37 +++
 .../extension/apiregions/api/config/Range.java     | 112 +++++++
 .../extension/apiregions/api/config/Validator.java | 341 +++++++++++++++++++++
 15 files changed, 1775 insertions(+)

diff --git a/.gitignore b/.gitignore
index 5b783ed..38f5ca4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,5 @@ maven-eclipse.xml
 .vlt
 .DS_Store
 jcr.log
+.vscode
 atlassian-ide-plugin.xml
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
new file mode 100644
index 0000000..3d421dd
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
@@ -0,0 +1,149 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.apache.felix.cm.json.Configurations;
+
+public abstract class AttributeableEntity {
+	
+	/** The additional attributes */
+	private final Map<String, JsonValue> attributes = new LinkedHashMap<>();
+	
+	/**
+	 * Create a new instance and set defaults.
+	 */
+	public AttributeableEntity() {
+		this.clear();
+	}
+
+    /**
+     * Clear the object and remove all metadata
+     */
+	public void clear() {
+		this.attributes.clear();
+	}
+	
+   /**
+     * Convert this object into JSON
+     *
+     * @return The json object
+     * @throws IOException If generating the JSON fails
+     */
+    public JsonObject toJSONObject() throws IOException {
+        final JsonObjectBuilder objectBuilder = this.createJson();
+        return objectBuilder.build();
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+		this.clear();
+        try {
+            for(final Map.Entry<String, JsonValue> entry : jsonObj.entrySet()) {
+				this.getAttributes().put(entry.getKey(), entry.getValue());
+			}
+        } catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+		}
+	}
+
+	/**
+	 * Get the attributes
+	 * @return The attributes
+	 */
+	public Map<String, JsonValue> getAttributes() {
+        return this.attributes;
+    }
+
+	/**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
+
+		for(final Map.Entry<String, JsonValue> entry : this.getAttributes().entrySet()) {
+			objectBuilder.add(entry.getKey(), entry.getValue());
+		}
+
+		return objectBuilder;
+	}
+	
+	/**
+	 * Helper method to get a string value from a JsonValue
+	 * @param jsonValue The json value
+	 * @return The string value or {@code null}.
+	 */
+	String getString(final JsonValue jsonValue) {
+		final Object obj = Configurations.convertToObject(jsonValue);
+		if ( obj != null ) {
+			return obj.toString();
+		}
+		return null;
+	}
+
+	/**
+	 * Helper method to get a string value from an attribute
+	 * @param attributeName The attribute name
+	 * @return The string value or {@code null}.
+	 */
+	String getString(final String attributeName) {
+		final JsonValue val = this.getAttributes().remove(attributeName);
+		if ( val != null ) {
+			final Object obj = Configurations.convertToObject(val);
+			if ( obj != null ) {
+				return obj.toString();
+			}
+		}
+		return null;
+	}
+
+	void setString(final JsonObjectBuilder builder, final String attributeName, final String value) {
+		if ( value != null ) {
+			builder.add(attributeName, value);
+		}
+	}
+	/**
+	 * Helper method to get a integer value from an attribute
+	 * @param attributeName The attribute name
+	 * @param defaultValue default value
+	 * @return The integer value or
+	 */
+	int getInteger(final String attributeName, final int defaultValue) {
+		final String val = this.getString(attributeName);
+		if ( val != null ) {
+			return Integer.parseInt(val);
+		}
+		return defaultValue;
+	}
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
new file mode 100644
index 0000000..936ea1c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
@@ -0,0 +1,94 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+/** 
+ * A configurable entity has properties
+ */
+public abstract class ConfigurableEntity extends DescribableEntity {
+	
+	/** The properties */
+    private final Map<String, Property> properties = new LinkedHashMap<>();
+
+    /**
+     * Clear the object and remove all metadata
+     */
+	public void clear() {
+        super.clear();
+		this.properties.clear();
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+            final JsonValue val = this.getAttributes().remove(Constants.KEY_PROPERTIES);
+            if ( val != null ) {
+                for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
+					final Property prop = new Property();
+					prop.fromJSONObject(innerEntry.getValue().asJsonObject());
+                    this.getProperties().put(innerEntry.getKey(), prop);
+                }
+            }            
+        } catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+	}
+
+	/**
+	 * Get the properties
+	 * @return The properties
+	 */
+    public Map<String, Property> getProperties() {
+        return this.properties;
+    }
+
+    /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+	JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objBuilder = super.createJson();
+
+		if ( !this.getProperties().isEmpty() ) {
+			final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
+			for(final Map.Entry<String, Property> entry : this.getProperties().entrySet()) {
+				propBuilder.add(entry.getKey(), entry.getValue().createJson());
+			}
+			objBuilder.add(Constants.KEY_PROPERTIES, propBuilder);
+		}
+
+		return objBuilder;
+   }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Configuration.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Configuration.java
new file mode 100644
index 0000000..5f95df2
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Configuration.java
@@ -0,0 +1,21 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+public class Configuration extends ConfigurableEntity {
+
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
new file mode 100644
index 0000000..b6ce23f
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
@@ -0,0 +1,276 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+
+/**
+ * A configuration api describes the set of supported OSGi
+ * configurations and framework properties. This object can be
+ * stored as an extension inside a feature model.
+ */
+public class ConfigurationApi extends AttributeableEntity {
+    
+    /** The name of the api regions extension. */
+    public static final String EXTENSION_NAME = "configuration-api";
+  
+    /**
+     * Get the configuration api from the feature - if it exists.
+     * 
+     * @param feature The feature
+     * @return The configuration api or {@code null}.
+     * @throws IllegalArgumentException If the extension is wrongly formatted
+     */
+    public static ConfigurationApi getConfigurationApi(final Feature feature) {
+        final Extension ext = feature == null ? null : feature.getExtensions().getByName(EXTENSION_NAME);
+        return getConfigurationApi(ext);
+    }
+
+    /**
+     * Get the configuration api from the extension.
+     * 
+     * @param ext The extension
+     * @return The configuration api or {@code null}.
+     * @throws IllegalArgumentException If the extension is wrongly formatted
+     */
+    public static ConfigurationApi getConfigurationApi(final Extension ext) {
+        if ( ext == null ) {
+            return null;
+        }
+        if ( ext.getType() != ExtensionType.JSON ) {
+            throw new IllegalArgumentException("Extension " + ext.getName() + " must have JSON type");
+        }
+        try {
+            final ConfigurationApi result = new ConfigurationApi();
+            result.fromJSONObject(ext.getJSONStructure().asJsonObject());
+            return result;
+        } catch ( final IOException ioe) {
+            throw new IllegalArgumentException(ioe.getMessage(), ioe);
+        }
+    }
+   
+    /** The map of configurations */
+ 	private final Map<String, Configuration> configurations = new LinkedHashMap<>();
+
+    /** The map of factory configurations */
+    private final Map<String, FactoryConfiguration> factories = new LinkedHashMap<>();
+
+    /** The map of framework properties */
+    private final Map<String, FrameworkProperty> frameworkProperties = new LinkedHashMap<>();
+
+    /** The list of internal configuration names */
+    private final List<String> internalConfigurations = new ArrayList<>();
+
+    /** The list of internal factory configuration names */
+    private final List<String> internalFactories = new ArrayList<>();
+
+    /** The list of internal framework property names */
+    private final List<String> internalFrameworkProperties = new ArrayList<>();
+    
+    /**
+     * Clear the object and remove all metadata
+     */
+    public void clear() {
+        super.clear();
+        this.configurations.clear();
+        this.factories.clear();
+        this.frameworkProperties.clear();
+        this.internalConfigurations.clear();
+        this.internalFactories.clear();
+        this.internalFrameworkProperties.clear();
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+    public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+            JsonValue val;
+            val = this.getAttributes().remove(Constants.KEY_CONFIGURATIONS);
+            if ( val != null ) {
+                for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
+                    final Configuration cfg = new Configuration();
+                    cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
+                    this.getConfigurations().put(innerEntry.getKey(), cfg);
+                }
+            }
+            
+            val = this.getAttributes().remove(Constants.KEY_FACTORIES);
+            if ( val != null ) {
+                for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
+                    final FactoryConfiguration cfg = new FactoryConfiguration();
+                    cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
+                    this.getFactories().put(innerEntry.getKey(), cfg);
+                }
+            }
+
+            val = this.getAttributes().remove(Constants.KEY_FWK_PROPERTIES);
+            if ( val != null ) {
+                for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
+                    final FrameworkProperty cfg = new FrameworkProperty();
+                    cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
+                    this.getFrameworkProperties().put(innerEntry.getKey(), cfg);
+                }
+            }
+
+            val = this.getAttributes().remove(Constants.KEY_INTERNAL_CONFIGURATIONS);
+            if ( val != null ) {
+                for(final JsonValue innerVal : val.asJsonArray()) {
+                    this.getInternalConfigurations().add(getString(innerVal));
+                }
+            }
+
+            val = this.getAttributes().remove(Constants.KEY_INTERNAL_FACTORIES);
+            if ( val != null ) {
+                for(final JsonValue innerVal : val.asJsonArray()) {
+                    this.getInternalFactories().add(getString(innerVal));
+                }
+            }
+
+            val = this.getAttributes().remove(Constants.KEY_INTERNAL_FWK_PROPERTIES);
+            if ( val != null ) {
+                for(final JsonValue innerVal : val.asJsonArray()) {
+                    this.getInternalFrameworkProperties().add(getString(innerVal));
+                }
+            }
+
+        } catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Get the configurations
+	 * @return the configurations
+	 */
+	public Map<String, Configuration> getConfigurations() {
+		return configurations;
+	}
+
+	/**
+     * Get the factory configurations
+	 * @return the factories
+	 */
+	public Map<String, FactoryConfiguration> getFactories() {
+		return factories;
+	}
+
+	/**
+     * Get the framework properties
+	 * @return the frameworkProperties
+	 */
+	public Map<String, FrameworkProperty> getFrameworkProperties() {
+		return frameworkProperties;
+	}
+
+	/**
+     * Get the internal configuration names
+	 * @return the internalConfigurations
+	 */
+	public List<String> getInternalConfigurations() {
+		return internalConfigurations;
+	}
+
+	/**
+     * Get the internal factory names
+	 * @return the internalFactories
+	 */
+	public List<String> getInternalFactories() {
+		return internalFactories;
+	}
+
+	/**
+     * Get the internal framework property names
+	 * @return the internalFrameworkProperties
+	 */
+	public List<String> getInternalFrameworkProperties() {
+		return internalFrameworkProperties;
+    }
+
+    /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objBuilder = super.createJson();
+        if ( !this.getConfigurations().isEmpty() ) {
+            final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
+            for(final Map.Entry<String, Configuration> entry : this.getConfigurations().entrySet()) {
+                propBuilder.add(entry.getKey(), entry.getValue().createJson());
+            }
+            objBuilder.add(Constants.KEY_CONFIGURATIONS, propBuilder);
+        }
+        if ( !this.getFactories().isEmpty() ) {
+            final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
+            for(final Map.Entry<String, FactoryConfiguration> entry : this.getFactories().entrySet()) {
+                propBuilder.add(entry.getKey(), entry.getValue().createJson());
+            }
+            objBuilder.add(Constants.KEY_FACTORIES, propBuilder);
+        }
+        if ( !this.getFrameworkProperties().isEmpty() ) {
+            final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
+            for(final Map.Entry<String, FrameworkProperty> entry : this.getFrameworkProperties().entrySet()) {
+                propBuilder.add(entry.getKey(), entry.getValue().createJson());
+            }
+            objBuilder.add(Constants.KEY_FWK_PROPERTIES, propBuilder);
+        }
+        if ( !this.getInternalConfigurations().isEmpty() ) {
+            final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final String n : this.getInternalConfigurations()) {
+                arrayBuilder.add(n);
+            }
+			objBuilder.add(Constants.KEY_INTERNAL_CONFIGURATIONS, arrayBuilder);
+		}
+		if ( !this.getInternalFactories().isEmpty() ) {
+            final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final String n : this.getInternalFactories()) {
+                arrayBuilder.add(n);
+            }
+			objBuilder.add(Constants.KEY_INTERNAL_FACTORIES, arrayBuilder);
+		}
+		if ( !this.getInternalFrameworkProperties().isEmpty() ) {
+            final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final String n : this.getInternalFrameworkProperties()) {
+                arrayBuilder.add(n);
+            }
+			objBuilder.add(Constants.KEY_INTERNAL_FWK_PROPERTIES, arrayBuilder);
+		}
+
+		return objBuilder;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Constants.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Constants.java
new file mode 100644
index 0000000..88f5788
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Constants.java
@@ -0,0 +1,65 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+/**
+ * Constants used in this implementation
+ */
+public abstract class Constants {
+    
+    static final String KEY_TITLE = "title";
+
+	static final String KEY_DESCRIPTION = "description";
+
+	static final String KEY_DEPRECATED = "deprecated";
+	
+	static final String KEY_PROPERTIES = "properties";
+
+    static final String KEY_CONFIGURATIONS = "configurations";
+
+    static final String KEY_FACTORIES = "factories";
+    
+    static final String KEY_FWK_PROPERTIES = "framework-properties";
+
+    static final String KEY_INTERNAL_CONFIGURATIONS = "internal-configurations";
+
+    static final String KEY_INTERNAL_FACTORIES = "internal-factories";
+    
+    static final String KEY_INTERNAL_FWK_PROPERTIES = "internal-framework-properties";
+	
+	static final String KEY_TYPE = "type";
+	
+	static final String KEY_CARDINALITY = "cardinality";
+	
+    static final String KEY_VARIABLE = "variable";
+    
+    static final String KEY_RANGE = "range";
+
+    static final String KEY_MIN = "min";
+
+    static final String KEY_MAX = "max";
+
+    static final String KEY_INCLUDES = "includes";
+
+    static final String KEY_EXCLUDES = "excludes";
+
+    static final String KEY_OPTIONS = "options";
+  
+    static final String KEY_REGEX = "regex";
+
+    static final String KEY_VALUE = "value";
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
new file mode 100644
index 0000000..69c9e3a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
@@ -0,0 +1,126 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+
+public abstract class DescribableEntity extends AttributeableEntity {
+	
+	/** The title */
+    private String title;
+
+	/** The description */
+    private String description;
+
+	/** The optional deprecation text */
+	private String deprecated;
+
+	/**
+     * Clear the object and remove all metadata
+     */
+	public void clear() {
+		super.clear();
+		this.setTitle(null);
+		this.setDescription(null);
+		this.setDeprecated(null);
+	}
+	
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+		super.fromJSONObject(jsonObj);
+        try {
+			this.setTitle(this.getString(Constants.KEY_TITLE));
+			this.setDescription(this.getString(Constants.KEY_DESCRIPTION));
+			this.setDeprecated(this.getString(Constants.KEY_DEPRECATED));
+        } catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+		}
+	}
+
+	/**
+	 * Get the title
+	 * @return the title
+	 */
+	public String getTitle() {
+		return title;
+	}
+
+	/**
+	 * Set the title
+	 * @param title the title to set
+	 */
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	/**
+	 * Get the description
+	 * @return the description
+	 */
+	public String getDescription() {
+		return description;
+	}
+
+	/**
+	 * Set the description
+	 * @param description the description to set
+	 */
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	/**
+	 * Get the deprecation text
+	 * @return the deprecated
+	 */
+	public String getDeprecated() {
+		return deprecated;
+	}
+
+	/**
+	 * Set the deprecation text
+	 * @param deprecated the deprecated to set
+	 */
+	public void setDeprecated(String deprecated) {
+		this.deprecated = deprecated;
+	}
+
+	/**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objectBuilder = super.createJson();
+
+		this.setString(objectBuilder, Constants.KEY_TITLE, this.getTitle());
+		this.setString(objectBuilder, Constants.KEY_DESCRIPTION, this.getDescription());
+		this.setString(objectBuilder, Constants.KEY_DEPRECATED, this.getDeprecated());
+
+		return objectBuilder;
+	}
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfiguration.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfiguration.java
new file mode 100644
index 0000000..c2345bb
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfiguration.java
@@ -0,0 +1,120 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+public class FactoryConfiguration extends ConfigurableEntity {
+    
+    private static final String KEY_OPERATIONS = "operations";
+
+    private static final String KEY_INTERNAL_NAMES = "internal-names";
+    
+    private final Set<Operation> operations = new HashSet<>();
+
+    private final List<String> internalNames = new ArrayList<>();
+
+    /**
+     * Clear the object and remove all metadata
+     */
+    public void clear() {
+        super.clear();
+		this.operations.clear();
+		this.internalNames.clear();
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+    public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+            JsonValue val;
+            val = this.getAttributes().remove(KEY_OPERATIONS);
+            if ( val != null ) {
+                for(final JsonValue innerVal : val.asJsonArray()) {
+                    final String v = getString(innerVal).toUpperCase();
+                    this.getOperations().add(Operation.valueOf(v));
+                }
+            }
+            
+            val = this.getAttributes().remove(KEY_INTERNAL_NAMES);
+            if ( val != null ) {
+                for(final JsonValue innerVal : val.asJsonArray()) {
+                    this.getInternalNames().add(getString(innerVal));
+                }
+            }
+
+		} catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+    }
+
+   /**
+	 * @return the operations
+	 */
+	public Set<Operation> getOperations() {
+		return operations;
+	}
+
+	/**
+	 * @return the internalNames
+	 */
+	public List<String> getInternalNames() {
+		return internalNames;
+	}
+
+   /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objBuilder = super.createJson();
+		
+		if ( !this.getOperations().isEmpty() ) {
+            final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final Operation op : this.getOperations()) {
+                arrayBuilder.add(op.name());
+            }
+			objBuilder.add(KEY_OPERATIONS, arrayBuilder);
+		}
+		if ( !this.getInternalNames().isEmpty() ) {
+            final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final String n : this.getInternalNames()) {
+                arrayBuilder.add(n);
+            }
+			objBuilder.add(KEY_INTERNAL_NAMES, arrayBuilder);
+		}
+		return objBuilder;
+   }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FrameworkProperty.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FrameworkProperty.java
new file mode 100644
index 0000000..320448b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FrameworkProperty.java
@@ -0,0 +1,24 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+/**
+ * A framework property
+ */
+public class FrameworkProperty extends Property {
+    
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Operation.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Operation.java
new file mode 100644
index 0000000..308143b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Operation.java
@@ -0,0 +1,26 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+/**
+ * Operations for factory configurations
+ */
+public enum Operation {
+    
+    CREATE,
+    UPDATE
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
new file mode 100644
index 0000000..12ba1e0
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
@@ -0,0 +1,82 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+
+public class Option extends DescribableEntity {
+
+    /** The value for the option */
+    private String value;
+
+    /**
+     * Clear the object and remove all metadata
+     */
+	public void clear() {
+        super.clear();
+		this.setValue(null);
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+			this.setValue(this.getString(Constants.KEY_VALUE));
+ 		} catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+    }
+    
+    /**
+     * Get the value for the option
+	 * @return the value
+	 */
+	public String getValue() {
+		return value;
+	}
+
+	/**
+     * Set the value for the option
+	 * @param value the value to set
+	 */
+	public void setValue(final String value) {
+		this.value = value;
+    }
+
+    /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objectBuilder = super.createJson();
+
+		this.setString(objectBuilder, Constants.KEY_VALUE, this.getValue());
+
+		return objectBuilder;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Property.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Property.java
new file mode 100644
index 0000000..b06ed54
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Property.java
@@ -0,0 +1,301 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+/**
+ * Instances of this class represent a single configuration property
+ */
+public class Property extends DescribableEntity {
+	
+	/** The property type */
+	private PropertyType type;
+
+	/** The property cardinality */
+    private int cardinality;
+
+	/** The optional variable */
+    private String variable;
+
+	/** The optional range */
+	private Range range;
+
+	/** The required includes for an array/collection (optional) */
+	private String[] includes;
+
+	/** The required excludes for an array/collection (optional) */
+	private String[] excludes;
+
+	/** The optional list of options for the value */
+	private List<Option> options;
+	
+	/** The optional regex */
+	private String regex;
+
+    /**
+     * Clear the object and remove all metadata
+     */
+	public void clear() {
+        super.clear();
+		this.setType(PropertyType.STRING);
+		this.setCardinality(1);
+		this.setVariable(null);
+		this.setRange(null);
+		this.setIncludes(null);
+		this.setExcludes(null);
+		this.setOptions(null);
+		this.setRegex(null);
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+			this.setVariable(this.getString(Constants.KEY_VARIABLE));
+			this.setCardinality(this.getInteger(Constants.KEY_CARDINALITY, this.getCardinality()));
+
+			final String typeVal = this.getString(Constants.KEY_TYPE);
+			if ( typeVal != null ) {
+                this.setType(PropertyType.valueOf(typeVal.toUpperCase()));				
+			}
+			final JsonValue rangeVal = this.getAttributes().remove(Constants.KEY_RANGE);
+			if ( rangeVal != null ) {
+				final Range range = new Range();
+				range.fromJSONObject(rangeVal.asJsonObject());
+				this.setRange(range);
+			}
+			final JsonValue incs = this.getAttributes().remove(Constants.KEY_INCLUDES);
+			if ( incs != null ) {
+				final List<String> list = new ArrayList<>();
+				for(final JsonValue innerVal : incs.asJsonArray()) {
+                    list.add(getString(innerVal));
+                }
+                this.setIncludes(list.toArray(new String[list.size()]));
+			}
+			final JsonValue excs = this.getAttributes().remove(Constants.KEY_EXCLUDES);
+			if ( excs != null ) {
+				final List<String> list = new ArrayList<>();
+				for(final JsonValue innerVal : excs.asJsonArray()) {
+                    list.add(getString(innerVal));
+                }
+                this.setExcludes(list.toArray(new String[list.size()]));
+			}
+			final JsonValue opts = this.getAttributes().remove(Constants.KEY_OPTIONS);
+			if ( opts != null ) {
+				final List<Option> list = new ArrayList<>();
+				for(final JsonValue innerVal : opts.asJsonArray()) {
+					final Option o = new Option();
+					o.fromJSONObject(innerVal.asJsonObject());
+					list.add(o);
+                }
+				this.setOptions(list);
+			}
+			this.setRegex(this.getString(Constants.KEY_REGEX));
+ 		} catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+	}
+	
+    /**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objectBuilder = super.createJson();
+
+		if ( this.getType() != null ) {
+			this.setString(objectBuilder, Constants.KEY_TYPE, this.getType().name());
+	    }
+		if ( this.getCardinality() != 1 ) {
+			objectBuilder.add(Constants.KEY_CARDINALITY, this.getCardinality());
+		}
+	    this.setString(objectBuilder, Constants.KEY_VARIABLE, this.getVariable());
+		
+		if ( this.range != null ) {
+			objectBuilder.add(Constants.KEY_RANGE, this.range.toJSONObject());
+		}
+		if ( this.includes != null && this.includes.length > 0 ) {
+			final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+			for(final String v : this.includes) {
+				arrayBuilder.add(v);
+			}
+			objectBuilder.add(Constants.KEY_INCLUDES, arrayBuilder);
+		}
+		if ( this.excludes != null && this.excludes.length > 0 ) {
+			final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+			for(final String v : this.excludes) {
+				arrayBuilder.add(v);
+			}
+			objectBuilder.add(Constants.KEY_EXCLUDES, arrayBuilder);
+		}
+		if ( this.options != null && !this.options.isEmpty()) {
+			final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+            for(final Option o : this.options) {
+				arrayBuilder.add(o.toJSONObject());
+			}
+			objectBuilder.add(Constants.KEY_OPTIONS, arrayBuilder);
+		}
+		this.setString(objectBuilder, Constants.KEY_REGEX, this.getRegex());
+		
+		return objectBuilder;
+	}
+
+    /**
+	 * Get the property type
+	 * @return the type
+	 */
+	public PropertyType getType() {
+		return type;
+	}
+
+	/**
+	 * Set the property type
+	 * @param type the type to set
+	 */
+	public void setType(final PropertyType type) {
+		this.type = type;
+	}
+
+	/**
+	 * Get the cardinality
+	 * @return the cardinality
+	 */
+	public int getCardinality() {
+		return cardinality;
+	}
+
+	/**
+	 * Set the cardinality
+	 * @param cardinality the cardinality to set
+	 */
+	public void setCardinality(final int cardinality) {
+		this.cardinality = cardinality;
+	}
+
+	/**
+	 * Get the variable
+	 * @return the variable
+	 */
+	public String getVariable() {
+		return variable;
+	}
+
+	/**
+	 * Set the variable
+	 * @param variable the variable to set
+	 */
+	public void setVariable(final String variable) {
+		this.variable = variable;
+	}
+
+	/**
+	 * Get the range
+	 * @return the range or {@code null}
+	 */
+	public Range getRange() {
+		return range;
+	}
+
+	/**
+	 * Set the range
+	 * @param range the range to set
+	 */
+	public void setRange(final Range range) {
+		this.range = range;
+	}
+
+	/**
+	 * Get the includes
+	 * @return the includes or {@code null}
+	 */
+	public String[] getIncludes() {
+		return includes;
+	}
+
+	/**
+	 * Set the includes
+	 * @param includes the includes to set
+	 */
+	public void setIncludes(final String[] includes) {
+		this.includes = includes;
+	}
+
+	/**
+	 * Get the excludes
+	 * @return the excludes or {@code null}
+	 */
+	public String[] getExcludes() {
+		return excludes;
+	}
+
+	/**
+	 * Set the excludes
+	 * @param excludes the excludes to set
+	 */
+	public void setExcludes(final String[] excludes) {
+		this.excludes = excludes;
+	}
+
+	/**
+	 * Get the list of options
+	 * @return the options or {@code null}
+	 */
+	public List<Option> getOptions() {
+		return options;
+	}
+
+	/**
+	 * Set the list of options
+	 * @param options the options to set
+	 */
+	public void setOptions(final List<Option> options) {
+		this.options = options;
+	}
+
+	/**
+	 * Get the regex
+	 * @return the regex
+	 */
+	public String getRegex() {
+		return regex;
+	}
+
+	/**
+	 * Set the regex
+	 * @param regex the regex to set
+	 */
+	public void setRegex(final String regex) {
+		this.regex = regex;
+	}
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyType.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyType.java
new file mode 100644
index 0000000..e0d2889
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyType.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+/**
+ * Property type
+ */
+public enum PropertyType {
+ 
+    STRING,
+    LONG,
+    INT,
+    SHORT,
+    CHAR,
+    BYTE,
+    DOUBLE,
+    FLOAT,
+    BOOLEAN,
+    PASSWORD,
+    URL,
+    EMAIL;
+ 
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
new file mode 100644
index 0000000..01a474f
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
@@ -0,0 +1,112 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.io.IOException;
+
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+
+public class Range extends AttributeableEntity {
+
+    /** The optional min value */
+    private String min;
+
+    /** The optional max value */
+    private String max;
+
+    /**
+     * Clear the object and remove all metadata
+     */
+	public void clear() {
+        super.clear();
+        this.setMax(null);
+        this.setMin(null);
+    }
+
+	/**
+	 * Extract the metadata from the JSON object.
+	 * This method first calls {@link #clear()}
+	 * @param jsonObj The JSON Object
+	 * @throws IOException If JSON parsing fails
+	 */
+	public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+        super.fromJSONObject(jsonObj);
+        try {
+			this.setMin(this.getString(Constants.KEY_MIN));
+			this.setMax(this.getString(Constants.KEY_MAX));
+ 		} catch (final JsonException | IllegalArgumentException e) {
+            throw new IOException(e);
+        }
+	}
+
+	/**
+     * Get the min value
+	 * @return the min
+	 */
+	public String getMin() {
+		return min;
+	}
+
+	/**
+     * Set the min value
+	 * @param min the min to set
+	 */
+	public void setMin(final String min) {
+		this.min = min;
+	}
+
+	/**
+     * Get the max value
+	 * @return the max
+	 */
+	public String getMax() {
+		return max;
+	}
+
+	/**
+     * Set the max value
+	 * @param max the max to set
+	 */
+	public void setMax(final String max) {
+		this.max = max;
+    }
+
+	/**
+     * Convert this object into JSON
+     *
+     * @return The json object builder
+     * @throws IOException If generating the JSON fails
+     */
+    JsonObjectBuilder createJson() throws IOException {
+		final JsonObjectBuilder objectBuilder = super.createJson();
+
+		this.setString(objectBuilder, Constants.KEY_MIN, this.getMin());
+		this.setString(objectBuilder, Constants.KEY_MAX, this.getMax());
+
+		return objectBuilder;
+	}
+
+	/* (non-Javadoc)
+	 * @see java.lang.Object#toString()
+	 */
+	@Override
+	public String toString() {
+		return "Range [min=" + getMax() + ", max=" + getMax() + "]";
+	}	
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Validator.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Validator.java
new file mode 100644
index 0000000..546fa1c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Validator.java
@@ -0,0 +1,341 @@
+/*
+ * 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.sling.feature.extension.apiregions.api.config;
+
+import java.lang.reflect.Array;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Validate values
+ */
+public class Validator {
+	
+	public interface ValidationResult {
+
+		boolean isValid();
+
+		List<String> getErrors();
+	}
+
+	/**
+	 * Validate the value against the property definition
+	 * @return {@code null} if the value is valid, a human readable validation error message otherwise
+	 */
+	public static ValidationResult validate(final Property prop, final Object value) {
+		final List<String> messages = new ArrayList<>();
+		if ( value == null ) {
+			messages.add("No value provided");
+		} else {
+			final List<Object> values;
+			if ( value.getClass().isArray() ) {
+				// array
+				values = new ArrayList<>();
+                for(int i=0;i<Array.getLength(value);i++) {
+					values.add(Array.get(value, i));
+				}
+			} else if ( value instanceof Collection ) { 
+				// collection
+				values = new ArrayList<>();
+				final Collection<?> c = (Collection<?>)value;
+				for(final Object o : c) {
+					values.add(o);
+				}
+			} else {
+				// single value
+				values = null;
+				validateValue(prop, value, messages);
+			}
+
+			if ( values != null ) {
+				// array or collection
+				if ( prop.getCardinality() > 0 && values.size() > prop.getCardinality() ) {
+                    messages.add("Array/collection contains too many elements, only " + prop.getCardinality() + " allowed");
+				}
+				for(final Object val : values) {
+					validateValue(prop, val, messages);
+				}
+				if ( prop.getIncludes() != null ) {
+					for(final String inc : prop.getIncludes()) {
+						boolean found = false;
+						for(final Object val : values) {
+                            if ( inc.equals(val.toString())) {
+								found = true;
+								break;
+							}
+						}
+						if ( !found ) {
+                            messages.add("Required included value " + inc + " not found");
+						}
+					}
+				}
+				if ( prop.getExcludes() != null ) {
+					for(final String exc : prop.getExcludes()) {
+						boolean found = false;
+						for(final Object val : values) {
+                            if ( exc.equals(val.toString())) {
+								found = true;
+								break;
+							}
+						}
+						if ( found ) {
+                            messages.add("Required excluded value " + exc + " found");
+						}
+					}
+				}
+			}
+		}
+		return new ValidationResult(){
+			
+			public boolean isValid() {
+				return messages.isEmpty();
+			}
+
+			public List<String> getErrors() {
+				return messages;
+			}	
+		};
+	}
+
+	static void validateValue(final Property prop, final Object value, final List<String> messages) {
+		if ( value != null ) {
+			switch ( prop.getType() ) {
+				case BOOLEAN : validateBoolean(prop, value, messages);
+							   break;
+				case BYTE : validateByte(prop, value, messages);
+							break;
+				case CHAR : validateChar(prop, value, messages);
+							break;
+				case DOUBLE : validateDouble(prop, value, messages); 
+							break;
+				case FLOAT : validateFloat(prop, value, messages); 
+							break;
+				case INT : validateInt(prop, value, messages);
+							break;
+				case LONG : validateLong(prop, value, messages);
+							break;
+				case SHORT : validateShort(prop, value, messages);
+							break;
+				case STRING : // no special validation for string
+							break;
+				case EMAIL : validateEmail(prop, value, messages); 
+							break;
+				case PASSWORD : validatePassword(prop, value, messages);
+							break;
+				case URL : validateURL(prop, value, messages);
+							break;
+				default : messages.add("Unable to validate value - unknown property type : " + prop.getType());
+			}
+			if ( prop.getRegex() != null ) {
+				final Pattern p = Pattern.compile(prop.getRegex());
+				if ( !p.matcher(value.toString()).matches() ) {
+                    messages.add("Value " + value + " does not match regex " + prop.getRegex());
+				}
+			}
+			if ( prop.getOptions() != null ) {
+				boolean found = false;
+				for(final Option opt : prop.getOptions()) {
+					if ( opt.getValue().equals(value.toString() ) ) {
+						found = true; 
+					}
+				}
+				if ( !found ) {
+					messages.add("Value " + value + " does not match provided options");
+				}
+			}
+		} else {
+			messages.add("Null value provided for validation");
+		}
+	}
+	
+	static void validateBoolean(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Boolean) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				if ( ! v.equalsIgnoreCase("true") && !v.equalsIgnoreCase("false") ) {
+					messages.add("Boolean value must either be true or false, but not " + value);
+				}
+			} else {
+				messages.add("Boolean value must either be of type Boolean or String : " + value);
+			}
+		}
+	}
+
+	static void validateByte(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Byte) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				try {
+					validateRange(prop, Byte.valueOf(v), messages);
+				} catch ( final NumberFormatException nfe ) {
+                    messages.add("Value is not a valid Byte : " + value);
+				}
+			} else {
+				messages.add("Byte value must either be of type Byte or String : " + value);
+			}
+		} else {
+			validateRange(prop, (Byte)value, messages);
+		}
+	}
+
+	static void validateShort(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Short) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				try {
+					validateRange(prop, Short.valueOf(v), messages);
+				} catch ( final NumberFormatException nfe ) {
+                    messages.add("Value is not a valid Short : " + value);
+				}
+			} else {
+				messages.add("Short value must either be of type Short or String : " + value);
+			}
+		} else {
+			validateRange(prop, (Short)value, messages);
+		}
+	}
+
+	static void validateInt(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Integer) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				try {
+					validateRange(prop, Integer.valueOf(v), messages);
+				} catch ( final NumberFormatException nfe ) {
+                    messages.add("Value is not a valid Integer : " + value);
+				}
+			} else {
+				messages.add("Integer value must either be of type Integer or String : " + value);
+			}
+		} else {
+			validateRange(prop, (Integer)value, messages);
+		}
+	}
+
+	static void validateLong(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Long) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				try {
+					validateRange(prop, Long.valueOf(v), messages);
+				} catch ( final NumberFormatException nfe ) {
+                    messages.add("Value is not a valid Long : " + value);
+				}
+			} else {
+				messages.add("Long value must either be of type Long or String : " + value);
+			}
+		} else {
+			validateRange(prop, (Long)value, messages);
+		}
+	}
+
+	static void validateFloat(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Float) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				try {
+					validateRange(prop, Float.valueOf(v), messages);
+				} catch ( final NumberFormatException nfe ) {
+                    messages.add("Value is not a valid Float : " + value);
+				}
+			} else {
+				messages.add("Float value must either be of type Float or String : " + value);
+			}
+		} else {
+			validateRange(prop, (Float)value, messages);
+		}
+	}
+
+	static void validateDouble(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Double) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				try {
+					validateRange(prop, Double.valueOf(v), messages);
+				} catch ( final NumberFormatException nfe ) {
+                    messages.add("Value is not a valid Double : " + value);
+				}
+			} else {
+				messages.add("Double value must either be of type Double or String : " + value);
+			}
+		} else {
+			validateRange(prop, (Double)value, messages);
+		}
+	}
+
+	static void validateChar(final Property prop, final Object value, final List<String> messages) {
+        if ( ! (value instanceof Character) ) {
+			if ( value instanceof String ) {
+				final String v = (String)value;
+				if ( v.length() > 1 ) {
+                    messages.add("Value is not a valid Character : " + value);
+				}
+			} else {
+				messages.add("Character value must either be of type Character or String : " + value);
+			}
+		}
+	}
+
+	static void validateURL(final Property prop, final Object value, final List<String> messages) {
+		final String val = value.toString();
+		try {
+			new URL(val);
+		} catch ( final MalformedURLException mue) {
+			messages.add("Value is not a valid URL : " + val);
+		}
+	}
+
+	static void validateEmail(final Property prop, final Object value, final List<String> messages) {
+		final String val = value.toString();
+		// poor man's validation (should probably use InternetAddress)
+		if ( !val.contains("@") ) {
+			messages.add("Not a valid email address " + val);
+		}
+	}
+
+	static void validatePassword(final Property prop, final Object value, final List<String> messages) {
+		if ( prop.getVariable() == null ) {
+			messages.add("Value for a password must use a variable");
+		}
+	}
+
+	static void validateRange(final Property prop, final Number value, final List<String> messages) {
+	    if ( prop.getRange() != null ) {
+			try {
+                if ( prop.getRange().getMin() != null ) {
+					final long min = Long.valueOf(prop.getRange().getMin());
+					if ( value.longValue() < min ) {
+                         messages.add("Value " + value + " is too low; should not be lower than " + prop.getRange().getMin());
+					}
+				}
+                if ( prop.getRange().getMax() != null ) {
+					final long max = Long.valueOf(prop.getRange().getMax());
+					if ( value.longValue() > max ) {
+                         messages.add("Value " + value + " is too high; should not be higher than " + prop.getRange().getMax());
+					}
+				}
+			} catch ( final NumberFormatException nfe) {
+				messages.add("Invalid range specified in " + prop.getRange());
+			}
+		}
+	}
+
+}
\ No newline at end of file