You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by GitBox <gi...@apache.org> on 2022/05/23 12:10:38 UTC

[GitHub] [unomi] sergehuber commented on a diff in pull request #426: UNOMI-571: JSON Schema extensions system

sergehuber commented on code in PR #426:
URL: https://github.com/apache/unomi/pull/426#discussion_r879358169


##########
extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java:
##########
@@ -50,86 +48,98 @@ public class SchemaServiceImpl implements SchemaService {
 
     ObjectMapper objectMapper = new ObjectMapper();
 
-    private final Map<String, JsonSchemaWrapper> predefinedUnomiJSONSchemaById = new HashMap<>();
-    private Map<String, JsonSchemaWrapper> schemasById = new HashMap<>();
+    /**
+     *  Schemas provided by Unomi runtime bundles in /META-INF/cxs/schemas/...
+     */
+    private final ConcurrentMap<String, JsonSchemaWrapper> predefinedUnomiJSONSchemaById = new ConcurrentHashMap<>();
+    /**
+     * All Unomi schemas indexed by URI
+     */
+    private final ConcurrentMap<String, JsonSchemaWrapper> schemasById = new ConcurrentHashMap<>();
+    /**
+     * Available extensions indexed by key:schema URI to be extended, value: list of schema extension URIs
+     */
+    private final ConcurrentMap<String, Set<String>> extensions = new ConcurrentHashMap<>();
 
     private Integer jsonSchemaRefreshInterval = 1000;
     private ScheduledFuture<?> scheduledFuture;
 
-    private BundleContext bundleContext;
     private PersistenceService persistenceService;
-    private SchedulerService schedulerService;
     private JsonSchemaFactory jsonSchemaFactory;
 
+    // TODO UNOMI-572: when fixing UNOMI-572 please remove the usage of the custom ScheduledExecutorService and re-introduce the Unomi Scheduler Service
+    private ScheduledExecutorService scheduler;
+    //private SchedulerService schedulerService;
 
-    @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());

Review Comment:
   Are we sure non of the data is used in the message ? Because otherwise it could lead to log injection.



##########
itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java:
##########
@@ -56,87 +54,185 @@ 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,
                 DEFAULT_TRYING_TRIES);
     }
 
     @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("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);
 
-        post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+        // 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("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);
+    }
 
-        jsonSchemas = keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(),
+    @Test
+    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("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);
+
+        // 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"));
+
+        // update the schema to allow "invalidPropName":
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties-updated.json"));
+        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());
-        assertEquals("JSON schema list should not be empty", 1, jsonSchemas.size());
     }
 
     @Test
-    public void testSaveNewValidJSONSchema() throws InterruptedException {
+    public void testExtension_SaveDelete() throws InterruptedException, IOException {
+        // Push base schemas
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy.json"));
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties.json"));
+        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);
 
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
+        // check that extended event is not valid at first
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-extended.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
 
-        CloseableHttpResponse response = post(JSONSCHEMA_URL, "schemas/events/test-event-type.json", ContentType.TEXT_PLAIN);
+        // register both extensions (for root event and the properties level)
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-extension.json"));
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties-extension.json"));
+        keepTrying("Extended event should be valid since of the extensions have been deployed",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-extended.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                isValid -> isValid,
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
 
-        assertEquals("Invalid response code", 200, response.getStatusLine().getStatusCode());
-        List jsonSchemas = keepTrying("Couldn't find json schemas", () -> get(JSONSCHEMA_URL, List.class), (list) -> !list.isEmpty(),
+        // delete one of the extension
+        schemaService.deleteSchema("https://unomi.apache.org/schemas/json/events/dummy/properties/extension/1-0-0");
+        keepTrying("Extended event should be invalid again, one necessary extension have been removed",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-extended.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 testExtension_Update() throws InterruptedException, IOException {
+        // Push base schemas
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy.json"));
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties.json"));
+        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);
+
+        // check that extended event is not valid at first
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-extended.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
+
+        // register both extensions (for root event and the properties level)
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-extension.json"));
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties-extension.json"));
+        keepTrying("Extended event should be valid since of the extensions have been deployed",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-extended.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                isValid -> isValid,
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
+
+        // check that extended event 2 is not valid due to usage of unevaluatedProperty not bring by schemas or extensions
+        assertFalse(schemaService.isValid(resourceAsString("schemas/event-dummy-extended-2.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"));
+
+        // Update extensions to allow the extended event 2
+        schemaService.saveSchema(resourceAsString("schemas/schema-dummy-properties-extension-2.json"));
+        keepTrying("Extended event 2 should be valid since of the extensions have been updated",
+                () -> schemaService.isValid(resourceAsString("schemas/event-dummy-extended-2.json"), "https://unomi.apache.org/schemas/json/events/dummy/1-0-0"),
+                isValid -> isValid,
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
     }
 
     @Test
-    public void testDeleteJSONSchema() throws InterruptedException {
-        assertTrue("JSON schema list should be empty", persistenceService.getAllItems(JsonSchemaWrapper.class).isEmpty());
+    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());

Review Comment:
   ```suggestion
           assertFalse("JSON schema list should not be empty, it should contain predefined Unomi schemas", jsonSchemas.isEmpty());
   ```



##########
extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java:
##########
@@ -139,103 +149,192 @@ public void saveSchema(String schema) {
     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;
     }
 
     @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;
+    }
+
+    private JsonSchemaWrapper buildJsonSchemaWrapper(String schema) {
+        JsonSchema jsonSchema = jsonSchemaFactory.getSchema(schema);
+        JsonNode schemaNode = jsonSchema.getSchemaNode();
+
+        String schemaId = schemaNode.get("$id").asText();
+        String target = schemaNode.at("/self/target").asText();
+        String name = schemaNode.at("/self/name").asText();
+        String extendsSchemaId = schemaNode.at("/self/extends").asText();
 
-        return predefinedUnomiJSONSchemaById.remove(schemaId) != null && schemasById.remove(schemaId) != null;
+        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, extendsSchemaId, new Date());
     }
 
-    @Override
-    public JsonSchemaWrapper getSchema(String schemaId) {
-        return schemasById.get(schemaId);
+    private void refreshJSONSchemas() {
+        // use local variable to avoid concurrency issues.
+        Map<String, JsonSchemaWrapper> schemasByIdReloaded = new HashMap<>();
+        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 changes = schemasByIdReloaded.size() != schemasById.size();
+        // check for modifications
+        if (!changes) {
+            for (JsonSchemaWrapper reloadedSchema : schemasByIdReloaded.values()) {
+                JsonSchemaWrapper oldSchema = schemasById.get(reloadedSchema.getItemId());
+                if (oldSchema == null || !oldSchema.getTimeStamp().equals(reloadedSchema.getTimeStamp())) {
+                    changes = true;
+                    break;
+                }
+            }
+        }
+
+        if (changes) {
+            schemasById.clear();
+            schemasById.putAll(schemasByIdReloaded);
+
+            initExtensions(schemasByIdReloaded);
+            initJsonSchemaFactory();
+        }
     }
 
-    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;
+    private void initExtensions(Map<String, JsonSchemaWrapper> schemas) {
+        Map<String, Set<String>> extensionsReloaded = new HashMap<>();
+        // lookup extensions
+        List<JsonSchemaWrapper> schemaExtensions = schemas.values()
+                .stream()
+                .filter(jsonSchemaWrapper -> StringUtils.isNotBlank(jsonSchemaWrapper.getExtendsSchemaId()))
+                .collect(Collectors.toList());
+
+        // build new in RAM extensions map
+        for (JsonSchemaWrapper extension : schemaExtensions) {
+            String extendedSchemaId = extension.getExtendsSchemaId();
+            if (!extension.getItemId().equals(extendedSchemaId)) {
+                if (!extensionsReloaded.containsKey(extendedSchemaId)) {
+                    extensionsReloaded.put(extendedSchemaId, new HashSet<>());
+                }
+                extensionsReloaded.get(extendedSchemaId).add(extension.getItemId());
+            } else {
+                logger.warn("A schema cannot extends himself, please fix your schema definition for schema: {}", extendedSchemaId);
             }
-            return IOUtils.toInputStream(jsonSchemaWrapper.getSchema());
-        };
+        }
+
+        extensions.clear();
+        extensions.putAll(extensionsReloaded);
     }
 
-    private void refreshJSONSchemas() {
-        schemasById = new HashMap<>();
-        schemasById.putAll(predefinedUnomiJSONSchemaById);
+    private String checkForExtensions(String id, String schema) throws JsonProcessingException {

Review Comment:
   I find the name of this method a bit strange as it actually returns a modified schema with extensions in it. Maybe generateExtendedSchema might be better ? 



##########
itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java:
##########
@@ -56,87 +54,185 @@ 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,
                 DEFAULT_TRYING_TRIES);
     }
 
     @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",

Review Comment:
   Same comment as above 



##########
itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java:
##########
@@ -136,29 +139,32 @@ public void setUp() throws InterruptedException {
         keepTrying("Profile " + TEST_PROFILE_ID + " not found in the required time", () -> profileService.load(TEST_PROFILE_ID),
                 Objects::nonNull, DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
 
-        keepTrying("Couldn't find json schema endpoint", () -> get(JSONSCHEMA_URL, List.class), Objects::nonNull, DEFAULT_TRYING_TIMEOUT,
-                DEFAULT_TRYING_TRIES);
+        // create schemas required for tests
+        schemaService.saveSchema(resourceAsString(TEST_EVENT_TYPE_SCHEMA));
+        schemaService.saveSchema(resourceAsString(FLOAT_PROPERTY_EVENT_TYPE_SCHEMA));
+        keepTrying("Couldn't find json schemas",
+                () -> schemaService.getInstalledJsonSchemaIds(),
+                (schemaIds) -> (schemaIds.contains("https://unomi.apache.org/schemas/json/events/floatPropertyType/1-0-0") &&
+                        schemaIds.contains("https://unomi.apache.org/schemas/json/events/testEventType/1-0-0")),
+                DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES);
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws InterruptedException {
         TestUtils.removeAllEvents(definitionsService, persistenceService);
         TestUtils.removeAllSessions(definitionsService, persistenceService);
         TestUtils.removeAllProfiles(definitionsService, persistenceService);
         profileService.delete(profile.getItemId(), false);
         segmentService.removeSegmentDefinition(SEGMENT_ID, false);
 
-        String encodedString = Base64.getEncoder()
-                .encodeToString("https://unomi.apache.org/schemas/json/events/testEventType/1-0-0".getBytes());
-        delete(JSONSCHEMA_URL + "/" + encodedString);
-
-        encodedString = Base64.getEncoder()
-                .encodeToString("https://unomi.apache.org/schemas/json/events/floatPropertyType/1-0-0".getBytes());
-        delete(JSONSCHEMA_URL + "/" + encodedString);
-
-        encodedString = Base64.getEncoder()
-                .encodeToString("https://unomi.apache.org/schemas/json/events/floatPropertyType/1-0-0".getBytes());
-        delete(JSONSCHEMA_URL + "/" + encodedString);
+        // cleanup schemas
+        schemaService.deleteSchema("https://unomi.apache.org/schemas/json/events/testEventType/1-0-0");
+        schemaService.deleteSchema("https://unomi.apache.org/schemas/json/events/floatPropertyType/1-0-0");
+        keepTrying("Couldn't find json schemas",

Review Comment:
   Maybe the message should be inverted here ? (Could still find JSON schema)



##########
extensions/json-schema/services/src/main/java/org/apache/unomi/schema/impl/SchemaServiceImpl.java:
##########
@@ -50,86 +48,98 @@ public class SchemaServiceImpl implements SchemaService {
 
     ObjectMapper objectMapper = new ObjectMapper();
 
-    private final Map<String, JsonSchemaWrapper> predefinedUnomiJSONSchemaById = new HashMap<>();
-    private Map<String, JsonSchemaWrapper> schemasById = new HashMap<>();
+    /**
+     *  Schemas provided by Unomi runtime bundles in /META-INF/cxs/schemas/...
+     */
+    private final ConcurrentMap<String, JsonSchemaWrapper> predefinedUnomiJSONSchemaById = new ConcurrentHashMap<>();
+    /**
+     * All Unomi schemas indexed by URI
+     */
+    private final ConcurrentMap<String, JsonSchemaWrapper> schemasById = new ConcurrentHashMap<>();
+    /**
+     * Available extensions indexed by key:schema URI to be extended, value: list of schema extension URIs
+     */
+    private final ConcurrentMap<String, Set<String>> extensions = new ConcurrentHashMap<>();
 
     private Integer jsonSchemaRefreshInterval = 1000;
     private ScheduledFuture<?> scheduledFuture;
 
-    private BundleContext bundleContext;
     private PersistenceService persistenceService;
-    private SchedulerService schedulerService;
     private JsonSchemaFactory jsonSchemaFactory;
 
+    // TODO UNOMI-572: when fixing UNOMI-572 please remove the usage of the custom ScheduledExecutorService and re-introduce the Unomi Scheduler Service
+    private ScheduledExecutorService scheduler;
+    //private SchedulerService schedulerService;
 
-    @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());

Review Comment:
   Same question here about log injection.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@unomi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org