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/05/19 16:59:05 UTC

[unomi] 01/01: UNOMI-571: Fix concurrency issue, cache issue in JSON Schema and provide better tests coverage

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

jkevan pushed a commit to branch json-schema-extensions
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit 9f119bb582bc3bb33b5289be3748b57430fdfa3a
Author: Kevan <ke...@jahia.com>
AuthorDate: Thu May 19 18:58:48 2022 +0200

    UNOMI-571: Fix concurrency issue, cache issue in JSON Schema and provide better tests coverage
---
 .../unomi/schema/rest/JsonSchemaEndPoint.java      |  20 +--
 .../apache/unomi/schema/api/JsonSchemaWrapper.java |  51 ++++--
 .../org/apache/unomi/schema/api/SchemaService.java |  25 +--
 .../unomi/schema/impl/SchemaServiceImpl.java       | 196 ++++++++++++---------
 .../unomi/schema/listener/JsonSchemaListener.java  |  16 --
 .../resources/OSGI-INF/blueprint/blueprint.xml     |   2 -
 .../java/org/apache/unomi/itests/JSONSchemaIT.java | 145 ++++++++++-----
 .../resources/schemas/event-dummy-invalid-1.json   |   9 +
 .../resources/schemas/event-dummy-invalid-2.json   |   9 +
 .../resources/schemas/event-dummy-invalid-3.json   |   8 +
 .../test/resources/schemas/event-dummy-valid.json  |   8 +
 .../resources/schemas/events/dummy-event-type.json |  43 -----
 .../schemas/events/float-property-type.json        |  23 ---
 .../schemas/events/negative-test-event-type.json   |  13 --
 .../resources/schemas/events/test-event-type.json  |  14 --
 .../schemas/schema-dummy-properties-updated.json   |  24 +++
 .../resources/schemas/schema-dummy-properties.json |  21 +++
 .../src/test/resources/schemas/schema-dummy.json   |  22 +++
 ...-invalid-name.json => schema-invalid-name.json} |   0
 .../test-invalid.json => schema-invalid.json}      |   0
 ...ined-event-type.json => schema-predefined.json} |   4 +-
 21 files changed, 362 insertions(+), 291 deletions(-)

diff --git a/extensions/json-schema/rest/src/main/java/org/apache/unomi/schema/rest/JsonSchemaEndPoint.java b/extensions/json-schema/rest/src/main/java/org/apache/unomi/schema/rest/JsonSchemaEndPoint.java
index 26f09222f..6c9fb021d 100644
--- a/extensions/json-schema/rest/src/main/java/org/apache/unomi/schema/rest/JsonSchemaEndPoint.java
+++ b/extensions/json-schema/rest/src/main/java/org/apache/unomi/schema/rest/JsonSchemaEndPoint.java
@@ -18,7 +18,6 @@
 package org.apache.unomi.schema.rest;
 
 import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
-import org.apache.unomi.api.Metadata;
 import org.apache.unomi.rest.exception.InvalidRequestException;
 import org.apache.unomi.schema.api.SchemaService;
 import org.osgi.service.component.annotations.Component;
@@ -32,7 +31,7 @@ import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.util.Base64;
-import java.util.List;
+import java.util.Set;
 
 @WebService
 @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@@ -56,21 +55,14 @@ public class JsonSchemaEndPoint {
     }
 
     /**
-     * Retrieves the 50 first json schema metadatas by default.
+     * Get the list of installed Json Schema Ids
      *
-     * @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
+     * @return A Set of JSON schema ids
      */
     @GET
     @Path("/")
-    public List<Metadata> getJsonSchemaMetadatas(@QueryParam("offset") @DefaultValue("0") int offset,
-            @QueryParam("size") @DefaultValue("50") int size, @QueryParam("sort") String sortBy) {
-        return schemaService.getJsonSchemaMetadatas(offset, size, sortBy).getList();
+    public Set<String> getInstalledJsonSchemaIds() {
+        return schemaService.getInstalledJsonSchemaIds();
     }
 
     /**
@@ -81,7 +73,7 @@ public class JsonSchemaEndPoint {
      */
     @POST
     @Path("/")
-    @Consumes(MediaType.TEXT_PLAIN)
+    @Consumes({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
     @Produces(MediaType.APPLICATION_JSON)
     public Response save(String jsonSchema) {
         try {
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/JsonSchemaWrapper.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/JsonSchemaWrapper.java
index ce123879e..41049519d 100644
--- a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/JsonSchemaWrapper.java
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/JsonSchemaWrapper.java
@@ -17,8 +17,10 @@
 
 package org.apache.unomi.schema.api;
 
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.MetadataItem;
+import org.apache.unomi.api.Item;
+import org.apache.unomi.api.TimestampedItem;
+
+import java.util.Date;
 
 /**
  * Object which represents a JSON schema, it's a wrapper because it contains some additional info used by the
@@ -26,35 +28,35 @@ import org.apache.unomi.api.MetadataItem;
  * The JSON schema is store as String to avoid transformation during JSON schema resolution in the Unomi SchemaService.
  * Also, it's extending  MetadataItem so that it can be persisted like that in Unomi storage system.
  */
-public class JsonSchemaWrapper extends MetadataItem {
+public class JsonSchemaWrapper extends Item implements TimestampedItem {
     public static final String ITEM_TYPE = "jsonSchema";
 
-    private String id;
     private String schema;
     private String target;
+    private String extendsSchema;
+    private Date timeStamp;
 
-    public JsonSchemaWrapper(){}
+    /**
+     * Instantiates a new JSON schema
+     */
+    public JsonSchemaWrapper() {
+    }
 
     /**
-     * Instantiates a new JSON schema with an id and a schema as string
+     * Instantiates a new JSON schema
      *
      * @param id     id of the schema
      * @param schema as string
-     * @param target of the schema
+     * @param target of the schema (optional)
+     * @param extendsSchema is the URI of another Schema to be extended by current one. (optional)
+     * @param timeStamp of the schema
      */
-    public JsonSchemaWrapper(String id, String schema, String target) {
-        super(new Metadata(id));
-        this.id = id;
+    public JsonSchemaWrapper(String id, String schema, String target, String extendsSchema, Date timeStamp) {
+        super(id);
         this.schema = schema;
         this.target = target;
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
+        this.extendsSchema = extendsSchema;
+        this.timeStamp = timeStamp;
     }
 
     public String getSchema() {
@@ -72,4 +74,17 @@ public class JsonSchemaWrapper extends MetadataItem {
     public void setTarget(String target) {
         this.target = target;
     }
+
+    public String getExtendsSchema() {
+        return extendsSchema;
+    }
+
+    public void setExtendsSchema(String extendsSchema) {
+        this.extendsSchema = extendsSchema;
+    }
+
+    @Override
+    public Date getTimeStamp() {
+        return timeStamp;
+    }
 }
\ No newline at end of file
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/SchemaService.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/SchemaService.java
index c479c4015..34c11e7d5 100644
--- a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/SchemaService.java
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/api/SchemaService.java
@@ -17,32 +17,16 @@
 
 package org.apache.unomi.schema.api;
 
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.PartialList;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Service that allow to manage JSON schema. It allows to get, save and delete schemas
  */
 public interface SchemaService {
 
-    /**
-     * 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 a jsonNode is valid against a schema
      *
@@ -52,6 +36,13 @@ public interface SchemaService {
      */
     boolean isValid(String data, String schemaId);
 
+    /**
+     * Get the list of installed Json Schema Ids
+     *
+     * @return A Set of JSON schema ids
+     */
+    Set<String> getInstalledJsonSchemaIds();
+
     /**
      * Get a schema matching by a schema id
      *
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
index e91d29776..51625f438 100644
--- a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java
@@ -17,27 +17,25 @@
 
 package org.apache.unomi.schema.impl;
 
-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.uri.URIFetcher;
 import org.apache.commons.io.IOUtils;
-import org.apache.unomi.api.Metadata;
-import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.Item;
 import org.apache.unomi.api.services.SchedulerService;
 import org.apache.unomi.persistence.spi.PersistenceService;
 import org.apache.unomi.schema.api.JsonSchemaWrapper;
 import org.apache.unomi.schema.api.SchemaService;
-import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -50,60 +48,84 @@ public class SchemaServiceImpl implements SchemaService {
 
     ObjectMapper objectMapper = new ObjectMapper();
 
-    private final Map<String, JsonSchemaWrapper> predefinedUnomiJSONSchemaById = new HashMap<>();
-    private Map<String, JsonSchemaWrapper> schemasById = new HashMap<>();
+    private final ConcurrentMap<String, JsonSchemaWrapper> predefinedUnomiJSONSchemaById = new ConcurrentHashMap<>();
+    private ConcurrentMap<String, JsonSchemaWrapper> schemasById = new ConcurrentHashMap<>();
+
+    private final JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(URI, JsonMetaSchema.getV201909())
+            .addKeyword(new NonValidationKeyword("self"))
+            .build();
+    private final URIFetcher jsonSchemaURIFetcher = uri -> {
+        logger.debug("Fetching schema {}", uri);
+        JsonSchemaWrapper jsonSchemaWrapper = schemasById.get(uri.toString());
+        if (jsonSchemaWrapper == null) {
+            logger.error("Couldn't find schema {}", uri);
+            return null;
+        }
+
+        return IOUtils.toInputStream(jsonSchemaWrapper.getSchema());
+    };
 
     private Integer jsonSchemaRefreshInterval = 1000;
     private ScheduledFuture<?> scheduledFuture;
 
-    private BundleContext bundleContext;
     private PersistenceService persistenceService;
     private SchedulerService schedulerService;
     private JsonSchemaFactory jsonSchemaFactory;
 
-
-    @Override
-    public PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String sortBy) {
-        PartialList<JsonSchemaWrapper> items = persistenceService.getAllItems(JsonSchemaWrapper.class, offset, size, sortBy);
-        List<Metadata> details = new LinkedList<>();
-        for (JsonSchemaWrapper definition : items.getList()) {
-            details.add(definition.getMetadata());
-        }
-        return new PartialList<>(details, items.getOffset(), items.getPageSize(), items.getTotalSize(), items.getTotalSizeRelation());
-    }
-
     @Override
     public boolean isValid(String data, String schemaId) {
-        JsonSchema jsonSchema = null;
-        JsonNode jsonNode = null;
+        JsonSchema jsonSchema;
+        JsonNode jsonNode;
 
         try {
             jsonNode = objectMapper.readTree(data);
             jsonSchema = jsonSchemaFactory.getSchema(new URI(schemaId));
         } catch (Exception e) {
-            logger.error("Failed to process data to validate because {} - Set SchemaServiceImpl at DEBUG level for more detail ", e.getMessage());
+            logger.error("Schema validation failed because: {} - Set SchemaServiceImpl at DEBUG level for more detail ", e.getMessage());
             logger.debug("full error",e);
             return false;
         }
 
         if (jsonNode == null) {
-            logger.warn("No data to validate");
+            // no data to validate
             return false;
         }
 
         if (jsonSchema == null) {
-            logger.warn("No schema found for {}", schemaId);
+            logger.warn("Schema validation failed because: Schema not found {}", schemaId);
+            return false;
+        }
+
+        Set<ValidationMessage> validationMessages;
+        try {
+            validationMessages = jsonSchema.validate(jsonNode);
+        } catch (Exception e) {
+            logger.error("Schema validation failed because: {} - Set SchemaServiceImpl at DEBUG level for more detail ", e.getMessage());
+            logger.debug("full error",e);
             return false;
         }
 
-        Set<ValidationMessage> validationMessages = jsonSchema.validate(jsonNode);
         if (validationMessages == null || validationMessages.isEmpty()) {
             return true;
+        } else {
+            logger.error("Schema validation found {} errors while validating against schema: {}  - Set SchemaServiceImpl at DEBUG level for more detail ", validationMessages.size(), schemaId);
+            if (logger.isDebugEnabled()) {
+                for (ValidationMessage validationMessage : validationMessages) {
+                    logger.debug("Validation error: {}", validationMessage);
+                }
+            }
+            return false;
         }
-        for (ValidationMessage validationMessage : validationMessages) {
-            logger.error("Error validating object against schema {}: {}", schemaId, validationMessage);
-        }
-        return false;
+    }
+
+    @Override
+    public JsonSchemaWrapper getSchema(String schemaId) {
+        return schemasById.get(schemaId);
+    }
+
+    @Override
+    public Set<String> getInstalledJsonSchemaIds() {
+        return schemasById.keySet();
     }
 
     @Override
@@ -114,22 +136,9 @@ public class SchemaServiceImpl implements SchemaService {
 
     @Override
     public void saveSchema(String schema) {
-        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schema);
-        JsonNode schemaNode = jsonSchema.getSchemaNode();
-        String id = schemaNode.get("$id").asText();
-
-        if (!predefinedUnomiJSONSchemaById.containsKey(id)) {
-            String target = schemaNode.at("/self/target").asText();
-            String name = schemaNode.at("/self/name").asText();
-
-            if ("events".equals(target) && !name.matches("[_A-Za-z][_0-9A-Za-z]*")) {
-                throw new IllegalArgumentException(
-                        "The \"/self/name\" value should match the following regular expression [_A-Za-z][_0-9A-Za-z]* for the Json schema on events");
-            }
-
-            JsonSchemaWrapper jsonSchemaWrapper = new JsonSchemaWrapper(id, schema, target);
+        JsonSchemaWrapper jsonSchemaWrapper = buildJsonSchemaWrapper(schema);
+        if (!predefinedUnomiJSONSchemaById.containsKey(jsonSchemaWrapper.getItemId())) {
             persistenceService.save(jsonSchemaWrapper);
-            schemasById.put(id, jsonSchemaWrapper);
         } else {
             throw new IllegalArgumentException("Trying to save a Json Schema that is using the ID of an existing Json Schema provided by Unomi is forbidden");
         }
@@ -139,7 +148,7 @@ public class SchemaServiceImpl implements SchemaService {
     public boolean deleteSchema(String schemaId) {
         // forbidden to delete predefined Unomi schemas
         if (!predefinedUnomiJSONSchemaById.containsKey(schemaId)) {
-            schemasById.remove(schemaId);
+            // remove persisted schema
             return persistenceService.remove(schemaId, JsonSchemaWrapper.class);
         }
         return false;
@@ -147,52 +156,69 @@ public class SchemaServiceImpl implements SchemaService {
 
     @Override
     public void loadPredefinedSchema(InputStream schemaStream) throws IOException {
-        String jsonSchema = IOUtils.toString(schemaStream);
-
-        // check that schema is valid and get the id
-        JsonNode schemaNode = jsonSchemaFactory.getSchema(jsonSchema).getSchemaNode();
-        String schemaId = schemaNode.get("$id").asText();
-        String target = schemaNode.at("/self/target").asText();
-        JsonSchemaWrapper jsonSchemaWrapper = new JsonSchemaWrapper(schemaId, jsonSchema, target);
-
-        predefinedUnomiJSONSchemaById.put(schemaId, jsonSchemaWrapper);
-        schemasById.put(schemaId, jsonSchemaWrapper);
+        String schema = IOUtils.toString(schemaStream);
+        JsonSchemaWrapper jsonSchemaWrapper = buildJsonSchemaWrapper(schema);
+        predefinedUnomiJSONSchemaById.put(jsonSchemaWrapper.getItemId(), jsonSchemaWrapper);
     }
 
     @Override
     public boolean unloadPredefinedSchema(InputStream schemaStream) {
         JsonNode schemaNode = jsonSchemaFactory.getSchema(schemaStream).getSchemaNode();
         String schemaId = schemaNode.get("$id").asText();
-
-        return predefinedUnomiJSONSchemaById.remove(schemaId) != null && schemasById.remove(schemaId) != null;
+        return predefinedUnomiJSONSchemaById.remove(schemaId) != null;
     }
 
-    @Override
-    public JsonSchemaWrapper getSchema(String schemaId) {
-        return schemasById.get(schemaId);
-    }
+    private JsonSchemaWrapper buildJsonSchemaWrapper(String schema) {
+        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schema);
+        JsonNode schemaNode = jsonSchema.getSchemaNode();
 
-    private URIFetcher getUriFetcher() {
-        return uri -> {
-            logger.debug("Fetching schema {}", uri);
-            JsonSchemaWrapper jsonSchemaWrapper = schemasById.get(uri.toString());
-            if (jsonSchemaWrapper == null) {
-                logger.error("Couldn't find schema {}", uri);
-                return null;
-            }
-            return IOUtils.toInputStream(jsonSchemaWrapper.getSchema());
-        };
+        String schemaId = schemaNode.get("$id").asText();
+        String target = schemaNode.at("/self/target").asText();
+        String name = schemaNode.at("/self/name").asText();
+        String extendsSchema = schemaNode.at("/self/extends").asText();
+
+        if ("events".equals(target) && !name.matches("[_A-Za-z][_0-9A-Za-z]*")) {
+            throw new IllegalArgumentException(
+                    "The \"/self/name\" value should match the following regular expression [_A-Za-z][_0-9A-Za-z]* for the Json schema on events");
+        }
+
+        return new JsonSchemaWrapper(schemaId, schema, target, extendsSchema, new Date());
     }
 
     private void refreshJSONSchemas() {
-        schemasById = new HashMap<>();
-        schemasById.putAll(predefinedUnomiJSONSchemaById);
+        // use local variable to avoid concurrency issues.
+        ConcurrentMap<String, JsonSchemaWrapper> schemasByIdReloaded = new ConcurrentHashMap<>();
+        schemasByIdReloaded.putAll(predefinedUnomiJSONSchemaById);
+        schemasByIdReloaded.putAll(persistenceService.getAllItems(JsonSchemaWrapper.class).stream().collect(Collectors.toMap(Item::getItemId, s -> s)));
+
+        // flush cache if size is different (can be new schema or deleted schemas)
+        boolean flushCache = schemasByIdReloaded.size() != schemasById.size();
+        // check dates
+        if (!flushCache) {
+            for (JsonSchemaWrapper reloadedSchema : schemasByIdReloaded.values()) {
+                JsonSchemaWrapper oldSchema = schemasById.get(reloadedSchema.getItemId());
+                if (oldSchema == null || !oldSchema.getTimeStamp().equals(reloadedSchema.getTimeStamp())) {
+                    flushCache = true;
+                    break;
+                }
+            }
+        }
 
-        persistenceService.getAllItems(JsonSchemaWrapper.class).forEach(
-                JsonSchemaWrapper -> schemasById.put(JsonSchemaWrapper.getId(), JsonSchemaWrapper));
+        if (flushCache) {
+            initJsonSchemaFactory();
+        }
+        schemasById = schemasByIdReloaded;
+    }
+
+    private void initPersistenceIndex() {
+        if (persistenceService.createIndex(JsonSchemaWrapper.ITEM_TYPE)) {
+            logger.info("{} index created", JsonSchemaWrapper.ITEM_TYPE);
+        } else {
+            logger.info("{} index already exists", JsonSchemaWrapper.ITEM_TYPE);
+        }
     }
 
-    private void initializeTimers() {
+    private void initTimers() {
         TimerTask task = new TimerTask() {
             @Override
             public void run() {
@@ -203,18 +229,18 @@ public class SchemaServiceImpl implements SchemaService {
                 .scheduleWithFixedDelay(task, 0, jsonSchemaRefreshInterval, TimeUnit.MILLISECONDS);
     }
 
-    public void init() {
-        JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(URI, JsonMetaSchema.getV201909())
-                .addKeyword(new NonValidationKeyword("self"))
-                .build();
-
+    private void initJsonSchemaFactory() {
         jsonSchemaFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
                 .addMetaSchema(jsonMetaSchema)
                 .defaultMetaSchemaURI(URI)
-                .uriFetcher(getUriFetcher(), "https", "http")
+                .uriFetcher(jsonSchemaURIFetcher, "https", "http")
                 .build();
+    }
 
-        initializeTimers();
+    public void init() {
+        initPersistenceIndex();
+        initJsonSchemaFactory();
+        initTimers();
         logger.info("Schema service initialized.");
     }
 
@@ -234,8 +260,4 @@ public class SchemaServiceImpl implements SchemaService {
     public void setJsonSchemaRefreshInterval(Integer jsonSchemaRefreshInterval) {
         this.jsonSchemaRefreshInterval = jsonSchemaRefreshInterval;
     }
-
-    public void setBundleContext(BundleContext bundleContext) {
-        this.bundleContext = bundleContext;
-    }
 }
diff --git a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/listener/JsonSchemaListener.java b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/listener/JsonSchemaListener.java
index 9e674d545..fda47dd85 100644
--- a/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/listener/JsonSchemaListener.java
+++ b/extensions/json-schema/services/src/main/java/org/apache/unomi/schema/listener/JsonSchemaListener.java
@@ -16,8 +16,6 @@
  */
 package org.apache.unomi.schema.listener;
 
-import org.apache.unomi.persistence.spi.PersistenceService;
-import org.apache.unomi.schema.api.JsonSchemaWrapper;
 import org.apache.unomi.schema.api.SchemaService;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -41,14 +39,9 @@ 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 SchemaService schemaService;
     private BundleContext bundleContext;
 
-    public void setPersistenceService(PersistenceService persistenceService) {
-        this.persistenceService = persistenceService;
-    }
-
     public void setSchemaService(SchemaService schemaService) {
         this.schemaService = schemaService;
     }
@@ -60,7 +53,6 @@ public class JsonSchemaListener implements SynchronousBundleListener {
     public void postConstruct() {
         logger.info("JSON schema listener initializing...");
         logger.debug("postConstruct {}", bundleContext.getBundle());
-        createIndexes();
 
         loadPredefinedSchemas(bundleContext, true);
 
@@ -106,14 +98,6 @@ public class JsonSchemaListener implements SynchronousBundleListener {
         }
     }
 
-    public void createIndexes() {
-        if (persistenceService.createIndex(JsonSchemaWrapper.ITEM_TYPE)) {
-            logger.info("{} index created", JsonSchemaWrapper.ITEM_TYPE);
-        } else {
-            logger.info("{} index already exists", JsonSchemaWrapper.ITEM_TYPE);
-        }
-    }
-
     private void loadPredefinedSchemas(BundleContext bundleContext, boolean load) {
         Enumeration<URL> predefinedSchemas = bundleContext.getBundle().findEntries(ENTRIES_LOCATION, "*.json", true);
         if (predefinedSchemas == null) {
diff --git a/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 02e3280c0..15443922b 100644
--- a/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/extensions/json-schema/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -33,7 +33,6 @@
 
     <bean id="schemaServiceImpl" class="org.apache.unomi.schema.impl.SchemaServiceImpl" init-method="init"
           destroy-method="destroy">
-        <property name="bundleContext" ref="blueprintBundleContext"/>
         <property name="persistenceService" ref="persistenceService"/>
         <property name="schedulerService" ref="schedulerService"/>
         <property name="jsonSchemaRefreshInterval" value="${json.schema.refresh.interval}"/>
@@ -42,7 +41,6 @@
 
     <bean id="jsonSchemaListenerImpl" class="org.apache.unomi.schema.listener.JsonSchemaListener"
           init-method="postConstruct" destroy-method="preDestroy">
-        <property name="persistenceService" ref="persistenceService"/>
         <property name="bundleContext" ref="blueprintBundleContext"/>
         <property name="schemaService" ref="schemaServiceImpl"/>
     </bean>
diff --git a/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java b/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
index 97879bdd4..0e63ae362 100644
--- a/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
@@ -19,7 +19,7 @@ package org.apache.unomi.itests;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.entity.ContentType;
-import org.apache.unomi.persistence.spi.PersistenceService;
+import org.apache.unomi.api.Event;
 import org.apache.unomi.schema.api.JsonSchemaWrapper;
 import org.apache.unomi.schema.api.SchemaService;
 import org.junit.After;
@@ -37,9 +37,7 @@ 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;
+import static org.junit.Assert.*;
 
 /**
  * Class to tests the JSON schema features
@@ -56,10 +54,6 @@ public class JSONSchemaIT extends BaseIT {
     @Filter(timeout = 600000)
     protected SchemaService schemaService;
 
-    @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,
@@ -67,76 +61,141 @@ public class JSONSchemaIT extends BaseIT {
     }
 
     @After
-    public void tearDown() {
-        schemaService.deleteSchema("https://unomi.apache.org/schemas/json/events/testEventType/1-0-0");
+    public void tearDown() throws InterruptedException {
+        removeItems(JsonSchemaWrapper.class, Event.class);
+        // ensure all schemas have been cleaned from schemaService.
+        keepTrying("Couldn't find json schemas",
+                () -> schemaService.getInstalledJsonSchemaIds(),
+                (list) -> (!list.contains("https://unomi.apache.org/schemas/json/events/dummy/1-0-0") &&
+                        !list.contains("https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0")),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
     }
 
     @Test
-    public void testGetJsonSchemasMetadatas() throws InterruptedException {
-        List jsonSchemas = get(JSONSCHEMA_URL, List.class);
-        assertTrue("JSON schema list should be empty", jsonSchemas.isEmpty());
+    public void testValidation_SaveDeleteSchemas() throws InterruptedException, IOException {
+        // check that event is not valid at first
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-valid.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
+
+        // Push schemas
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy.json"));
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties.json"));
+        keepTrying("Couldn't find json schemas",
+                () -> get(JSONSCHEMA_URL, List.class),
+                (list) -> (list.contains("https://unomi.apache.org/schemas/json/events/dummy/1-0-0") &&
+                        list.contains("https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0")),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
 
-        post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+        // event should be valid now
+        keepTrying("Event should be valid",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-valid.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                isValid -> isValid,
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
 
-        jsonSchemas = keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(),
+        // Test multiple invalid event:
+        // unevaluated property at root:
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-invalid-1.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
+        // unevaluated property in properties:
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-invalid-2.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
+        // bad type number but should be string:
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-invalid-3.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
+
+        // remove one of the schema:
+        assertTrue(schemaService.deleteSchema("https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0"));
+        keepTrying("Schema should have been deleted",
+                () -> schemaService.getInstalledJsonSchemaIds(),
+                (list) -> !list.contains("https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0"),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+
+        // event should be invalid now that one of the schema have been deleted -> this is validating cache is correctly flushed
+        keepTrying("Event should be invalid since of the schema have been deleted",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-valid.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                isValid -> !isValid,
                 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 {
+    public void testValidation_UpdateSchema() throws InterruptedException, IOException {
+        // check that event is not valid at first
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-valid.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
+
+        // Push schemas
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy.json"));
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties.json"));
+        keepTrying("Couldn't find json schemas",
+                () -> get(JSONSCHEMA_URL, List.class),
+                (list) -> (list.contains("https://unomi.apache.org/schemas/json/events/dummy/1-0-0") &&
+                        list.contains("https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0")),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
 
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
+        // event should be valid now
+        keepTrying("Event should be valid",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-valid.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                isValid -> isValid,
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
 
-        CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+        // Test the invalid event, that use the new prop "invalidPropName" in properties:
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-invalid-2.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
 
-        assertEquals("Invalid response code", 200, response.getStatusLine().getStatusCode());
-        List jsonSchemas = keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(),
+        // update the schema to allow "invalidPropName":
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties-updated.json"));
+        keepTrying("schema should be updated by refresh 1sec",
+                () -> schemaService.getSchema("https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0"),
+                schema -> (schema != null && schema.getSchema().contains("invalidPropName")),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+
+        // "invalidPropName" should be valid and allowed now
+        keepTrying("Event should be valid since of the schema have been updated",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-invalid-2.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                isValid -> isValid,
                 DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
-        assertFalse("JSON schema list should not be empty", jsonSchemas.isEmpty());
     }
 
     @Test
-    public void testSavePredefinedJSONSchema() throws IOException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
-        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/predefined-event-type.json", ContentType.TEXT_PLAIN)) {
-            assertEquals("Unable to save schema", 400, response.getStatusLine().getStatusCode());
-        }
+    public void testEndPoint_GetInstalledJsonSchemas() throws InterruptedException {
+        List<String> jsonSchemas = get(JSONSCHEMA_URL, List.class);
+        assertFalse("JSON schema list should not be empty, it should contains predefined Unomi schemas", jsonSchemas.isEmpty());
     }
 
     @Test
-    public void testDeleteJSONSchema() throws InterruptedException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
+    public void testEndPoint_SaveDelete() throws InterruptedException, IOException {
+        assertNull(schemaService.getSchema("https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
 
-        post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+        // Post schema using REST call
+        try(CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/schema-dummy.json", ContentType.TEXT_PLAIN)) {
+            assertEquals("Invalid response code", 200, response.getStatusLine().getStatusCode());
+        }
 
-        keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(), DEFAULT_TRYING_TIMEOUT,
-                DEFAULT_TRYING_TRIES);
+        // See schema is available
+        keepTrying("Schema should have been created", () -> schemaService.getSchema("https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                Objects::nonNull, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
 
+        // Delete Schema using REST call
         String encodedString = Base64.getEncoder()
-                .encodeToString("https://unomi.apache.org/schemas/json/events/testEventType/1-0-0".getBytes());
+                .encodeToString("https://unomi.apache.org/schemas/json/events/dummy/1-0-0".getBytes());
         CloseableHttpResponse response = delete(JSONSCHEMA_URL + "/" + encodedString);
         assertEquals("Invalid response code", 204, response.getStatusLine().getStatusCode());
 
-        List jsonSchemas = keepTrying("wait for empty list of schemas", () -> get(JSONSCHEMA_URL, List.class), List::isEmpty,
-                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+        keepTrying("Schema should have been deleted", () -> schemaService.getSchema("https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                Objects::isNull, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+    }
 
-        assertTrue("JSON schema list should be empty", jsonSchemas.isEmpty());
+    @Test
+    public void testSaveFail_PredefinedJSONSchema() throws IOException {
+        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/schema-predefined.json", ContentType.TEXT_PLAIN)) {
+            assertEquals("Unable to save schema", 400, response.getStatusLine().getStatusCode());
+        }
     }
 
     @Test
-    public void testSaveNewInvalidJSONSchema() throws IOException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
-        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-invalid.json", ContentType.TEXT_PLAIN)) {
+    public void testSaveFail_NewInvalidJSONSchema() throws IOException {
+        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/schema-invalid.json", ContentType.TEXT_PLAIN)) {
             assertEquals("Unable to save schema", 400, response.getStatusLine().getStatusCode());
         }
     }
 
     @Test
-    public void testSaveSchemaWithInvalidName() throws IOException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
-        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-invalid-name.json", ContentType.TEXT_PLAIN)) {
+    public void testSaveFail_SchemaWithInvalidName() throws IOException {
+        try (CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/schema-invalid-name.json", ContentType.TEXT_PLAIN)) {
             assertEquals("Unable to save schema", 400, response.getStatusLine().getStatusCode());
         }
     }
diff --git a/itests/src/test/resources/schemas/event-dummy-invalid-1.json b/itests/src/test/resources/schemas/event-dummy-invalid-1.json
new file mode 100644
index 000000000..208c9edc2
--- /dev/null
+++ b/itests/src/test/resources/schemas/event-dummy-invalid-1.json
@@ -0,0 +1,9 @@
+{
+  "eventType":"dummy",
+  "scope":"dummy_scope",
+  "invalidPropName": "This is for sure an unknown property !!!!!",
+  "properties": {
+    "workspace": "dummy_workspace",
+    "path": "dummy/path"
+  }
+}
\ No newline at end of file
diff --git a/itests/src/test/resources/schemas/event-dummy-invalid-2.json b/itests/src/test/resources/schemas/event-dummy-invalid-2.json
new file mode 100644
index 000000000..eb3be4cba
--- /dev/null
+++ b/itests/src/test/resources/schemas/event-dummy-invalid-2.json
@@ -0,0 +1,9 @@
+{
+  "eventType":"dummy",
+  "scope":"dummy_scope",
+  "properties": {
+    "invalidPropName": "This is for sure an unknown property !!!!!",
+    "workspace": "dummy_workspace",
+    "path": "dummy/path"
+  }
+}
\ No newline at end of file
diff --git a/itests/src/test/resources/schemas/event-dummy-invalid-3.json b/itests/src/test/resources/schemas/event-dummy-invalid-3.json
new file mode 100644
index 000000000..841a598c9
--- /dev/null
+++ b/itests/src/test/resources/schemas/event-dummy-invalid-3.json
@@ -0,0 +1,8 @@
+{
+  "eventType":"dummy",
+  "scope":"dummy_scope",
+  "properties": {
+    "workspace": "dummy_workspace",
+    "path": 15
+  }
+}
\ No newline at end of file
diff --git a/itests/src/test/resources/schemas/event-dummy-valid.json b/itests/src/test/resources/schemas/event-dummy-valid.json
new file mode 100644
index 000000000..ae55bdf33
--- /dev/null
+++ b/itests/src/test/resources/schemas/event-dummy-valid.json
@@ -0,0 +1,8 @@
+{
+  "eventType":"dummy",
+  "scope":"dummy_scope",
+  "properties": {
+    "workspace": "dummy_workspace",
+    "path": "dummy/path"
+  }
+}
\ No newline at end of file
diff --git a/itests/src/test/resources/schemas/events/dummy-event-type.json b/itests/src/test/resources/schemas/events/dummy-event-type.json
deleted file mode 100644
index e17ce633f..000000000
--- a/itests/src/test/resources/schemas/events/dummy-event-type.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
-  "$id": "https://unomi.apache.org/schemas/json/events/dummy_event_type/1-0-0",
-  "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "self": {
-    "vendor": "org.apache.unomi",
-    "name": "dummy_event_type",
-    "format": "jsonschema",
-    "target": "events",
-    "version": "1-0-0"
-  },
-  "title": "DummyEvent",
-  "type": "object",
-  "allOf": [
-    {
-      "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0"
-    }
-  ],
-  "properties": {
-    "source": {
-      "type": "object",
-      "properties": {
-        "itemType": {
-          "type": "string"
-        },
-        "scope": {
-          "type": "string"
-        },
-        "itemId": {
-          "type": "string"
-        },
-        "properties": {
-          "type": "object",
-          "properties": {
-            "myProperty": {
-              "type": "string",
-              "maxLength": 20000
-            }
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/itests/src/test/resources/schemas/events/float-property-type.json b/itests/src/test/resources/schemas/events/float-property-type.json
deleted file mode 100644
index 93bb3f8ea..000000000
--- a/itests/src/test/resources/schemas/events/float-property-type.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "$id": "https://unomi.apache.org/schemas/json/events/floatPropertyType/1-0-0",
-  "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "self":{
-    "vendor":"org.apache.unomi",
-    "target" : "events",
-    "name":"floatPropertyType",
-    "format":"jsonschema",
-    "version":"1-0-0"
-  },
-  "title": "FloatPropertyEvent",
-  "type": "object",
-  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }],
-  "properties" : {
-    "properties" : {
-      "type" : "object",
-      "properties" : {
-        "floatProperty" : { "type" : "number" }
-      },
-      "additionalProperties": false
-    }
-  }
-}
diff --git a/itests/src/test/resources/schemas/events/negative-test-event-type.json b/itests/src/test/resources/schemas/events/negative-test-event-type.json
deleted file mode 100644
index 5508a5e99..000000000
--- a/itests/src/test/resources/schemas/events/negative-test-event-type.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "$id": "https://unomi.apache.org/schemas/json/events/negativeTestEventType/1-0-0",
-  "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "self":{
-    "vendor":"org.apache.unomi",
-    "name":"negativeTestEventType",
-    "format":"jsonschema",
-    "version":"1-0-0"
-  },
-  "title": "TestEvent",
-  "type": "object",
-  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }]
-}
diff --git a/itests/src/test/resources/schemas/events/test-event-type.json b/itests/src/test/resources/schemas/events/test-event-type.json
deleted file mode 100644
index 8d5a928a2..000000000
--- a/itests/src/test/resources/schemas/events/test-event-type.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "$id": "https://unomi.apache.org/schemas/json/events/testEventType/1-0-0",
-  "$schema": "https://json-schema.org/draft/2019-09/schema",
-  "self":{
-    "vendor":"org.apache.unomi",
-    "name":"testEventType",
-    "format":"jsonschema",
-    "target":"events",
-    "version":"1-0-0"
-  },
-  "title": "TestEvent",
-  "type": "object",
-  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }]
-}
diff --git a/itests/src/test/resources/schemas/schema-dummy-properties-updated.json b/itests/src/test/resources/schemas/schema-dummy-properties-updated.json
new file mode 100644
index 000000000..f92c2f9d2
--- /dev/null
+++ b/itests/src/test/resources/schemas/schema-dummy-properties-updated.json
@@ -0,0 +1,24 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0",
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "self": {
+    "vendor": "org.modules.jahia",
+    "name": "dummyProperties",
+    "format": "jsonschema",
+    "version": "1-0-0"
+  },
+  "title": "dummyProperties",
+  "type": "object",
+  "properties": {
+    "workspace": {
+      "type": "string"
+    },
+    "path": {
+      "type": "string"
+    },
+    "invalidPropName": {
+      "type": "string"
+    }
+  },
+  "unevaluatedProperties": false
+}
diff --git a/itests/src/test/resources/schemas/schema-dummy-properties.json b/itests/src/test/resources/schemas/schema-dummy-properties.json
new file mode 100644
index 000000000..498dd58aa
--- /dev/null
+++ b/itests/src/test/resources/schemas/schema-dummy-properties.json
@@ -0,0 +1,21 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0",
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "self": {
+    "vendor": "org.modules.jahia",
+    "name": "dummyProperties",
+    "format": "jsonschema",
+    "version": "1-0-0"
+  },
+  "title": "dummyProperties",
+  "type": "object",
+  "properties": {
+    "workspace": {
+      "type": "string"
+    },
+    "path": {
+      "type": "string"
+    }
+  },
+  "unevaluatedProperties": false
+}
diff --git a/itests/src/test/resources/schemas/schema-dummy.json b/itests/src/test/resources/schemas/schema-dummy.json
new file mode 100644
index 000000000..6e133b25f
--- /dev/null
+++ b/itests/src/test/resources/schemas/schema-dummy.json
@@ -0,0 +1,22 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/events/dummy/1-0-0",
+  "$schema": "https://json-schema.org/draft/2019-09/schema",
+  "self":{
+    "vendor":"org.apache.unomi",
+    "name":"dummy",
+    "format":"jsonschema",
+    "target":"events",
+    "version":"1-0-0"
+  },
+  "title": "DummyEvent",
+  "type": "object",
+  "allOf": [
+    { "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }
+  ],
+  "properties": {
+    "properties": {
+      "$ref": "https://unomi.apache.org/schemas/json/events/dummy/properties/1-0-0"
+    }
+  },
+  "unevaluatedProperties": false
+}
diff --git a/itests/src/test/resources/schemas/events/test-invalid-name.json b/itests/src/test/resources/schemas/schema-invalid-name.json
similarity index 100%
rename from itests/src/test/resources/schemas/events/test-invalid-name.json
rename to itests/src/test/resources/schemas/schema-invalid-name.json
diff --git a/itests/src/test/resources/schemas/events/test-invalid.json b/itests/src/test/resources/schemas/schema-invalid.json
similarity index 100%
rename from itests/src/test/resources/schemas/events/test-invalid.json
rename to itests/src/test/resources/schemas/schema-invalid.json
diff --git a/itests/src/test/resources/schemas/events/predefined-event-type.json b/itests/src/test/resources/schemas/schema-predefined.json
similarity index 79%
rename from itests/src/test/resources/schemas/events/predefined-event-type.json
rename to itests/src/test/resources/schemas/schema-predefined.json
index 8d9ffd967..b4da0ada6 100644
--- a/itests/src/test/resources/schemas/events/predefined-event-type.json
+++ b/itests/src/test/resources/schemas/schema-predefined.json
@@ -10,5 +10,7 @@
   },
   "title": "TestEvent",
   "type": "object",
-  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }]
+  "allOf": [
+    { "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }
+  ]
 }