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

jsinovassinnaik pushed a commit to branch UNOMI-775-add-validation-endpoint
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit 68b8a38d9c9f56c2dd53f432439da7b7e71ea439
Author: jsinovassin <jsinovassinn...@jahia.com>
AuthorDate: Fri Apr 28 18:40:39 2023 +0200

    UNOMI-775 : add json schema validation endpoint for event list
---
 .../unomi/schema/rest/JsonSchemaEndPoint.java      | 27 +++++++--
 .../org/apache/unomi/schema/api/SchemaService.java | 10 ++++
 .../unomi/schema/impl/SchemaServiceImpl.java       | 58 +++++++++++++++++---
 .../java/org/apache/unomi/itests/JSONSchemaIT.java | 64 +++++++++++++++++++---
 4 files changed, 139 insertions(+), 20 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 fdb919538..c157370fd 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
@@ -36,8 +36,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import java.util.Collection;
-import java.util.Set;
+import java.util.*;
 
 @WebService
 @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
@@ -95,7 +94,7 @@ public class JsonSchemaEndPoint {
      */
     @POST
     @Path("/")
-    @Consumes({ MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON })
+    @Consumes({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
     @Produces(MediaType.APPLICATION_JSON)
     public Response save(String jsonSchema) {
         try {
@@ -119,12 +118,13 @@ public class JsonSchemaEndPoint {
 
     /**
      * Being able to validate a given event is useful when you want to develop 
custom events and associated schemas
+     *
      * @param event the event to be validated
      * @return Validation error messages if there is some
      */
     @POST
     @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
-    @Consumes({ MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON })
+    @Consumes({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
     @Path("/validateEvent")
     public Collection<ValidationError> validateEvent(String event) {
         try {
@@ -134,4 +134,23 @@ public class JsonSchemaEndPoint {
             throw new InvalidRequestException(errorMessage, errorMessage);
         }
     }
+
+    /**
+     * Being able to validate a given list of event is useful when you want to 
develop custom events and associated schemas
+     *
+     * @param events the events to be validated
+     * @return Validation error messages if there is some grouped per event 
type
+     */
+    @POST
+    @Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
+    @Consumes({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON})
+    @Path("/validateEvents")
+    public Map<String, Set<ValidationError>> validateEvents(String events) {
+        try {
+            return schemaService.validateEvents(events);
+        } catch (Exception e) {
+            String errorMessage = "Unable to validate event: " + 
e.getMessage();
+            throw new InvalidRequestException(errorMessage, errorMessage);
+        }
+    }
 }
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 2690f5ba8..162ecbe77 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
@@ -20,6 +20,7 @@ package org.apache.unomi.schema.api;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -63,6 +64,15 @@ public interface SchemaService {
      */
     Set<ValidationError> validateEvent(String event) throws 
ValidationException;
 
+    /**
+     * perform a validation of a list of the given events
+     *
+     * @param events the events to validate
+     * @return The Map of validation errors group per event type in case there 
is some, empty map otherwise
+     * @throws ValidationException in case something goes wrong and validation 
could not be performed.
+     */
+    Map<String,Set<ValidationError>> validateEvents(String events) throws 
ValidationException;
+
     /**
      * Get the list of installed Json Schema Ids
      *
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 2371738b6..7da059fa6 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
@@ -40,6 +40,7 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
+import java.text.MessageFormat;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.stream.Collectors;
@@ -54,7 +55,7 @@ public class SchemaServiceImpl implements SchemaService {
     ObjectMapper objectMapper = new ObjectMapper();
 
     /**
-     *  Schemas provided by Unomi runtime bundles in /META-INF/cxs/schemas/...
+     * Schemas provided by Unomi runtime bundles in /META-INF/cxs/schemas/...
      */
     private final ConcurrentMap<String, JsonSchemaWrapper> 
predefinedUnomiJSONSchemaById = new ConcurrentHashMap<>();
     /**
@@ -111,12 +112,52 @@ public class SchemaServiceImpl implements SchemaService {
 
     @Override
     public Set<ValidationError> validateEvent(String event) throws 
ValidationException {
-        JsonNode jsonEvent = parseData(event);
-        String eventType = extractEventType(jsonEvent);
+        return validateNodeEvent(parseData(event));
+    }
+
+    @Override
+    public Map<String, Set<ValidationError>> validateEvents(String events) 
throws ValidationException {
+        Map<String, Set<ValidationError>> errorsPerEventType = new HashMap<>();
+        JsonNode eventsNodes = parseData(events);
+        eventsNodes.forEach(event -> {
+            String eventType = event.get("eventType").asText();
+            try {
+                Set<ValidationError> errors = validateNodeEvent(event);
+                if (errorsPerEventType.containsKey(eventType)) {
+                    errorsPerEventType.get(eventType).addAll(errors);
+                } else {
+                    errorsPerEventType.put(eventType, errors);
+                }
+            } catch (ValidationException e) {
+                Set<ValidationError> errors = buildCustomErrorMessage();
+                if (errorsPerEventType.containsKey(eventType)) {
+                    errorsPerEventType.get(eventType).addAll(errors);
+                } else {
+                    errorsPerEventType.put(eventType, errors);
+                }
+                errorsPerEventType.put(eventType, errors);
+
+                logger.debug(e.getMessage());
+            }
+        });
+        return errorsPerEventType;
+    }
+
+    private Set<ValidationError> buildCustomErrorMessage() {
+        ValidationMessage.Builder builder = new ValidationMessage.Builder();
+        builder.customMessage("No Schema found for this event 
type").format(new MessageFormat("Not used pattern. Message format is 
required"));
+        ValidationError error = new ValidationError(builder.build());
+        Set<ValidationError> errors = new HashSet<>();
+        errors.add(error);
+        return errors;
+    }
+
+    private Set<ValidationError> validateNodeEvent(JsonNode event) throws 
ValidationException {
+        String eventType = extractEventType(event);
         JsonSchemaWrapper eventSchema = getSchemaForEventType(eventType);
         JsonSchema jsonSchema = getJsonSchema(eventSchema.getItemId());
 
-        return validate(jsonEvent, jsonSchema);
+        return validate(event, jsonSchema);
     }
 
     @Override
@@ -145,9 +186,9 @@ public class SchemaServiceImpl implements SchemaService {
         return schemasById.values().stream()
                 .filter(jsonSchemaWrapper ->
                         jsonSchemaWrapper.getTarget() != null &&
-                        jsonSchemaWrapper.getTarget().equals(TARGET_EVENTS) &&
-                        jsonSchemaWrapper.getName() != null &&
-                        jsonSchemaWrapper.getName().equals(eventType))
+                                
jsonSchemaWrapper.getTarget().equals(TARGET_EVENTS) &&
+                                jsonSchemaWrapper.getName() != null &&
+                                jsonSchemaWrapper.getName().equals(eventType))
                 .findFirst()
                 .orElseThrow(() -> new ValidationException("Schema not found 
for event type: " + eventType));
     }
@@ -211,7 +252,6 @@ public class SchemaServiceImpl implements SchemaService {
         if (StringUtils.isEmpty(data)) {
             throw new ValidationException("Empty data, nothing to validate");
         }
-
         try {
             return objectMapper.readTree(data);
         } catch (Exception e) {
@@ -318,7 +358,7 @@ public class SchemaServiceImpl implements SchemaService {
             ArrayNode allOf;
             if (jsonSchema.at("/allOf") instanceof MissingNode) {
                 allOf = objectMapper.createArrayNode();
-            } else if (jsonSchema.at("/allOf") instanceof ArrayNode){
+            } else if (jsonSchema.at("/allOf") instanceof ArrayNode) {
                 allOf = (ArrayNode) jsonSchema.at("/allOf");
             } else {
                 logger.warn("Cannot extends schema allOf property, it should 
be an Array, please fix your schema definition for schema: {}", id);
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 43cb5949d..cd4373b48 100644
--- a/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/JSONSchemaIT.java
@@ -30,6 +30,7 @@ import org.apache.unomi.api.services.ScopeService;
 import org.apache.unomi.itests.tools.httpclient.HttpClientThatWaitsForUnomi;
 import org.apache.unomi.schema.api.JsonSchemaWrapper;
 import org.apache.unomi.schema.api.SchemaService;
+import org.apache.unomi.schema.api.ValidationError;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,6 +48,8 @@ import java.util.*;
 
 import static org.junit.Assert.*;
 
+import java.util.stream.Collectors;
+
 /**
  * Class to tests the JSON schema features
  */
@@ -67,7 +70,7 @@ public class JSONSchemaIT extends BaseIT {
                 DEFAULT_TRYING_TRIES);
 
         TestUtils.createScope(DUMMY_SCOPE, "Dummy scope", scopeService);
-        keepTrying("Scope "+ DUMMY_SCOPE +" not found in the required time", 
() -> scopeService.getScope(DUMMY_SCOPE),
+        keepTrying("Scope " + DUMMY_SCOPE + " not found in the required time", 
() -> scopeService.getScope(DUMMY_SCOPE),
                 Objects::nonNull, DEFAULT_TRYING_TIMEOUT, 
DEFAULT_TRYING_TRIES);
     }
 
@@ -242,6 +245,53 @@ public class JSONSchemaIT extends BaseIT {
                 DEFAULT_TRYING_TRIES);
     }
 
+    @Test
+    public void testValidateEvents_valid() throws Exception {
+        
assertNull(schemaService.getSchema("https://vendor.test.com/schemas/json/events/flattened/1-0-0";));
+        
assertNull(schemaService.getSchema("https://vendor.test.com/schemas/json/events/flattened/properties/1-0-0";));
+        
assertNull(schemaService.getSchema("https://vendor.test.com/schemas/json/events/flattened/properties/interests/1-0-0";));
+
+        // Test that at first the flattened event is not valid.
+        
assertFalse(schemaService.isEventValid(resourceAsString("schemas/event-flattened-valid.json")));
+
+        // save schemas and check the event pass the validation
+        
schemaService.saveSchema(resourceAsString("schemas/schema-flattened.json"));
+        
schemaService.saveSchema(resourceAsString("schemas/schema-flattened-flattenedProperties.json"));
+        
schemaService.saveSchema(resourceAsString("schemas/schema-flattened-flattenedProperties-interests.json"));
+        
schemaService.saveSchema(resourceAsString("schemas/schema-flattened-properties.json"));
+
+        StringBuilder listEvents = new StringBuilder("");
+        listEvents
+                .append("[")
+                .append(resourceAsString("schemas/event-flattened-valid.json"))
+                .append("]");
+
+        keepTrying("No error should have been detected",
+                () -> {
+                    try {
+                        return 
schemaService.validateEvents(listEvents.toString()).get("flattened").isEmpty();
+                    } catch (Exception e) {
+                        return false;
+                    }
+                },
+                isValid -> isValid, DEFAULT_TRYING_TIMEOUT, 
DEFAULT_TRYING_TRIES);
+
+        StringBuilder listInvalidEvents = new StringBuilder("");
+        listInvalidEvents
+                .append("[")
+                
.append(resourceAsString("schemas/event-flattened-invalid-1.json")).append(",")
+                
.append(resourceAsString("schemas/event-flattened-invalid-2.json")).append(",")
+                
.append(resourceAsString("schemas/event-flattened-invalid-3.json")).append(",")
+                
.append(resourceAsString("schemas/event-flattened-invalid-3.json"))
+                .append("]");
+        Map<String, Set<ValidationError>> errors = 
schemaService.validateEvents(listInvalidEvents.toString());
+
+        assertEquals(9, errors.get("flattened").size());
+        // Verify that error on interests.football appear only once even if 
two events have the issue
+        assertEquals(1, 
errors.get("flattened").stream().filter(validationError -> 
validationError.getError().startsWith("$.flattenedProperties.interests.football")).collect(Collectors.toList()).size());
+    }
+
+
     @Test
     public void testFlattenedProperties() throws Exception {
         
assertNull(schemaService.getSchema("https://vendor.test.com/schemas/json/events/flattened/1-0-0";));
@@ -269,15 +319,15 @@ public class JSONSchemaIT extends BaseIT {
 
         // check that range query is not working on flattened props:
         Condition condition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
-        
condition.setParameter("propertyName","flattenedProperties.interests.cars");
-        condition.setParameter("comparisonOperator","greaterThan");
+        condition.setParameter("propertyName", 
"flattenedProperties.interests.cars");
+        condition.setParameter("comparisonOperator", "greaterThan");
         condition.setParameter("propertyValueInteger", 2);
         assertNull(persistenceService.query(condition, null, Event.class, 0, 
-1));
 
         // check that term query is working on flattened props:
         condition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
-        
condition.setParameter("propertyName","flattenedProperties.interests.cars");
-        condition.setParameter("comparisonOperator","equals");
+        condition.setParameter("propertyName", 
"flattenedProperties.interests.cars");
+        condition.setParameter("comparisonOperator", "equals");
         condition.setParameter("propertyValueInteger", 15);
         List<Event> events = persistenceService.query(condition, null, 
Event.class, 0, -1).getList();
         assertEquals(1, events.size());
@@ -325,8 +375,8 @@ public class JSONSchemaIT extends BaseIT {
 
         // wait for the event to be indexed
         Condition condition = new 
Condition(definitionsService.getConditionType("eventPropertyCondition"));
-        condition.setParameter("propertyName","properties.marker.keyword");
-        condition.setParameter("comparisonOperator","equals");
+        condition.setParameter("propertyName", "properties.marker.keyword");
+        condition.setParameter("comparisonOperator", "equals");
         condition.setParameter("propertyValue", eventMarker);
         List<Event> events = keepTrying("The event should have been persisted",
                 () -> persistenceService.query(condition, null, Event.class), 
results -> results.size() == 1,

Reply via email to