You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by jk...@apache.org on 2022/03/23 14:04:04 UTC

[unomi] branch master updated: UNOMI-555 : add possiblity to store jsonSchema (#393)

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

jkevan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/master by this push:
     new 15f5c3b  UNOMI-555 : add possiblity to store jsonSchema (#393)
15f5c3b is described below

commit 15f5c3b7cbce14c5f933564f20189fb2366ef20e
Author: jsinovassin <58...@users.noreply.github.com>
AuthorDate: Wed Mar 23 15:04:00 2022 +0100

    UNOMI-555 : add possiblity to store jsonSchema (#393)
    
    * UNOMI-555 : add possiblity to store jsonSchema
    
    * handle feedback
---
 .../apache/unomi/api/schema/UnomiJSONSchema.java   |  71 +++++
 .../unomi/api/schema/json/JSONArrayType.java       |   6 +-
 .../unomi/api/schema/json/JSONBooleanType.java     |   6 +-
 .../apache/unomi/api/schema/json/JSONEnumType.java |   6 +-
 .../unomi/api/schema/json/JSONIntegerType.java     |   6 +-
 .../apache/unomi/api/schema/json/JSONNullType.java |   6 +-
 .../unomi/api/schema/json/JSONNumberType.java      |   6 +-
 .../unomi/api/schema/json/JSONObjectType.java      |  18 +-
 .../apache/unomi/api/schema/json/JSONSchema.java   |   7 +-
 .../unomi/api/schema/json/JSONStringType.java      |   6 +-
 .../org/apache/unomi/api/schema/json/JSONType.java |  19 +-
 .../unomi/api/schema/json/JSONTypeFactory.java     |  16 +-
 .../apache/unomi/api/services/SchemaRegistry.java  |  78 ++++-
 extensions/groovy-actions/rest/pom.xml             |   2 +-
 .../graphql/schema/GraphQLSchemaProvider.java      |  30 +-
 .../test/java/org/apache/unomi/itests/AllITs.java  |   3 +-
 .../test/java/org/apache/unomi/itests/BaseIT.java  |  17 +-
 .../org/apache/unomi/itests/ContextServletIT.java  |  65 +++--
 .../java/org/apache/unomi/itests/JSONSchemaIT.java | 131 +++++++++
 .../java/org/apache/unomi/itests/TestUtils.java    |   9 +
 .../resources/schemas/events/test-event-type.json  |   3 +-
 .../{test-event-type.json => test-invalid.json}    |   6 +-
 .../main/resources/etc/custom.system.properties    |   2 +
 .../META-INF/cxs/mappings/jsonschema.json          |  25 ++
 pom.xml                                            |   1 +
 .../unomi/rest/endpoints/JsonSchemaEndPoint.java   | 109 +++++++
 .../services/impl/events/EventServiceImpl.java     |   3 +-
 .../services/impl/schemas/SchemaRegistryImpl.java  | 317 +++++++++++----------
 .../impl/schemas/UnomiPropertyTypeKeyword.java     |  37 ++-
 .../services/listener/JsonSchemaListener.java      | 170 +++++++++++
 .../resources/OSGI-INF/blueprint/blueprint.xml     |  20 +-
 .../main/resources/org.apache.unomi.services.cfg   |   5 +-
 .../shell/commands/DeploymentCommandSupport.java   |   4 +-
 33 files changed, 927 insertions(+), 283 deletions(-)

diff --git a/api/src/main/java/org/apache/unomi/api/schema/UnomiJSONSchema.java b/api/src/main/java/org/apache/unomi/api/schema/UnomiJSONSchema.java
new file mode 100644
index 0000000..3ae5077
--- /dev/null
+++ b/api/src/main/java/org/apache/unomi/api/schema/UnomiJSONSchema.java
@@ -0,0 +1,71 @@
+/*
+ * 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.unomi.api.schema;
+
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.MetadataItem;
+
+/**
+ * Object which represents a JSON schema stored in the persistence service
+ */
+public class UnomiJSONSchema extends MetadataItem {
+    public static final String ITEM_TYPE = "jsonSchema";
+
+    private String id;
+    private String schema;
+    private String target;
+
+    public UnomiJSONSchema(){}
+
+    /**
+     * Instantiates a new JSON schema with an id and a schema as string
+     *
+     * @param id     id of the schema
+     * @param schema as string
+     * @param target of the schema
+     */
+    public UnomiJSONSchema(String id, String schema, String target) {
+        super(new Metadata(id));
+        this.id = id;
+        this.schema = schema;
+        this.target = target;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getSchema() {
+        return schema;
+    }
+
+    public void setSchema(String schema) {
+        this.schema = schema;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+}
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONArrayType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONArrayType.java
index add106d..aa8cc2a 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONArrayType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONArrayType.java
@@ -16,8 +16,6 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.List;
 import java.util.Map;
 
@@ -26,8 +24,8 @@ public class JSONArrayType extends JSONType {
     List<JSONType> items;
     JSONType contains;
 
-    public JSONArrayType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONArrayType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("array");
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONBooleanType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONBooleanType.java
index cfaa734..aea431e 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONBooleanType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONBooleanType.java
@@ -16,13 +16,11 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.Map;
 
 public class JSONBooleanType extends JSONType {
-    public JSONBooleanType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONBooleanType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("boolean");
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONEnumType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONEnumType.java
index d2e18a0..57ba1e6 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONEnumType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONEnumType.java
@@ -16,13 +16,11 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.Map;
 
 public class JSONEnumType extends JSONType {
-    public JSONEnumType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONEnumType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("enum");
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONIntegerType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONIntegerType.java
index 6aa9a32..9ae40dd 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONIntegerType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONIntegerType.java
@@ -16,13 +16,11 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.Map;
 
 public class JSONIntegerType extends JSONType {
-    public JSONIntegerType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONIntegerType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("integer");
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNullType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONNullType.java
index ec0adde..809a29f 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNullType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONNullType.java
@@ -16,13 +16,11 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.Map;
 
 public class JSONNullType extends JSONType {
-    public JSONNullType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONNullType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("null");
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNumberType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONNumberType.java
index bb6bf13..d591777 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONNumberType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONNumberType.java
@@ -16,13 +16,11 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.Map;
 
 public class JSONNumberType extends JSONType {
-    public JSONNumberType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONNumberType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("number");
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONObjectType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONObjectType.java
index 45b25e0..da28f41 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONObjectType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONObjectType.java
@@ -16,37 +16,35 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 public class JSONObjectType extends JSONType {
 
-    Map<String,List<JSONType>> properties = new HashMap<>();
+    Map<String, List<JSONType>> properties = new HashMap<>();
     JSONType additionalProperties;
-    Map<String,List<JSONType>> patternProperties = new HashMap<>();
+    Map<String, List<JSONType>> patternProperties = new HashMap<>();
     JSONType propertyNames;
 
     int maxProperties;
 
-    public JSONObjectType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONObjectType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("object");
-        Map<String,Object> propertiesTree = (Map<String,Object>) schemaTree.get("properties");
+        Map<String, Object> propertiesTree = (Map<String, Object>) schemaTree.get("properties");
         if (propertiesTree != null) {
             propertiesTree.entrySet().forEach(entry -> {
-                properties.put(entry.getKey(), jsonTypeFactory.getTypes((Map<String,Object>)entry.getValue()));
+                properties.put(entry.getKey(), jsonTypeFactory.getTypes((Map<String, Object>) entry.getValue()));
             });
         }
     }
 
-    public Map<String,List<JSONType>> getProperties() {
+    public Map<String, List<JSONType>> getProperties() {
         return properties;
     }
 
-    public void setProperties(Map<String,List<JSONType>> properties) {
+    public void setProperties(Map<String, List<JSONType>> properties) {
         this.properties = properties;
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONSchema.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONSchema.java
index 544d009..1e65f8e 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONSchema.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONSchema.java
@@ -18,7 +18,6 @@
 package org.apache.unomi.api.schema.json;
 
 import org.apache.unomi.api.PluginType;
-import org.apache.unomi.api.services.SchemaRegistry;
 
 import java.util.List;
 import java.util.Map;
@@ -34,11 +33,11 @@ public class JSONSchema extends JSONType implements PluginType {
     private String name;
     private String version;
 
-    public JSONSchema(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONSchema(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         schemaId = (String) schemaTree.get("$id");
         if (schemaTree.containsKey("self")) {
-            Map<String,Object> self = (Map<String,Object>) schemaTree.get("self");
+            Map<String, Object> self = (Map<String, Object>) schemaTree.get("self");
             name = (String) self.get("name");
             vendor = (String) self.get("vendor");
             version = (String) self.get("version");
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONStringType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONStringType.java
index c1b9c10..c63f6b5 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONStringType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONStringType.java
@@ -16,13 +16,11 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.Map;
 
 public class JSONStringType extends JSONType {
-    public JSONStringType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
-        super(schemaTree, jsonTypeFactory, schemaRegistry);
+    public JSONStringType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
+        super(schemaTree, jsonTypeFactory);
         setType("string");
     }
 }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONType.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONType.java
index 3b60b0f..d1c0474 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONType.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONType.java
@@ -16,8 +16,6 @@
  */
 package org.apache.unomi.api.schema.json;
 
-import org.apache.unomi.api.services.SchemaRegistry;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -31,18 +29,15 @@ public class JSONType {
     List<JSONType> anyOf;
     List<JSONType> oneOf;
 
-    Map<String,Object> customKeywords;
+    Map<String, Object> customKeywords;
 
-    protected Map<String,Object> schemaTree;
+    protected Map<String, Object> schemaTree;
 
     protected JSONTypeFactory jsonTypeFactory;
 
-    protected SchemaRegistry schemaRegistry;
-
-    public JSONType(Map<String,Object> schemaTree, JSONTypeFactory jsonTypeFactory, SchemaRegistry schemaRegistry) {
+    public JSONType(Map<String, Object> schemaTree, JSONTypeFactory jsonTypeFactory) {
         this.schemaTree = schemaTree;
         this.jsonTypeFactory = jsonTypeFactory;
-        this.schemaRegistry = schemaRegistry;
     }
 
     public String getName() {
@@ -69,20 +64,16 @@ public class JSONType {
         return jsonTypeFactory;
     }
 
-    public SchemaRegistry getSchemaRegistry() {
-        return schemaRegistry;
-    }
-
     public String getRef() {
         ref = (String) schemaTree.get("$ref");
         return ref;
     }
 
     public List<JSONType> getAllOf() {
-        List<Map<String,Object>> allOfTree = (List<Map<String,Object>>) schemaTree.get("allOf");
+        List<Map<String, Object>> allOfTree = (List<Map<String, Object>>) schemaTree.get("allOf");
         List<JSONType> allOfTypes = new ArrayList<>();
         if (allOfTree != null) {
-            for (Map<String,Object> allOfEntry : allOfTree) {
+            for (Map<String, Object> allOfEntry : allOfTree) {
                 List<JSONType> entryTypes = jsonTypeFactory.getTypes(allOfEntry);
                 allOfTypes.addAll(entryTypes);
             }
diff --git a/api/src/main/java/org/apache/unomi/api/schema/json/JSONTypeFactory.java b/api/src/main/java/org/apache/unomi/api/schema/json/JSONTypeFactory.java
index da79016..05ac24d 100644
--- a/api/src/main/java/org/apache/unomi/api/schema/json/JSONTypeFactory.java
+++ b/api/src/main/java/org/apache/unomi/api/schema/json/JSONTypeFactory.java
@@ -17,6 +17,8 @@
 package org.apache.unomi.api.schema.json;
 
 import org.apache.unomi.api.services.SchemaRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
@@ -27,6 +29,8 @@ import java.util.Map;
 
 public class JSONTypeFactory {
 
+    private static final Logger logger = LoggerFactory.getLogger(JSONTypeFactory.class);
+
     Map<String, Class<? extends JSONType>> jsonTypes = new HashMap<>();
 
     SchemaRegistry schemaRegistry;
@@ -42,7 +46,7 @@ public class JSONTypeFactory {
         jsonTypes.put("null", JSONNullType.class);
     }
 
-    List<JSONType> getTypes(Map<String,Object> schemaTree) {
+    List<JSONType> getTypes(Map<String, Object> schemaTree) {
         if (schemaTree.containsKey("$ref")) {
             String schemaId = (String) schemaTree.get("$ref");
             JSONSchema refSchema = schemaRegistry.getSchema(schemaId);
@@ -54,7 +58,7 @@ public class JSONTypeFactory {
         }
         if (schemaTree.containsKey("enum")) {
             List<JSONType> result = new ArrayList<>();
-            result.add(new JSONEnumType(schemaTree, this, schemaRegistry));
+            result.add(new JSONEnumType(schemaTree, this));
             return result;
         }
         Object typeObject = schemaTree.get("type");
@@ -77,12 +81,12 @@ public class JSONTypeFactory {
                 continue;
             }
             Class<? extends JSONType> typeClass = jsonTypes.get(type);
-            Constructor<? extends JSONType> constructor = null;
+            Constructor<? extends JSONType> constructor;
             try {
-                constructor = typeClass.getConstructor(Map.class, JSONTypeFactory.class, SchemaRegistry.class);
-                resultJsonTypes.add(constructor.newInstance(schemaTree, this, schemaRegistry));
+                constructor = typeClass.getConstructor(Map.class, JSONTypeFactory.class);
+                resultJsonTypes.add(constructor.newInstance(schemaTree, this));
             } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
-                e.printStackTrace();
+                logger.error("Error while building object type", e);
             }
         }
         return resultJsonTypes;
diff --git a/api/src/main/java/org/apache/unomi/api/services/SchemaRegistry.java b/api/src/main/java/org/apache/unomi/api/services/SchemaRegistry.java
index 1bdea07..b25a613 100644
--- a/api/src/main/java/org/apache/unomi/api/services/SchemaRegistry.java
+++ b/api/src/main/java/org/apache/unomi/api/services/SchemaRegistry.java
@@ -17,21 +17,93 @@
 
 package org.apache.unomi.api.services;
 
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.PartialList;
 import org.apache.unomi.api.schema.json.JSONSchema;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
+/**
+ * Service that allow to manage JSON schema. It allows to get, save and delete schemas
+ */
 public interface SchemaRegistry {
 
+    /**
+     * Retrieves json schema metadatas, ordered according to the specified {@code sortBy} String and and paged: only {@code size} of them
+     * are retrieved, starting with the {@code
+     * offset}-th one.
+     *
+     * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements
+     * @param size   a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved
+     * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering elements according to the property order in the
+     *               String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by
+     *               a column ({@code :}) and an order specifier: {@code asc} or {@code desc}.
+     * @return a {@link PartialList} of json schema metadata
+     */
+    PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String sortBy);
+
+    /**
+     * Verify if an object is valid against a schema
+     *
+     * @param object   to validate
+     * @param schemaId id of the schema used for the validation
+     * @return true is the object is valid
+     */
     boolean isValid(Object object, String schemaId);
 
+
+    /**
+     * Get a schema matching by a schema id
+     *
+     * @param schemaId Id of the schema
+     * @return A JSON schema
+     */
     JSONSchema getSchema(String schemaId);
 
-    List<JSONSchema> getTargetSchemas(String target);
+    /**
+     * Get a list a {@link org.apache.unomi.api.schema.json.JSONSchema}
+     *
+     * @param target to filter the schemas
+     * @return a list of JSONSchema
+     */
+    List<JSONSchema> getSchemasByTarget(String target);
+
+    /**
+     * Save a new schema or update a schema
+     *
+     * @param schema as a String value
+     */
+    void saveSchema(String schema);
+
+    /**
+     * Save a new schema or update a schema
+     *
+     * @param schemaStream inputStream of the schema
+     */
+    void saveSchema(InputStream schemaStream) throws IOException;
 
-    String registerSchema(String target, InputStream jsonSchemaInputStream);
+    /**
+     * Load a predefined schema into memory
+     *
+     * @param schemaStream inputStream of the schema
+     */
+    void loadPredefinedSchema(InputStream schemaStream);
 
-    boolean unregisterSchema(String target, String schemaId);
+    /**
+     * Delete a schema according to its id
+     *
+     * @param schemaId id of the schema to delete
+     * @return true if the schema has been deleted
+     */
+    boolean deleteSchema(String schemaId);
 
+    /**
+     * Delete a schema
+     *
+     * @param schemaStream inputStream of the schema to delete
+     * @return true if the schema has been deleted
+     */
+    boolean deleteSchema(InputStream schemaStream);
 }
diff --git a/extensions/groovy-actions/rest/pom.xml b/extensions/groovy-actions/rest/pom.xml
index 30c0854..e1f7ff2 100644
--- a/extensions/groovy-actions/rest/pom.xml
+++ b/extensions/groovy-actions/rest/pom.xml
@@ -92,4 +92,4 @@
             <artifactId>commons-io</artifactId>
         </dependency>
     </dependencies>
-</project>
\ No newline at end of file
+</project>
diff --git a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java
index 5a02c80..8347fd7 100644
--- a/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java
+++ b/graphql/cxs-impl/src/main/java/org/apache/unomi/graphql/schema/GraphQLSchemaProvider.java
@@ -331,7 +331,7 @@ public class GraphQLSchemaProvider {
     }
 
     private void registerDynamicUnomiInputEvents(GraphQLSchema.Builder schemaBuilder) {
-        final List<JSONSchema> unomiEventTypes = schemaRegistry.getTargetSchemas("events");
+        final List<JSONSchema> unomiEventTypes = schemaRegistry.getSchemasByTarget("events");
 
         if (!unomiEventTypes.isEmpty()) {
             for (JSONSchema unomiEventType : unomiEventTypes) {
@@ -353,7 +353,7 @@ public class GraphQLSchemaProvider {
     }
 
     private void registerDynamicUnomiOutputEvents(GraphQLSchema.Builder schemaBuilder) {
-        final List<JSONSchema> unomiEventTypes = schemaRegistry.getTargetSchemas("events");
+        final List<JSONSchema> unomiEventTypes = schemaRegistry.getSchemasByTarget("events");
 
         if (!unomiEventTypes.isEmpty()) {
             final GraphQLCodeRegistry.Builder codeRegisterBuilder = graphQLAnnotations.getContainer().getCodeRegistryBuilder();
@@ -377,8 +377,8 @@ public class GraphQLSchemaProvider {
     }
 
     private void registerDynamicInputFilterFields(final String typeName,
-                                                  final Class<?> annotatedClass,
-                                                  final Collection<DefinitionType> propertyTypes) {
+            final Class<?> annotatedClass,
+            final Collection<DefinitionType> propertyTypes) {
         final GraphQLInputObjectType originalObject = getInputObjectType(annotatedClass);
 
         final List<GraphQLInputObjectField> inputObjectFields =
@@ -391,17 +391,17 @@ public class GraphQLSchemaProvider {
     }
 
     private void registerDynamicOutputFields(final String graphQLTypeName,
-                                             final Class<?> annotatedClass,
-                                             final Class<? extends DynamicFieldDataFetcher> fetcherClass,
-                                             final Collection<DefinitionType> propertyTypes) {
+            final Class<?> annotatedClass,
+            final Class<? extends DynamicFieldDataFetcher> fetcherClass,
+            final Collection<DefinitionType> propertyTypes) {
         final GraphQLObjectType objectType = graphQLAnnotations.object(annotatedClass);
         registerDynamicOutputFields(graphQLTypeName, objectType, fetcherClass, propertyTypes);
     }
 
     private void registerDynamicOutputFields(final String graphQLTypeName,
-                                             final GraphQLObjectType graphQLObjectType,
-                                             final Class<? extends DynamicFieldDataFetcher> fetcherClass,
-                                             final Collection<DefinitionType> propertyTypes) {
+            final GraphQLObjectType graphQLObjectType,
+            final Class<? extends DynamicFieldDataFetcher> fetcherClass,
+            final Collection<DefinitionType> propertyTypes) {
         final GraphQLCodeRegistry.Builder codeRegisterBuilder = graphQLAnnotations.getContainer().getCodeRegistryBuilder();
 
         final List<GraphQLFieldDefinition> fieldDefinitions = new ArrayList<>();
@@ -572,15 +572,15 @@ public class GraphQLSchemaProvider {
     }
 
     private void registerDynamicInputFields(final String graphQLTypeName,
-                                            final Class<?> clazz,
-                                            final Collection<DefinitionType> propertyTypes) {
+            final Class<?> clazz,
+            final Collection<DefinitionType> propertyTypes) {
         final GraphQLInputObjectType inputObjectType = getInputObjectType(clazz);
         registerDynamicInputFields(graphQLTypeName, inputObjectType, propertyTypes);
     }
 
     private void registerDynamicInputFields(final String graphQLTypeName,
-                                            final GraphQLInputObjectType graphQLInputObjectType,
-                                            final Collection<DefinitionType> propertyTypes) {
+            final GraphQLInputObjectType graphQLInputObjectType,
+            final Collection<DefinitionType> propertyTypes) {
         final List<GraphQLInputObjectField> fieldDefinitions = new ArrayList<>();
 
         propertyTypes.forEach(propertyType -> {
@@ -650,7 +650,7 @@ public class GraphQLSchemaProvider {
         }
 
         // now add all unomi defined event types
-        final List<JSONSchema> unomiEventTypes = schemaRegistry.getTargetSchemas("events");
+        final List<JSONSchema> unomiEventTypes = schemaRegistry.getSchemasByTarget("events");
         unomiEventTypes.forEach(eventType -> {
             final String typeName = UnomiToGraphQLConverter.convertEventType(eventType.getName());
             final GraphQLInputType eventInputType = (GraphQLInputType) getFromTypeRegistry(typeName + "Input");
diff --git a/itests/src/test/java/org/apache/unomi/itests/AllITs.java b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
index facb730..b198a93 100644
--- a/itests/src/test/java/org/apache/unomi/itests/AllITs.java
+++ b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
@@ -58,7 +58,8 @@ import org.junit.runners.Suite.SuiteClasses;
         GraphQLProfileIT.class,
         GraphQLProfilePropertiesIT.class,
         GraphQLSegmentIT.class,
-        GraphQLWebSocketIT.class
+        GraphQLWebSocketIT.class,
+        JSONSchemaIT.class
 })
 public class AllITs {
 }
diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index 102d107..767020a 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -40,7 +40,6 @@ import org.apache.unomi.api.conditions.Condition;
 import org.apache.unomi.api.rules.Rule;
 import org.apache.unomi.api.services.DefinitionsService;
 import org.apache.unomi.api.services.RulesService;
-import org.apache.unomi.api.services.SchemaRegistry;
 import org.apache.unomi.lifecycle.BundleWatcher;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
 import org.apache.unomi.persistence.spi.PersistenceService;
@@ -130,10 +129,6 @@ public abstract class BaseIT {
     @Filter(timeout = 600000)
     protected ConfigurationAdmin configurationAdmin;
 
-    @Inject
-    @Filter(timeout = 600000)
-    protected SchemaRegistry schemaRegistry;
-
     private CloseableHttpClient httpClient;
 
     @Before
@@ -331,7 +326,6 @@ public abstract class BaseIT {
         persistenceService = getService(PersistenceService.class);
         definitionsService = getService(DefinitionsService.class);
         rulesService = getService(RulesService.class);
-        schemaRegistry = getService(SchemaRegistry.class);
     }
 
     public void updateConfiguration(String serviceName, String configPid, String propName, Object propValue) throws InterruptedException, IOException {
@@ -432,13 +426,13 @@ public abstract class BaseIT {
         return null;
     }
 
-    protected CloseableHttpResponse post(final String url, final String resource) {
+    protected CloseableHttpResponse post(final String url, final String resource, ContentType contentType) {
         try {
             final HttpPost request = new HttpPost(getFullUrl(url));
 
             if (resource != null) {
                 final String resourceAsString = resourceAsString(resource);
-                request.setEntity(new StringEntity(resourceAsString, JSON_CONTENT_TYPE));
+                request.setEntity(new StringEntity(resourceAsString, contentType));
             }
 
             return executeHttpRequest(request);
@@ -448,7 +442,11 @@ public abstract class BaseIT {
         return null;
     }
 
-    protected void delete(final String url) {
+    protected CloseableHttpResponse post(final String url, final String resource) {
+        return post(url,resource, JSON_CONTENT_TYPE);
+    }
+
+    protected CloseableHttpResponse delete(final String url) {
         CloseableHttpResponse response = null;
         try {
             final HttpDelete httpDelete = new HttpDelete(getFullUrl(url));
@@ -466,6 +464,7 @@ public abstract class BaseIT {
                 }
             }
         }
+        return response;
     }
 
     protected CloseableHttpResponse executeHttpRequest(HttpUriRequest request) throws IOException {
diff --git a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
index 14a6c98..60548a7 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
@@ -37,11 +37,12 @@ import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerSuite;
 import org.ops4j.pax.exam.util.Filter;
 import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.URI;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
@@ -60,6 +61,8 @@ import static org.junit.Assert.*;
 @ExamReactorStrategy(PerSuite.class)
 public class ContextServletIT extends BaseIT {
     private final static String CONTEXT_URL = "/cxs/context.json";
+    private final static String JSONSCHEMA_URL = "/cxs/jsonSchema";
+
     private final static String THIRD_PARTY_HEADER_NAME = "X-Unomi-Peer";
     private final static String TEST_EVENT_TYPE = "test-event-type";
     private final static String TEST_EVENT_TYPE_SCHEMA = "test-event-type.json";
@@ -68,8 +71,13 @@ public class ContextServletIT extends BaseIT {
     private final static String SEGMENT_ID = "test-segment-id";
     private final static int SEGMENT_NUMBER_OF_DAYS = 30;
 
+    private static final int DEFAULT_TRYING_TIMEOUT = 2000;
+    private static final int DEFAULT_TRYING_TRIES = 30;
+
     private ObjectMapper objectMapper = new ObjectMapper();
 
+    private final static Logger LOGGER = LoggerFactory.getLogger(ContextServletIT.class);
+
     @Inject
     @Filter(timeout = 600000)
     protected EventService eventService;
@@ -117,6 +125,9 @@ public class ContextServletIT extends BaseIT {
         profile = new Profile(profileId);
         profileService.save(profile);
 
+        keepTrying("Couldn't find json schema endpoint",
+                () -> get(JSONSCHEMA_URL, List.class), Objects::nonNull,
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
         refreshPersistence();
     }
 
@@ -128,13 +139,21 @@ public class ContextServletIT extends BaseIT {
         profileService.delete(profile.getItemId(), false);
         segmentService.removeSegmentDefinition(SEGMENT_ID, false);
 
-        schemaRegistry.unregisterSchema("events", TEST_EVENT_TYPE);
-        schemaRegistry.unregisterSchema("events", FLOAT_PROPERTY_EVENT_TYPE);
+        String encodedString = Base64.getEncoder()
+                .encodeToString("https://unomi.apache.org/schemas/json/events/test-event-type/1-0-0".getBytes());
+        delete(JSONSCHEMA_URL + "/" + encodedString);
+
+        encodedString = Base64.getEncoder()
+                .encodeToString("https://unomi.apache.org/schemas/json/events/float-property-type/1-0-0".getBytes());
+        delete(JSONSCHEMA_URL + "/" + encodedString);
+
+        encodedString = Base64.getEncoder()
+                .encodeToString("https://unomi.apache.org/schemas/json/events/float-property-type/1-0-0".getBytes());
+        delete(JSONSCHEMA_URL + "/" + encodedString);
     }
 
-    private void registerEventType(String jsonSchemaFileName) throws IOException {
-        InputStream jsonSchemaInputStream = bundleContext.getBundle().getResource("schemas/events/" + jsonSchemaFileName).openStream();
-        schemaRegistry.registerSchema("events", jsonSchemaInputStream);
+    private void registerEventType(String jsonSchemaFileName) {
+        post(JSONSCHEMA_URL, "schemas/events/" + jsonSchemaFileName, ContentType.TEXT_PLAIN);
     }
 
     @Test
@@ -161,7 +180,7 @@ public class ContextServletIT extends BaseIT {
         contextRequest.setEvents(Arrays.asList(event));
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
         request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request, sessionId);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -195,7 +214,7 @@ public class ContextServletIT extends BaseIT {
         contextRequest.setSessionId(session.getItemId());
         contextRequest.setEvents(Arrays.asList(event));
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request, sessionId);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -227,7 +246,7 @@ public class ContextServletIT extends BaseIT {
         contextRequest.setSessionId(session.getItemId());
         contextRequest.setEvents(Arrays.asList(event));
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request, sessionId);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -253,7 +272,7 @@ public class ContextServletIT extends BaseIT {
         contextRequest.setRequireSegments(true);
         contextRequest.setEvents(Arrays.asList(event));
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         String cookieHeaderValue = TestUtils.executeContextJSONRequest(request, sessionId).getCookieHeaderValue();
         refreshPersistence();
         Thread.sleep(1000); //Making sure DB is updated
@@ -287,7 +306,7 @@ public class ContextServletIT extends BaseIT {
         contextRequest.setRequireSegments(true);
         contextRequest.setEvents(Arrays.asList(event));
         HttpPost request = new HttpPost(regularURI);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         //The first event is with a default timestamp (now)
         String cookieHeaderValue = TestUtils.executeContextJSONRequest(request, sessionId).getCookieHeaderValue();
         refreshPersistence();
@@ -319,7 +338,7 @@ public class ContextServletIT extends BaseIT {
         contextRequest.setRequireSegments(true);
         contextRequest.setEvents(Arrays.asList(event));
         HttpPost request = new HttpPost(regularURI);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         //The first event is with a default timestamp (now)
         TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request, sessionId);
         String cookieHeaderValue = response.getCookieHeaderValue();
@@ -351,7 +370,7 @@ public class ContextServletIT extends BaseIT {
         //Act
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
         request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -381,7 +400,7 @@ public class ContextServletIT extends BaseIT {
         //Act
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
         request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -412,7 +431,7 @@ public class ContextServletIT extends BaseIT {
         //Act
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
         request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -442,7 +461,7 @@ public class ContextServletIT extends BaseIT {
         //Act
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
         request.addHeader(THIRD_PARTY_HEADER_NAME, UNOMI_KEY);
-        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(objectMapper.writeValueAsString(contextRequest), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -465,7 +484,7 @@ public class ContextServletIT extends BaseIT {
         Map<String, String> parameters = new HashMap<>();
         parameters.put("VULN_FILE_PATH", vulnFileCanonicalPath);
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(getValidatedBundleJSON("security/ognl-payload-1.json", parameters), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("security/ognl-payload-1.json", parameters), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -487,7 +506,7 @@ public class ContextServletIT extends BaseIT {
         Map<String, String> parameters = new HashMap<>();
         parameters.put("VULN_FILE_PATH", vulnFileCanonicalPath);
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(getValidatedBundleJSON("security/mvel-payload-1.json", parameters), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("security/mvel-payload-1.json", parameters), ContentType.APPLICATION_JSON));
         TestUtils.executeContextJSONRequest(request);
         refreshPersistence();
         Thread.sleep(2000); //Making sure event is updated in DB
@@ -501,7 +520,7 @@ public class ContextServletIT extends BaseIT {
 
 		Map<String,String> parameters = new HashMap<>();
 		HttpPost request = new HttpPost(URL + CONTEXT_URL);
-		request.setEntity(new StringEntity(getValidatedBundleJSON("personalization.json", parameters), ContentType.create("application/json")));
+		request.setEntity(new StringEntity(getValidatedBundleJSON("personalization.json", parameters), ContentType.APPLICATION_JSON));
 		TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request);
 		assertEquals("Invalid response code", 200, response.getStatusCode());
 		refreshPersistence();
@@ -515,7 +534,7 @@ public class ContextServletIT extends BaseIT {
         Map<String,String> parameters = new HashMap<>();
         parameters.put("storeInSession", "false");
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-controlgroup.json", parameters), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-controlgroup.json", parameters), ContentType.APPLICATION_JSON));
         TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request);
         assertEquals("Invalid response code", 200, response.getStatusCode());
         refreshPersistence();
@@ -539,7 +558,7 @@ public class ContextServletIT extends BaseIT {
         // now let's test with session storage
         parameters.put("storeInSession", "true");
         request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-controlgroup.json", parameters), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("personalization-controlgroup.json", parameters), ContentType.APPLICATION_JSON));
         response = TestUtils.executeContextJSONRequest(request);
         assertEquals("Invalid response code", 200, response.getStatusCode());
         refreshPersistence();
@@ -603,7 +622,7 @@ public class ContextServletIT extends BaseIT {
         // first let's make sure everything works without the requireScoring parameter
         parameters = new HashMap<>();
         HttpPost request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(getValidatedBundleJSON("withoutRequireScores.json", parameters), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("withoutRequireScores.json", parameters), ContentType.APPLICATION_JSON));
         TestUtils.RequestResponse response = TestUtils.executeContextJSONRequest(request);
         assertEquals("Invalid response code", 200, response.getStatusCode());
         refreshPersistence();
@@ -616,7 +635,7 @@ public class ContextServletIT extends BaseIT {
         // now let's test adding it.
         parameters = new HashMap<>();
         request = new HttpPost(URL + CONTEXT_URL);
-        request.setEntity(new StringEntity(getValidatedBundleJSON("withRequireScores.json", parameters), ContentType.create("application/json")));
+        request.setEntity(new StringEntity(getValidatedBundleJSON("withRequireScores.json", parameters), ContentType.APPLICATION_JSON));
         response = TestUtils.executeContextJSONRequest(request);
         assertEquals("Invalid response code", 200, response.getStatusCode());
         refreshPersistence();
diff --git a/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java b/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
new file mode 100644
index 0000000..ce4e4c8
--- /dev/null
+++ b/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
@@ -0,0 +1,131 @@
+/*
+ * 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.unomi.itests;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.unomi.api.schema.UnomiJSONSchema;
+import org.apache.unomi.api.services.SchemaRegistry;
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerSuite;
+import org.ops4j.pax.exam.util.Filter;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.util.Base64;
+import java.util.List;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Class to tests the JSON schema features
+ */
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerSuite.class)
+public class JSONSchemaIT extends BaseIT {
+    private final static String JSONSCHEMA_URL = "/cxs/jsonSchema";
+    private static final int DEFAULT_TRYING_TIMEOUT = 2000;
+    private static final int DEFAULT_TRYING_TRIES = 30;
+
+    @Inject
+    @Filter(timeout = 600000)
+    protected SchemaRegistry schemaRegistry;
+
+    @Inject
+    @Filter(timeout = 600000)
+    protected PersistenceService persistenceService;
+
+    @Before
+    public void setUp() throws InterruptedException {
+        keepTrying("Couldn't find json schema endpoint", () -> get(JSONSCHEMA_URL, List.class), Objects::nonNull, DEFAULT_TRYING_TIMEOUT,
+                DEFAULT_TRYING_TRIES);
+    }
+
+    @After
+    public void tearDown() {
+        schemaRegistry.deleteSchema("https://unomi.apache.org/schemas/json/events/test-event-type/1-0-0");
+    }
+
+    @Test
+    public void testGetJsonSchemasMetadatas() throws InterruptedException {
+        List jsonSchemas = get(JSONSCHEMA_URL, List.class);
+        assertTrue("JSON schema list should be empty", jsonSchemas.isEmpty());
+
+        post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+
+        refreshPersistence();
+        jsonSchemas = keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+        assertFalse("JSON schema list should not be empty", jsonSchemas.isEmpty());
+        assertEquals("JSON schema list should not be empty", 1, jsonSchemas.size());
+    }
+
+    @Test
+    public void testSaveNewValidJSONSchema() throws InterruptedException {
+
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(UnomiJSONSchema.class).isEmpty());
+
+        CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+
+        assertEquals("Invalid response code", 200, response.getStatusLine().getStatusCode());
+        refreshPersistence();
+        List jsonSchemas = keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+        assertFalse("JSON schema list should not be empty", jsonSchemas.isEmpty());
+    }
+
+    @Test
+    public void testDeleteJSONSchema() throws InterruptedException {
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(UnomiJSONSchema.class).isEmpty());
+
+        post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+
+        refreshPersistence();
+        keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(), DEFAULT_TRYING_TIMEOUT,
+                DEFAULT_TRYING_TRIES);
+
+        String encodedString = Base64.getEncoder()
+                .encodeToString("https://unomi.apache.org/schemas/json/events/test-event-type/1-0-0".getBytes());
+        CloseableHttpResponse response = delete(JSONSCHEMA_URL + "/" + encodedString);
+        assertEquals("Invalid response code", 204, response.getStatusLine().getStatusCode());
+
+        refreshPersistence();
+        List jsonSchemas = keepTrying("wait for empty list of schemas", () -> get(JSONSCHEMA_URL, List.class), List::isEmpty,
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+
+        assertTrue("JSON schema list should be empty", jsonSchemas.isEmpty());
+    }
+
+    @Test
+    public void testSaveNewInvalidJSONSchema() throws IOException {
+        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(UnomiJSONSchema.class).isEmpty());
+        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-invalid.json", ContentType.TEXT_PLAIN)) {
+            assertEquals("Save should have failed", 500, response.getStatusLine().getStatusCode());
+        }
+    }
+}
diff --git a/itests/src/test/java/org/apache/unomi/itests/TestUtils.java b/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
index 7416d46..0b4073e 100644
--- a/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
+++ b/itests/src/test/java/org/apache/unomi/itests/TestUtils.java
@@ -18,13 +18,22 @@
 package org.apache.unomi.itests;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.util.EntityUtils;
 import org.apache.unomi.api.ContextResponse;
 import org.apache.unomi.api.Event;
diff --git a/itests/src/test/resources/schemas/events/test-event-type.json b/itests/src/test/resources/schemas/events/test-event-type.json
index 46b56e2..99ba3f8 100644
--- a/itests/src/test/resources/schemas/events/test-event-type.json
+++ b/itests/src/test/resources/schemas/events/test-event-type.json
@@ -5,9 +5,10 @@
     "vendor":"org.apache.unomi",
     "name":"events/test-event-type",
     "format":"jsonschema",
+    "target":"events",
     "version":"1-0-0"
   },
   "title": "TestEvent",
   "type": "object",
   "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }]
-}
\ No newline at end of file
+}
diff --git a/itests/src/test/resources/schemas/events/test-event-type.json b/itests/src/test/resources/schemas/events/test-invalid.json
similarity index 70%
copy from itests/src/test/resources/schemas/events/test-event-type.json
copy to itests/src/test/resources/schemas/events/test-invalid.json
index 46b56e2..794a77c 100644
--- a/itests/src/test/resources/schemas/events/test-event-type.json
+++ b/itests/src/test/resources/schemas/events/test-invalid.json
@@ -1,13 +1,13 @@
 {
-  "$id": "https://unomi.apache.org/schemas/json/events/test-event-type/1-0-0",
   "$schema": "https://json-schema.org/draft/2019-09/schema",
   "self":{
     "vendor":"org.apache.unomi",
-    "name":"events/test-event-type",
+    "name":"events/invalid",
     "format":"jsonschema",
     "version":"1-0-0"
   },
+  "invalidEntry": "An invalid entry",
   "title": "TestEvent",
   "type": "object",
   "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }]
-}
\ No newline at end of file
+}
diff --git a/package/src/main/resources/etc/custom.system.properties b/package/src/main/resources/etc/custom.system.properties
index fbb3a4d..3d9d2cf 100644
--- a/package/src/main/resources/etc/custom.system.properties
+++ b/package/src/main/resources/etc/custom.system.properties
@@ -183,6 +183,8 @@ org.apache.unomi.rules.statistics.refresh.interval=${env:UNOMI_RULES_STATISTICS_
 org.apache.unomi.rules.optimizationActivated=${env:UNOMI_RULES_OPTIMIZATION_ACTIVATED:-true}
 # The number of threads to compose the pool size of the scheduler.
 org.apache.unomi.scheduler.thread.poolSize=${env:UNOMI_SCHEDULER_THREAD_POOL_SIZE:-5}
+# When performing json schema updates,
+services.json.schema.refresh.interval=${env:UNOMI_JSON_SCHEMA_REFRESH_INTERVAL:-1000}
 
 #######################################################################################################################
 ## Third Party server settings                                                                                       ##
diff --git a/persistence-elasticsearch/core/src/main/resources/META-INF/cxs/mappings/jsonschema.json b/persistence-elasticsearch/core/src/main/resources/META-INF/cxs/mappings/jsonschema.json
new file mode 100644
index 0000000..896e1be
--- /dev/null
+++ b/persistence-elasticsearch/core/src/main/resources/META-INF/cxs/mappings/jsonschema.json
@@ -0,0 +1,25 @@
+{
+  "dynamic_templates": [
+    {
+      "all": {
+        "match": "*",
+        "match_mapping_type": "string",
+        "mapping": {
+          "type": "text",
+          "analyzer": "folding",
+          "fields": {
+            "keyword": {
+              "type": "keyword",
+              "ignore_above": 256
+            }
+          }
+        }
+      }
+    }
+  ],
+  "properties": {
+    "schema": {
+      "type": "text"
+    }
+  }
+}
diff --git a/pom.xml b/pom.xml
index 4f3e41e..0874955 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,6 +73,7 @@
         <version.pax.exam>4.13.1</version.pax.exam>
         <elasticsearch.version>7.4.2</elasticsearch.version>
         <groovy.version>3.0.3</groovy.version>
+        <networknt.version>1.0.49</networknt.version>
         <bean.validation.version>1.1.0.Final</bean.validation.version>
         <hibernate.validator.version>5.4.3.Final</hibernate.validator.version>
 
diff --git a/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaEndPoint.java b/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaEndPoint.java
new file mode 100644
index 0000000..96992d2
--- /dev/null
+++ b/rest/src/main/java/org/apache/unomi/rest/endpoints/JsonSchemaEndPoint.java
@@ -0,0 +1,109 @@
+/*
+ * 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.unomi.rest.endpoints;
+
+import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.services.SchemaRegistry;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.Base64;
+import java.util.List;
+
+@WebService
+@Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+@CrossOriginResourceSharing(allowAllOrigins = true, allowCredentials = true)
+@Path("/jsonSchema")
+@Component(service = JsonSchemaEndPoint.class, property = "osgi.jaxrs.resource=true")
+public class JsonSchemaEndPoint {
+
+    private static final Logger logger = LoggerFactory.getLogger(JsonSchemaEndPoint.class.getName());
+
+    @Reference
+    private SchemaRegistry schemaRegistry;
+
+    public JsonSchemaEndPoint() {
+        logger.info("Initializing JSON schema service endpoint...");
+    }
+
+    @WebMethod(exclude = true)
+    public void setSchemaRegistry(SchemaRegistry schemaRegistry) {
+        this.schemaRegistry = schemaRegistry;
+    }
+
+    /**
+     * Retrieves the 50 first json schema metadatas by default.
+     *
+     * @param offset zero or a positive integer specifying the position of the first element in the total ordered collection of matching elements
+     * @param size   a positive integer specifying how many matching elements should be retrieved or {@code -1} if all of them should be retrieved
+     * @param sortBy an optional ({@code null} if no sorting is required) String of comma ({@code ,}) separated property names on which ordering should be performed, ordering
+     *               elements according to the property order in the
+     *               String, considering each in turn and moving on to the next one in case of equality of all preceding ones. Each property name is optionally followed by
+     *               a column ({@code :}) and an order specifier: {@code asc} or {@code desc}.
+     * @return a List of the 50 first json schema metadata
+     */
+    @GET
+    @Path("/")
+    public List<Metadata> getJsonSchemaMetadatas(@QueryParam("offset") @DefaultValue("0") int offset,
+            @QueryParam("size") @DefaultValue("50") int size, @QueryParam("sort") String sortBy) {
+        return schemaRegistry.getJsonSchemaMetadatas(offset, size, sortBy).getList();
+    }
+
+    /**
+     * Save a JSON schema
+     *
+     * @param jsonSchema the schema as string to save
+     * @return Response of the API call
+     */
+    @POST
+    @Path("/")
+    @Consumes(MediaType.TEXT_PLAIN)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response save(String jsonSchema) {
+        schemaRegistry.saveSchema(jsonSchema);
+        return Response.ok().build();
+    }
+
+    /**
+     * Deletes a JSON schema.
+     * The id is a Base64 id as the id have is basically an URL
+     *
+     * @param base64JsonSchemaId the identifier of the JSON schema that we want to delete
+     */
+    @DELETE
+    @Path("/{base64JsonSchemaId}")
+    public void remove(@PathParam("base64JsonSchemaId") String base64JsonSchemaId) {
+        schemaRegistry.deleteSchema(new String(Base64.getDecoder().decode(base64JsonSchemaId)));
+    }
+}
diff --git a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
index be5ac44..ddaefea 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
@@ -115,7 +115,6 @@ public class EventServiceImpl implements EventService {
     public void setSchemaRegistry(SchemaRegistry schemaRegistry) {
         this.schemaRegistry = schemaRegistry;
     }
-
     public void setPersistenceService(PersistenceService persistenceService) {
         this.persistenceService = persistenceService;
     }
@@ -140,7 +139,7 @@ public class EventServiceImpl implements EventService {
     }
 
     public boolean isEventValid(Event event) {
-        return this.schemaRegistry.isValid(event, "https://unomi.apache.org/schemas/json/events/" + event.getEventType() + "/1-0-0");
+        return schemaRegistry.isValid(event, "https://unomi.apache.org/schemas/json/events/" + event.getEventType() + "/1-0-0");
     }
 
     public String authenticateThirdPartyServer(String key, String ip) {
diff --git a/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaRegistryImpl.java b/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaRegistryImpl.java
index eb137e0..ae4e3db 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaRegistryImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaRegistryImpl.java
@@ -20,101 +20,106 @@ package org.apache.unomi.services.impl.schemas;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.networknt.schema.*;
+import com.networknt.schema.JsonMetaSchema;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.NonValidationKeyword;
+import com.networknt.schema.SpecVersion;
+import com.networknt.schema.ValidationMessage;
 import com.networknt.schema.uri.URIFetcher;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.schema.UnomiJSONSchema;
 import org.apache.unomi.api.schema.json.JSONSchema;
 import org.apache.unomi.api.schema.json.JSONTypeFactory;
 import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.api.services.SchedulerService;
 import org.apache.unomi.api.services.SchemaRegistry;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
-import org.osgi.framework.Bundle;
+import org.apache.unomi.persistence.spi.PersistenceService;
 import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.SynchronousBundleListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
 import java.io.InputStream;
-import java.net.URL;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TimerTask;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-public class SchemaRegistryImpl implements SchemaRegistry, SynchronousBundleListener {
+public class SchemaRegistryImpl implements SchemaRegistry {
 
     private static final String URI = "https://json-schema.org/draft/2019-09/schema";
 
     private static final Logger logger = LoggerFactory.getLogger(SchemaRegistryImpl.class.getName());
 
-    private final Map<Long, List<JSONSchema>> schemaTypesByBundle = new HashMap<>();
-    private final Map<String, JSONSchema> schemaTypesById = new HashMap<>();
+    private final Map<String, JSONSchema> predefinedUnomiJSONSchemaById = new HashMap<>();
 
-    private final Map<String, JsonSchema> jsonSchemasById = new LinkedHashMap<>();
-
-    private final Map<String, Long> bundleIdBySchemaId = new HashMap<>();
+    private Map<String, JSONSchema> schemasById = new HashMap<>();
 
     private BundleContext bundleContext;
 
     private ProfileService profileService;
 
-    private JsonSchemaFactory jsonSchemaFactory;
-
-    ObjectMapper objectMapper = new ObjectMapper();
+    private PersistenceService persistenceService;
 
-    Pattern uriPathPattern = Pattern.compile("/schemas/json(.*)/\\d-\\d-\\d");
+    private SchedulerService schedulerService;
 
-    public void bundleChanged(BundleEvent event) {
-        switch (event.getType()) {
-            case BundleEvent.STARTED:
-                processBundleStartup(event.getBundle().getBundleContext());
-                break;
-            case BundleEvent.STOPPING:
-                processBundleStop(event.getBundle().getBundleContext());
-                break;
-        }
-    }
-
-    public void init() {
+    private JsonSchemaFactory jsonSchemaFactory;
 
-        JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(URI, JsonMetaSchema.getV201909())
-                .addKeyword(new UnomiPropertyTypeKeyword(profileService, this))
-                .addKeyword(new NonValidationKeyword("self"))
-                .build();
-        jsonSchemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
-                .addMetaSchema(jsonMetaSchema)
-                .defaultMetaSchemaURI(URI)
-                .uriFetcher(getBundleUriFetcher(bundleContext), "https", "http").build();
+    ObjectMapper objectMapper = new ObjectMapper();
 
-        processBundleStartup(bundleContext);
+    private ScheduledFuture<?> scheduledFuture;
 
-        // process already started bundles
-        for (Bundle bundle : bundleContext.getBundles()) {
-            if (bundle.getBundleContext() != null && bundle.getBundleId() != bundleContext.getBundle().getBundleId()) {
-                processBundleStartup(bundle.getBundleContext());
-            }
-        }
+    private Integer jsonSchemaRefreshInterval = 1000;
 
-        bundleContext.addBundleListener(this);
-        logger.info("Schema registry initialized.");
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
     }
 
-    public void destroy() {
-        bundleContext.removeBundleListener(this);
-        logger.info("Schema registry shutdown.");
+    public void setSchedulerService(SchedulerService schedulerService) {
+        this.schedulerService = schedulerService;
     }
 
-    public void setBundleContext(BundleContext bundleContext) {
-        this.bundleContext = bundleContext;
+    public void setJsonSchemaRefreshInterval(Integer jsonSchemaRefreshInterval) {
+        this.jsonSchemaRefreshInterval = jsonSchemaRefreshInterval;
     }
 
-    public void setProfileService(ProfileService profileService) {
-        this.profileService = profileService;
+    @Override
+    public PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String sortBy) {
+        PartialList<UnomiJSONSchema> items = persistenceService.getAllItems(UnomiJSONSchema.class, offset, size, sortBy);
+        List<Metadata> details = new LinkedList<>();
+        for (UnomiJSONSchema definition : items.getList()) {
+            details.add(definition.getMetadata());
+        }
+        return new PartialList<>(details, items.getOffset(), items.getPageSize(), items.getTotalSize(), items.getTotalSizeRelation());
     }
 
+    @Override
     public boolean isValid(Object object, String schemaId) {
-        JsonSchema jsonSchema = jsonSchemasById.get(schemaId);
+        String schemaAsString;
+        JsonSchema jsonSchema = null;
+        try {
+            JSONSchema validationSchema = schemasById.get(schemaId);
+            if (validationSchema != null){
+                schemaAsString = objectMapper.writeValueAsString(schemasById.get(schemaId).getSchemaTree());
+                jsonSchema = jsonSchemaFactory.getSchema(schemaAsString);
+            } else {
+                logger.warn("No schema found for {}", schemaId);
+            }
+        } catch (JsonProcessingException e) {
+            logger.error("Failed to process json schema", e);
+        }
+
         if (jsonSchema != null) {
             JsonNode jsonNode = CustomObjectMapper.getObjectMapper().convertValue(object, JsonNode.class);
             Set<ValidationMessage> validationMessages = jsonSchema.validate(jsonNode);
@@ -130,128 +135,142 @@ public class SchemaRegistryImpl implements SchemaRegistry, SynchronousBundleList
     }
 
     @Override
-    public List<JSONSchema> getTargetSchemas(String target) {
-        return schemaTypesById.values().stream().filter(jsonSchema -> jsonSchema.getTarget() != null && jsonSchema.getTarget().equals(target)).collect(Collectors.toList());
+    public List<JSONSchema> getSchemasByTarget(String target) {
+        return schemasById.values().stream().filter(jsonSchema -> jsonSchema.getTarget() != null && jsonSchema.getTarget().equals(target))
+                .collect(Collectors.toList());
+
     }
 
     @Override
-    public JSONSchema getSchema(String schemaId) {
-        return schemaTypesById.get(schemaId);
+    public void saveSchema(String schema) {
+        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schema);
+        if (predefinedUnomiJSONSchemaById.get(jsonSchema.getSchemaNode().get("$id").asText()) == null) {
+            persistenceService.save(buildUnomiJsonSchema(schema));
+            JSONSchema localSchema = buildJSONSchema(jsonSchema);
+            schemasById.put(jsonSchema.getSchemaNode().get("$id").asText(), localSchema);
+        } else {
+            logger.error("Can not store a JSON Schema which have the id of a schema preovided by Unomi");
+        }
     }
 
-    private void loadPredefinedSchemas(BundleContext bundleContext) {
-        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries("META-INF/cxs/schemas", "*.json", true);
-        if (predefinedSchemas == null) {
-            return;
-        }
+    @Override
+    public void saveSchema(InputStream schemaStream) throws IOException {
+        saveSchema(IOUtils.toString(schemaStream));
+    }
 
-        List<JSONSchema> jsonSchemas = this.schemaTypesByBundle.get(bundleContext.getBundle().getBundleId());
-
-        while (predefinedSchemas.hasMoreElements()) {
-            URL predefinedSchemaURL = predefinedSchemas.nextElement();
-            logger.debug("Found predefined JSON schema at " + predefinedSchemaURL + ", loading... ");
-
-            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
-                JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schemaInputStream);
-                String schemaTarget = null;
-                String[] splitPath = predefinedSchemaURL.getPath().split("/");
-                if (splitPath.length > 5) {
-                    String target = splitPath[4];
-                    if (StringUtils.isNotBlank(target)) {
-                        schemaTarget = target;
-                    }
-                }
-                registerSchema(bundleContext.getBundle().getBundleId(), jsonSchemas, schemaTarget, jsonSchema);
-            } catch (Exception e) {
-                logger.error("Error while loading schema definition " + predefinedSchemaURL, e);
-            }
-        }
+    @Override
+    public void loadPredefinedSchema(InputStream schemaStream) {
+        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schemaStream);
+        JSONSchema localJsonSchema = buildJSONSchema(jsonSchema);
 
+        predefinedUnomiJSONSchemaById.put(jsonSchema.getSchemaNode().get("$id").asText(), localJsonSchema);
+        schemasById.put(jsonSchema.getSchemaNode().get("$id").asText(), localJsonSchema);
     }
 
-    public String registerSchema(String target, InputStream jsonSchemaInputStream) {
-        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(jsonSchemaInputStream);
-        try {
-            return registerSchema(null, null, target, jsonSchema);
-        } catch (JsonProcessingException e) {
-            logger.error("Error registering JSON schema", e);
-            return null;
-        }
+    @Override
+    public boolean deleteSchema(String schemaId) {
+        schemasById.remove(schemaId);
+        return persistenceService.remove(schemaId, UnomiJSONSchema.class);
     }
 
-    public boolean unregisterSchema(String target, String schemaId) {
-        jsonSchemasById.remove(schemaId);
-        schemaTypesById.remove(schemaId);
-        return true;
+    @Override
+    public boolean deleteSchema(InputStream schemaStream) {
+        JsonNode schemaNode = jsonSchemaFactory.getSchema(schemaStream).getSchemaNode();
+        return deleteSchema(schemaNode.get("$id").asText());
     }
 
-    private String registerSchema(Long bundleId, List<JSONSchema> jsonSchemas, String target, JsonSchema jsonSchema) throws JsonProcessingException {
-        String schemaId = jsonSchema.getSchemaNode().get("$id").asText();
-        jsonSchemasById.put(schemaId, jsonSchema);
-        if (bundleContext != null) {
-            bundleIdBySchemaId.put(schemaId, bundleId);
-        }
-        Map<String, Object> schemaTree = (Map<String, Object>) objectMapper.treeToValue(jsonSchema.getSchemaNode(), Map.class);
-        JSONSchema unomiJsonSchema = new JSONSchema(schemaTree, new JSONTypeFactory(this), this);
-        if (bundleId != null) {
-            unomiJsonSchema.setPluginId(bundleId);
-        }
-        unomiJsonSchema.setSchemaId(schemaId);
-        unomiJsonSchema.setTarget(target);
-        if (jsonSchemas != null) {
-            jsonSchemas.add(unomiJsonSchema);
+    @Override
+    public JSONSchema getSchema(String schemaId) {
+        return schemasById.get(schemaId);
+    }
+
+    private JSONSchema buildJSONSchema(JsonSchema jsonSchema) {
+        return Optional.of(jsonSchema).map(jsonSchemaToProcess -> {
+            try {
+                return (Map<String, Object>) objectMapper.treeToValue(jsonSchemaToProcess.getSchemaNode(), Map.class);
+            } catch (JsonProcessingException e) {
+                logger.error("Failed to process Json object, e");
+            }
+            return Collections.<String, Object>emptyMap();
+        }).map(jsonSchemaToProcess -> {
+            JSONSchema schema = new JSONSchema(jsonSchemaToProcess, new JSONTypeFactory(this));
+            schema.setPluginId(bundleContext.getBundle().getBundleId());
+            return schema;
+        }).get();
+    }
+
+    private UnomiJSONSchema buildUnomiJsonSchema(String schema) {
+        JsonNode schemaNode = jsonSchemaFactory.getSchema(schema).getSchemaNode();
+        return new UnomiJSONSchema(schemaNode.get("$id").asText(), schema, schemaNode.at("/self/target").asText());
+    }
+
+    public JsonSchema getJsonSchema(String schemaId) {
+        String schemaAsString = null;
+        try {
+            schemaAsString = objectMapper.writeValueAsString(schemasById.get(schemaId).getSchemaTree());
+        } catch (JsonProcessingException e) {
+            logger.error("Failed to process json schema", e);
         }
-        schemaTypesById.put(schemaId, unomiJsonSchema);
-        return schemaId;
+        return jsonSchemaFactory.getSchema(schemaAsString);
     }
 
-    private URIFetcher getBundleUriFetcher(BundleContext bundleContext) {
+    private URIFetcher getUriFetcher() {
         return uri -> {
             logger.debug("Fetching schema {}", uri);
-            Long bundleId = bundleIdBySchemaId.get(uri.toString());
-            if (bundleId == null) {
-                logger.error("Couldn't find bundle for schema {}", uri);
-                return null;
-            }
-            Matcher uriPathMatcher = uriPathPattern.matcher(uri.getPath());
-            String uriPath = uri.getPath();
-            if (uriPathMatcher.matches()) {
-                uriPath = uriPathMatcher.group(1) + ".json";
+            String schemaAsString = null;
+            try {
+                schemaAsString = objectMapper.writeValueAsString(schemasById.get(uri.toString()).getSchemaTree());
+            } catch (JsonProcessingException e) {
+                logger.error("Failed to process json schema", e);
             }
-            URL schemaURL = bundleContext.getBundle(bundleId).getResource("META-INF/cxs/schemas" + uriPath);
-            if (schemaURL != null) {
-                return schemaURL.openStream();
-            } else {
-                logger.error("Couldn't find resource {} in bundle {}", "META-INF/cxs/schemas" + uriPath, bundleId);
+            JsonSchema schema = jsonSchemaFactory.getSchema(schemaAsString);
+            if (schema == null) {
+                logger.error("Couldn't find schema {}", uri);
                 return null;
             }
+            return IOUtils.toInputStream(schema.getSchemaNode().asText());
         };
     }
 
-    private void processBundleStartup(BundleContext bundleContext) {
-        if (bundleContext == null) {
-            return;
-        }
-        schemaTypesByBundle.put(bundleContext.getBundle().getBundleId(), new ArrayList<>());
-        loadPredefinedSchemas(bundleContext);
+    private void refreshJSONSchemas() {
+        schemasById = new HashMap<>();
+        schemasById.putAll(predefinedUnomiJSONSchemaById);
+        persistenceService.getAllItems(UnomiJSONSchema.class).forEach(
+                jsonSchema -> schemasById.put(jsonSchema.getId(), buildJSONSchema(jsonSchemaFactory.getSchema(jsonSchema.getSchema()))));
     }
 
-    private void processBundleStop(BundleContext bundleContext) {
-        if (bundleContext == null) {
-            return;
-        }
-        List<JSONSchema> JSONSchemas = schemaTypesByBundle.remove(bundleContext.getBundle().getBundleId());
-        if (JSONSchemas != null) {
-            for (JSONSchema JSONSchema : JSONSchemas) {
-                jsonSchemasById.remove(JSONSchema.getSchemaId());
-                bundleIdBySchemaId.remove(JSONSchema.getSchemaId());
-                schemaTypesById.remove(JSONSchema.getSchemaId());
+    private void initializeTimers() {
+        TimerTask task = new TimerTask() {
+            @Override
+            public void run() {
+                refreshJSONSchemas();
             }
-        }
+        };
+        scheduledFuture = schedulerService.getScheduleExecutorService()
+                .scheduleWithFixedDelay(task, 0, jsonSchemaRefreshInterval, TimeUnit.MILLISECONDS);
+    }
+
+    public void init() {
+
+        JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(URI, JsonMetaSchema.getV201909())
+                .addKeyword(new UnomiPropertyTypeKeyword(profileService, this)).addKeyword(new NonValidationKeyword("self")).build();
+        jsonSchemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
+                .addMetaSchema(jsonMetaSchema).defaultMetaSchemaURI(URI).uriFetcher(getUriFetcher(), "https", "http").build();
+
+        initializeTimers();
+        logger.info("Schema registry initialized.");
+    }
+
+    public void destroy() {
+        scheduledFuture.cancel(true);
+        logger.info("Schema registry shutdown.");
     }
 
-    protected JsonSchema getJsonSchema(String schemaId) {
-        return jsonSchemasById.get(schemaId);
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
     }
 
+    public void setProfileService(ProfileService profileService) {
+        this.profileService = profileService;
+    }
 }
diff --git a/services/src/main/java/org/apache/unomi/services/impl/schemas/UnomiPropertyTypeKeyword.java b/services/src/main/java/org/apache/unomi/services/impl/schemas/UnomiPropertyTypeKeyword.java
index 2375e7b..2f02d51 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/schemas/UnomiPropertyTypeKeyword.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/schemas/UnomiPropertyTypeKeyword.java
@@ -17,19 +17,29 @@
 package org.apache.unomi.services.impl.schemas;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.networknt.schema.*;
+import com.networknt.schema.AbstractJsonValidator;
+import com.networknt.schema.AbstractKeyword;
+import com.networknt.schema.CustomErrorMessageType;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaException;
+import com.networknt.schema.JsonValidator;
+import com.networknt.schema.ValidationContext;
+import com.networknt.schema.ValidationMessage;
 import org.apache.unomi.api.PropertyType;
 import org.apache.unomi.api.services.ProfileService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.text.MessageFormat;
-import java.util.*;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
 
 class UnomiPropertyTypeKeyword extends AbstractKeyword {
 
-    private static final Logger logger = LoggerFactory.getLogger(UnomiPropertyTypeKeyword.class);
-
     private final ProfileService profileService;
     private final SchemaRegistryImpl schemaRegistry;
 
@@ -42,7 +52,8 @@ class UnomiPropertyTypeKeyword extends AbstractKeyword {
         ProfileService profileService;
         SchemaRegistryImpl schemaRegistry;
 
-        public UnomiPropertyTypeJsonValidator(String keyword, String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, ProfileService profileService, SchemaRegistryImpl schemaRegistry) {
+        public UnomiPropertyTypeJsonValidator(String keyword, String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
+                ValidationContext validationContext, ProfileService profileService, SchemaRegistryImpl schemaRegistry) {
             super(keyword);
             this.schemaPath = schemaPath;
             this.schemaNode = schemaNode;
@@ -60,13 +71,17 @@ class UnomiPropertyTypeKeyword extends AbstractKeyword {
                 String fieldName = fieldNames.next();
                 PropertyType propertyType = getPropertyType(fieldName);
                 if (propertyType == null) {
-                    validationMessages.add(buildValidationMessage(CustomErrorMessageType.of("property-not-found", new MessageFormat("{0} : Couldn''t find property type with id={1}")), at, fieldName));
+                    validationMessages.add(buildValidationMessage(CustomErrorMessageType
+                            .of("property-not-found", new MessageFormat("{0} : Couldn''t find property type with id={1}")), at, fieldName));
                 } else {
                     // @todo further validation, if it can be used in this context (event, profile, session)
                     String valueTypeId = propertyType.getValueTypeId();
-                    JsonSchema jsonSchema = schemaRegistry.getJsonSchema("https://unomi.apache.org/schemas/json/values/" + valueTypeId + ".json");
+                    JsonSchema jsonSchema = schemaRegistry
+                            .getJsonSchema("https://unomi.apache.org/schemas/json/values/" + valueTypeId + ".json");
                     if (jsonSchema == null) {
-                        validationMessages.add(buildValidationMessage(CustomErrorMessageType.of("value-schema-not-found", new MessageFormat("{0} : Couldn''t find schema type with id={1}")), at, "https://unomi.apache.org/schemas/json/values/" + valueTypeId + ".json"));
+                        validationMessages.add(buildValidationMessage(CustomErrorMessageType
+                                        .of("value-schema-not-found", new MessageFormat("{0} : Couldn''t find schema type with id={1}")), at,
+                                "https://unomi.apache.org/schemas/json/values/" + valueTypeId + ".json"));
                     } else {
                         Set<ValidationMessage> propertyValidationMessages = jsonSchema.validate(node.get(fieldName));
                         if (propertyValidationMessages != null) {
@@ -104,7 +119,9 @@ class UnomiPropertyTypeKeyword extends AbstractKeyword {
     }
 
     @Override
-    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
-        return new UnomiPropertyTypeJsonValidator(this.getValue(), schemaPath, schemaNode, parentSchema, validationContext, profileService, schemaRegistry);
+    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext)
+            throws JsonSchemaException, Exception {
+        return new UnomiPropertyTypeJsonValidator(this.getValue(), schemaPath, schemaNode, parentSchema, validationContext, profileService,
+                schemaRegistry);
     }
 }
diff --git a/services/src/main/java/org/apache/unomi/services/listener/JsonSchemaListener.java b/services/src/main/java/org/apache/unomi/services/listener/JsonSchemaListener.java
new file mode 100644
index 0000000..b3b2fd2
--- /dev/null
+++ b/services/src/main/java/org/apache/unomi/services/listener/JsonSchemaListener.java
@@ -0,0 +1,170 @@
+/*
+ * 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.unomi.services.listener;
+
+import org.apache.unomi.api.schema.UnomiJSONSchema;
+import org.apache.unomi.api.services.SchemaRegistry;
+import org.apache.unomi.persistence.spi.PersistenceService;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * An implementation of a BundleListener for the JSON schema.
+ * It will load the pre-defined schema files in the folder META-INF/cxs/schemas.
+ * The script will be stored in the ES index jsonSchemas
+ */
+public class JsonSchemaListener implements SynchronousBundleListener {
+
+    private static final Logger logger = LoggerFactory.getLogger(JsonSchemaListener.class.getName());
+    public static final String ENTRIES_LOCATION = "META-INF/cxs/schemas";
+
+    private PersistenceService persistenceService;
+
+    private SchemaRegistry schemaRegistry;
+
+    private BundleContext bundleContext;
+
+    public void setPersistenceService(PersistenceService persistenceService) {
+        this.persistenceService = persistenceService;
+    }
+
+    public void setSchemaRegistry(SchemaRegistry schemaRegistry) {
+        this.schemaRegistry = schemaRegistry;
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void postConstruct() {
+        logger.info("JSON schema listener initializing...");
+        logger.debug("postConstruct {}", bundleContext.getBundle());
+        createIndex();
+
+        loadPredefinedSchemas(bundleContext);
+
+        for (Bundle bundle : bundleContext.getBundles()) {
+            if (bundle.getBundleContext() != null && bundle.getBundleId() != bundleContext.getBundle().getBundleId()) {
+                saveSchemas(bundle.getBundleContext());
+            }
+        }
+
+        bundleContext.addBundleListener(this);
+        logger.info("JSON schema listener initialized.");
+    }
+
+    public void preDestroy() {
+        bundleContext.removeBundleListener(this);
+        logger.info("JSON schema listener shutdown.");
+    }
+
+    private void processBundleStartup(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        saveSchemas(bundleContext);
+    }
+
+    private void processBundleStop(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        unloadSchemas(bundleContext);
+    }
+
+    public void bundleChanged(BundleEvent event) {
+        switch (event.getType()) {
+            case BundleEvent.STARTED:
+                processBundleStartup(event.getBundle().getBundleContext());
+                break;
+            case BundleEvent.STOPPING:
+                if (!event.getBundle().getSymbolicName().equals(bundleContext.getBundle().getSymbolicName())) {
+                    processBundleStop(event.getBundle().getBundleContext());
+                }
+                break;
+        }
+    }
+
+    public void createIndex() {
+        if (persistenceService.createIndex(UnomiJSONSchema.ITEM_TYPE)) {
+            logger.info("{} index created", UnomiJSONSchema.ITEM_TYPE);
+        } else {
+            logger.info("{} index already exists", UnomiJSONSchema.ITEM_TYPE);
+        }
+    }
+
+    private void saveSchemas(BundleContext bundleContext) {
+        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
+        if (predefinedSchemas == null) {
+            return;
+        }
+
+        while (predefinedSchemas.hasMoreElements()) {
+            URL predefinedSchemaURL = predefinedSchemas.nextElement();
+            logger.debug("Found JSON schema at {}, loading... ", predefinedSchemaURL);
+
+            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
+                schemaRegistry.saveSchema(schemaInputStream);
+            } catch (Exception e) {
+                logger.error("Error while loading schema definition {}", predefinedSchemaURL, e);
+            }
+        }
+    }
+
+    private void loadPredefinedSchemas(BundleContext bundleContext) {
+        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
+        if (predefinedSchemas == null) {
+            return;
+        }
+
+        while (predefinedSchemas.hasMoreElements()) {
+            URL predefinedSchemaURL = predefinedSchemas.nextElement();
+            logger.debug("Found predefined JSON schema at {}, loading... ", predefinedSchemaURL);
+            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
+                schemaRegistry.loadPredefinedSchema(schemaInputStream);
+            } catch (Exception e) {
+                logger.error("Error while loading schema definition {}", predefinedSchemaURL, e);
+            }
+        }
+    }
+
+    private void unloadSchemas(BundleContext bundleContext) {
+        Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
+        if (predefinedSchemas == null) {
+            return;
+        }
+
+        while (predefinedSchemas.hasMoreElements()) {
+            URL predefinedSchemaURL = predefinedSchemas.nextElement();
+            logger.debug("Found predefined JSON schema at {}, loading... ", predefinedSchemaURL);
+
+            try (InputStream schemaInputStream = predefinedSchemaURL.openStream()) {
+                schemaRegistry.deleteSchema(schemaInputStream);
+            } catch (Exception e) {
+                logger.error("Error while removing schema at {}", predefinedSchemaURL, e);
+            }
+        }
+    }
+}
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index a8fa903..4be07c9 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -45,6 +45,7 @@
             <cm:property name="events.shouldBeCheckedEventSourceId" value="false"/>
             <cm:property name="rules.optimizationActivated" value="true"/>
             <cm:property name="schedules.thread.poolSize" value="5"/>
+            <cm:property name="json.schema.refresh.interval" value="1000"/>
         </cm:default-properties>
     </cm:property-placeholder>
 
@@ -104,16 +105,31 @@
     <bean id="schemaRegistryImpl" class="org.apache.unomi.services.impl.schemas.SchemaRegistryImpl" init-method="init"
           destroy-method="destroy">
         <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="profileService" ref="profileServiceImpl" />
+        <property name="profileService" ref="profileServiceImpl"/>
+        <property name="persistenceService" ref="persistenceService"/>
+        <property name="schedulerService" ref="schedulerServiceImpl"/>
+        <property name="jsonSchemaRefreshInterval" value="${services.json.schema.refresh.interval}"/>
     </bean>
     <service id="schemaRegistry" ref="schemaRegistryImpl" interface="org.apache.unomi.api.services.SchemaRegistry"/>
 
+    <bean id="jsonSchemaListenerImpl" class="org.apache.unomi.services.listener.JsonSchemaListener"
+          init-method="postConstruct" destroy-method="preDestroy">
+        <property name="persistenceService" ref="persistenceService"/>
+        <property name="bundleContext" ref="blueprintBundleContext"/>
+        <property name="schemaRegistry" ref="schemaRegistryImpl"/>
+    </bean>
+    <service id="jsonSchemaListener" ref="jsonSchemaListenerImpl">
+        <interfaces>
+            <value>org.osgi.framework.SynchronousBundleListener</value>
+        </interfaces>
+    </service>
+
     <bean id="eventServiceImpl" class="org.apache.unomi.services.impl.events.EventServiceImpl">
         <property name="persistenceService" ref="persistenceService"/>
         <property name="definitionsService" ref="definitionsServiceImpl"/>
         <property name="sourceService" ref="sourceServiceImpl"/>
         <property name="bundleContext" ref="blueprintBundleContext"/>
-        <property name="schemaRegistry" ref="schemaRegistryImpl" />
+        <property name="schemaRegistry" ref="schemaRegistryImpl"/>
         <property name="predefinedEventTypeIds">
             <set>
                 <value>view</value>
diff --git a/services/src/main/resources/org.apache.unomi.services.cfg b/services/src/main/resources/org.apache.unomi.services.cfg
index fca3cc9..1f9a9bd 100644
--- a/services/src/main/resources/org.apache.unomi.services.cfg
+++ b/services/src/main/resources/org.apache.unomi.services.cfg
@@ -76,4 +76,7 @@ events.shouldBeCheckedEventSourceId=${org.apache.unomi.events.shouldBeCheckedEve
 rules.optimizationActivated=${org.apache.unomi.rules.optimizationActivated:-true}
 
 # The number of threads to compose the pool size of the scheduler.
-scheduler.thread.poolSize=${org.apache.unomi.scheduler.thread.poolSize:-5}
\ No newline at end of file
+scheduler.thread.poolSize=${org.apache.unomi.scheduler.thread.poolSize:-5}
+
+# The interval in milliseconds to reload the json schemas in memory
+services.json.schema.refresh.interval=${org.apache.unomi.json.schema.refresh.interval:-1000}
diff --git a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeploymentCommandSupport.java b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeploymentCommandSupport.java
index e417d0d..56db909 100644
--- a/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeploymentCommandSupport.java
+++ b/tools/shell-dev-commands/src/main/java/org/apache/unomi/shell/commands/DeploymentCommandSupport.java
@@ -73,6 +73,7 @@ public abstract class DeploymentCommandSupport implements Action {
     public static final String VALUE_DEFINITION_TYPE = "values";
     public static final String MERGER_DEFINITION_TYPE = "mergers";
     public static final String MAPPING_DEFINITION_TYPE = "mappings";
+    public static final String JSON_SCHEMA_DEFINITION_TYPE = "jsonschema";
 
     protected final static List<String> definitionTypes = Arrays.asList(
             CONDITION_DEFINITION_TYPE,
@@ -87,7 +88,8 @@ public abstract class DeploymentCommandSupport implements Action {
             PATCH_DEFINITION_TYPE,
             VALUE_DEFINITION_TYPE,
             MERGER_DEFINITION_TYPE,
-            MAPPING_DEFINITION_TYPE);
+            MAPPING_DEFINITION_TYPE,
+            JSON_SCHEMA_DEFINITION_TYPE);
 
     @Argument(index = 0, name = "bundleId", description = "The bundle identifier where to find the definition", multiValued = false)
     Long bundleIdentifier;