This is an automated email from the ASF dual-hosted git repository.
dgriffon pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git
The following commit(s) were added to refs/heads/master by this push:
new a374c88c9 UNOMI-562 (#407)
a374c88c9 is described below
commit a374c88c945771aa04a975315e4b32a410dc7915
Author: David Griffon <[email protected]>
AuthorDate: Tue Apr 26 17:24:23 2022 +0200
UNOMI-562 (#407)
* DMF-5359 :
- Add schema to contextRequest object
- Improve schema validation by using custom deserializers for
contextRequest and eventCollectorRequest
* improve deserialization
* restore null option to schemas
* send expected error
* improve response in case of invalid object to match what is expected
* code review changes
* fix unit tests + add validation on event collector request object
* fix session properties
* improve github action
* do log on integration test only
* fix tests
* restore fail on error
* remove isEventValid as now done at json level
* rebase master
* finish rebase of master
---
.github/workflows/unomi-ci-build-tests.yml | 11 ++
api/pom.xml | 5 +
.../apache/unomi/api/services/EventService.java | 9 --
.../apache/unomi/api/services/SchemaService.java | 7 +-
.../test/java/org/apache/unomi/itests/BaseIT.java | 8 ++
.../org/apache/unomi/itests/ContextServletIT.java | 4 -
.../org/apache/unomi/itests/InputValidationIT.java | 6 +
.../resources/schemas/events/dummy-event-type.json | 14 +++
.../unomi/persistence/spi/CustomObjectMapper.java | 8 ++
.../deserializers/ContextRequestDeserializer.java | 133 +++++++++++++++++++++
.../EventCollectorRequestDeserializer.java | 72 +++++++++++
.../org/apache/unomi/rest/server/RestServer.java | 25 +++-
.../RetroCompatibilityParamConverterProvider.java | 5 +-
.../rest/service/impl/RestServiceUtilsImpl.java | 5 -
.../services/impl/events/EventServiceImpl.java | 6 +-
.../services/impl/schemas/SchemaServiceImpl.java | 4 +-
.../META-INF/cxs/schemas/contextrequest.json | 65 ++++++++++
.../main/resources/META-INF/cxs/schemas/event.json | 8 +-
.../cxs/schemas/eventscollectorrequest.json | 23 ++++
.../cxs/schemas/personalization/filter.json | 21 ++++
.../personalization/personalizationrequest.json | 25 ++++
.../personalization/personalizedcontent.json | 22 ++++
.../cxs/schemas/personalization/target.json | 17 +++
.../META-INF/cxs/schemas/timestampeditem.json | 2 +-
24 files changed, 466 insertions(+), 39 deletions(-)
diff --git a/.github/workflows/unomi-ci-build-tests.yml
b/.github/workflows/unomi-ci-build-tests.yml
index 6a059f135..cb5f82463 100644
--- a/.github/workflows/unomi-ci-build-tests.yml
+++ b/.github/workflows/unomi-ci-build-tests.yml
@@ -41,3 +41,14 @@ jobs:
cache: maven
- name: Integration tests
run: mvn -ntp clean install -Pintegration-tests
+ - name: Archive unomi logs
+ uses: actions/upload-artifact@v3
+ if: failure()
+ with:
+ name: unomi-${{ matrix.java }}-${{ github.run_number }}
+ path: itests/target/exam/**/data/log
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v3
+ if: failure()
+ with:
+ report_paths: 'itests/target/failsafe-reports/TEST-*.xml'
diff --git a/api/pom.xml b/api/pom.xml
index 75da098d3..fa706a4dd 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -57,6 +57,11 @@
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<reporting>
diff --git a/api/src/main/java/org/apache/unomi/api/services/EventService.java
b/api/src/main/java/org/apache/unomi/api/services/EventService.java
index 4dde0a998..7c59b37fc 100644
--- a/api/src/main/java/org/apache/unomi/api/services/EventService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/EventService.java
@@ -69,15 +69,6 @@ public interface EventService {
*/
boolean isEventAllowed(Event event, String thirdPartyId);
-
- /**
- * Check if event fields complies with corresponding event JSON schema
definition
- *
- * @param event event to test
- * @return true if the event is valid
- */
- boolean isEventValid(Event event);
-
/**
* Get the third party server name, if the request is originated from a
known peer
*
diff --git a/api/src/main/java/org/apache/unomi/api/services/SchemaService.java
b/api/src/main/java/org/apache/unomi/api/services/SchemaService.java
index bbd6c9dfa..7d6e65480 100644
--- a/api/src/main/java/org/apache/unomi/api/services/SchemaService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/SchemaService.java
@@ -17,6 +17,7 @@
package org.apache.unomi.api.services;
+import com.fasterxml.jackson.databind.JsonNode;
import org.apache.unomi.api.Metadata;
import org.apache.unomi.api.PartialList;
import org.apache.unomi.api.schema.json.JSONSchema;
@@ -45,13 +46,13 @@ public interface SchemaService {
PartialList<Metadata> getJsonSchemaMetadatas(int offset, int size, String
sortBy);
/**
- * Verify if an object is valid against a schema
+ * Verify if a jsonNode is valid against a schema
*
- * @param object to validate
+ * @param jsonNode to validate
* @param schemaId id of the schema used for the validation
* @return true is the object is valid
*/
- boolean isValid(Object object, String schemaId);
+ boolean isValid(JsonNode jsonNode, String schemaId);
/**
* Get a schema matching by a schema id
diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index 5bc8d0360..1007ff74f 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -108,6 +108,7 @@ public abstract class BaseIT {
protected static final int REQUEST_TIMEOUT = 60000;
protected static final int DEFAULT_TRYING_TIMEOUT = 2000;
protected static final int DEFAULT_TRYING_TRIES = 30;
+ private final static String JSONSCHEMA_URL = "/cxs/jsonSchema";
@Inject
@Filter(timeout = 600000)
@@ -584,4 +585,11 @@ public abstract class BaseIT {
}
}
+ void registerEventType(String jsonSchemaFileName) {
+ post(JSONSCHEMA_URL, "schemas/events/" + jsonSchemaFileName,
ContentType.TEXT_PLAIN);
+ }
+ void unRegisterEventType(String jsonSchemaId) {
+ delete(JSONSCHEMA_URL + "/" +
Base64.getEncoder().encodeToString(jsonSchemaId.getBytes()));
+ }
+
}
diff --git a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
index 5c54efcae..300eed58b 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ContextServletIT.java
@@ -163,10 +163,6 @@ public class ContextServletIT extends BaseIT {
delete(JSONSCHEMA_URL + "/" + encodedString);
}
- private void registerEventType(String jsonSchemaFileName) {
- post(JSONSCHEMA_URL, "schemas/events/" + jsonSchemaFileName,
ContentType.TEXT_PLAIN);
- }
-
@Test
public void testUpdateEventFromContextAuthorizedThirdParty_Success()
throws IOException, InterruptedException {
//Arrange
diff --git
a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java
b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java
index 6199caace..538b8b52a 100644
--- a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java
@@ -24,6 +24,7 @@ import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.apache.unomi.itests.tools.httpclient.HttpClientThatWaitsForUnomi;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.PaxExam;
@@ -45,6 +46,7 @@ public class InputValidationIT extends BaseIT {
private final static String EVENT_COLLECTOR_URL = "/eventcollector";
private final static String CONTEXT_JS_URL = "/context.js";
private final static String CONTEXT_JSON_URL = "/context.json";
+ private final static String DUMMY_EVENT_TYPE_SCHEMA =
"dummy-event-type.json";
private final static String ERROR_MESSAGE_REQUEST_SIZE_LIMIT_EXCEEDED =
"Request rejected by the server because: Request size exceed the limit";
private final static String ERROR_MESSAGE_INVALID_DATA_RECEIVED = "Request
rejected by the server because: Invalid received data";
@@ -69,8 +71,10 @@ public class InputValidationIT extends BaseIT {
@Test
public void test_eventCollector_valid() throws IOException {
+ registerEventType(DUMMY_EVENT_TYPE_SCHEMA);
doPOSTRequestTest(EVENT_COLLECTOR_URL, null,
"/validation/eventcollector_valid.json", 200, null);
doGETRequestTest(EVENT_COLLECTOR_URL, null,
"/validation/eventcollector_valid.json", 200, null);
+
unRegisterEventType("https://unomi.apache.org/schemas/json/events/dummy_event_type/1-0-0");
}
@Test
@@ -99,8 +103,10 @@ public class InputValidationIT extends BaseIT {
@Test
public void test_eventCollector_request_size_exceed_limit() throws
IOException {
+ registerEventType(DUMMY_EVENT_TYPE_SCHEMA);
doPOSTRequestTest(EVENT_COLLECTOR_URL, null,
"/validation/eventcollector_request_size_invalid.json", 400,
ERROR_MESSAGE_REQUEST_SIZE_LIMIT_EXCEEDED);
doPOSTRequestTest(EVENT_COLLECTOR_URL, null,
"/validation/eventcollector_request_size_valid.json", 200, null);
+
unRegisterEventType("https://unomi.apache.org/schemas/json/events/dummy_event_type/1-0-0");
}
@Test
diff --git a/itests/src/test/resources/schemas/events/dummy-event-type.json
b/itests/src/test/resources/schemas/events/dummy-event-type.json
new file mode 100644
index 000000000..439f2f9f2
--- /dev/null
+++ b/itests/src/test/resources/schemas/events/dummy-event-type.json
@@ -0,0 +1,14 @@
+{
+ "$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":"events/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" }]
+}
diff --git
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
index f5f28448c..9e23a2399 100644
---
a/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
+++
b/persistence-spi/src/main/java/org/apache/unomi/persistence/spi/CustomObjectMapper.java
@@ -20,6 +20,7 @@ package org.apache.unomi.persistence.spi;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
@@ -50,6 +51,10 @@ public class CustomObjectMapper extends ObjectMapper {
private ItemDeserializer itemDeserializer;
public CustomObjectMapper() {
+ this(null);
+ }
+
+ public CustomObjectMapper(Map<Class, StdDeserializer<?>> deserializers) {
super();
super.registerModule(new JaxbAnnotationModule());
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
@@ -67,6 +72,9 @@ public class CustomObjectMapper extends ObjectMapper {
itemDeserializer = new ItemDeserializer();
deserializerModule.addDeserializer(Item.class, itemDeserializer);
+ if (deserializers != null) {
+ deserializers.forEach(deserializerModule::addDeserializer);
+ }
builtinItemTypeClasses = new HashMap<>();
builtinItemTypeClasses.put(Campaign.ITEM_TYPE, Campaign.class);
diff --git
a/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java
b/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java
new file mode 100644
index 000000000..5e5488f3b
--- /dev/null
+++
b/rest/src/main/java/org/apache/unomi/rest/deserializers/ContextRequestDeserializer.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.rest.deserializers;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.apache.unomi.api.ContextRequest;
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.Profile;
+import org.apache.unomi.api.services.PersonalizationService;
+import org.apache.unomi.api.services.SchemaService;
+import org.apache.unomi.rest.validation.request.InvalidRequestException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Custom deserializer for ContextRequest that do validate the object using
JSON Schema
+ */
+public class ContextRequestDeserializer extends
StdDeserializer<ContextRequest> {
+
+ private final SchemaService schemaService;
+
+ public ContextRequestDeserializer(SchemaService schemaRegistry) {
+ this(null, schemaRegistry);
+ }
+
+ public ContextRequestDeserializer(Class<ContextRequest> vc, SchemaService
schemaRegistry) {
+ super(vc);
+ this.schemaService = schemaRegistry;
+ }
+
+ @Override
+ public ContextRequest deserialize(JsonParser jsonParser,
DeserializationContext context) throws IOException, JsonProcessingException {
+ JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+ // Validate schema on it
+ if (!schemaService.isValid(node,
"https://unomi.apache.org/schemas/json/contextrequest/1-0-0")) {
+ throw new InvalidRequestException("Invalid received data",
"Invalid received data");
+ }
+ ContextRequest cr = new ContextRequest();
+ if (node.get("requireSegments") != null) {
+ cr.setRequireSegments(node.get("requireSegments").booleanValue());
+ }
+ final JsonNode requiredProfileProperties =
node.get("requiredProfileProperties");
+ if (requiredProfileProperties instanceof ArrayNode) {
+ List<String> profileProperties = new ArrayList<>();
+ requiredProfileProperties.elements().forEachRemaining(el ->
profileProperties.add(el.textValue()));
+ cr.setRequiredProfileProperties(profileProperties);
+ }
+ final JsonNode requiredSessionPropertiesNode =
node.get("requiredSessionProperties");
+ if (requiredSessionPropertiesNode instanceof ArrayNode) {
+ List<String> requiredSessionProperties = new ArrayList<>();
+ requiredSessionPropertiesNode.elements().forEachRemaining(el ->
requiredSessionProperties.add(el.textValue()));
+ cr.setRequiredSessionProperties(requiredSessionProperties);
+ }
+ if (node.get("requireScores") != null) {
+ cr.setRequireScores(node.get("requireScores").booleanValue());
+ }
+ final JsonNode eventsNode = node.get("events");
+ if (eventsNode instanceof ArrayNode) {
+ ArrayNode events = (ArrayNode) eventsNode;
+ List<Event> filteredEvents = new ArrayList<>();
+ for (JsonNode event : events) {
+ if (schemaService.isValid(event,
"https://unomi.apache.org/schemas/json/events/" +
event.get("eventType").textValue() + "/1-0-0")) {
+
filteredEvents.add(jsonParser.getCodec().treeToValue(event, Event.class));
+ }
+ }
+ cr.setEvents(filteredEvents);
+ }
+ final JsonNode filtersNode = node.get("filters");
+ if (filtersNode instanceof ArrayNode) {
+ ArrayNode filters = (ArrayNode) filtersNode;
+ List<PersonalizationService.PersonalizedContent> f = new
ArrayList<>();
+ filters.elements().forEachRemaining(el -> {
+ try {
+ f.add(jsonParser.getCodec().treeToValue(el,
PersonalizationService.PersonalizedContent.class));
+ } catch (JsonProcessingException e) {
+ // Unable to deserialize, ignore the entry
+ }
+ });
+ cr.setFilters(f);
+ }
+ final JsonNode personalizationsNode = node.get("personalizations");
+ if (personalizationsNode instanceof ArrayNode) {
+ ArrayNode personalizations = (ArrayNode) personalizationsNode;
+ List<PersonalizationService.PersonalizationRequest> p = new
ArrayList<>();
+ personalizations.elements().forEachRemaining(el -> {
+ try {
+ p.add(jsonParser.getCodec().treeToValue(el,
PersonalizationService.PersonalizationRequest.class));
+ } catch (JsonProcessingException e) {
+ // Unable to deserialize, ignore the entry
+ }
+ });
+ cr.setPersonalizations(p);
+ }
+ if (node.get("profileOverrides") != null) {
+
cr.setProfileOverrides(jsonParser.getCodec().treeToValue(node.get("profileOverrides"),
Profile.class));
+ }
+ if (node.get("sessionPropertiesOverrides") != null) {
+
jsonParser.getCodec().treeToValue(node.get("sessionPropertiesOverrides"),
Map.class);
+ }
+ if (node.get("sessionId") != null) {
+ cr.setSessionId(node.get("sessionId").textValue());
+ }
+ if (node.get("profileId") != null) {
+ cr.setProfileId(node.get("profileId").textValue());
+ }
+ if (node.get("clientId") != null) {
+ cr.setClientId(node.get("clientId").textValue());
+ }
+ return cr;
+ }
+}
\ No newline at end of file
diff --git
a/rest/src/main/java/org/apache/unomi/rest/deserializers/EventCollectorRequestDeserializer.java
b/rest/src/main/java/org/apache/unomi/rest/deserializers/EventCollectorRequestDeserializer.java
new file mode 100644
index 000000000..5e78fc0a9
--- /dev/null
+++
b/rest/src/main/java/org/apache/unomi/rest/deserializers/EventCollectorRequestDeserializer.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.rest.deserializers;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.EventsCollectorRequest;
+import org.apache.unomi.api.services.SchemaService;
+import org.apache.unomi.rest.validation.request.InvalidRequestException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom deserializer for EventCollectorRequest that do validate the events
using JSon Schema
+ */
+public class EventCollectorRequestDeserializer extends
StdDeserializer<EventsCollectorRequest> {
+
+ private final SchemaService schemaService;
+
+ public EventCollectorRequestDeserializer(SchemaService schemaRegistry) {
+ this(null, schemaRegistry);
+ }
+
+ public EventCollectorRequestDeserializer(Class<EventsCollectorRequest> vc,
SchemaService schemaService) {
+ super(vc);
+ this.schemaService = schemaService;
+ }
+
+ @Override
+ public EventsCollectorRequest deserialize(JsonParser jsonParser,
DeserializationContext context) throws IOException, JsonProcessingException {
+ JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+ if (!schemaService.isValid(node,
"https://unomi.apache.org/schemas/json/eventscollectorrequest/1-0-0")) {
+ throw new InvalidRequestException("Invalid received data",
"Invalid received data");
+ }
+
+ // Validate schema on each event
+ List<Event> filteredEvents = new ArrayList<>();
+ final JsonNode eventsNode = node.get("events");
+ if (eventsNode instanceof ArrayNode) {
+ for (JsonNode event : eventsNode) {
+ if (schemaService.isValid(event,
"https://unomi.apache.org/schemas/json/events/" +
event.get("eventType").textValue() + "/1-0-0")) {
+
filteredEvents.add(jsonParser.getCodec().treeToValue(event, Event.class));
+ }
+ }
+ }
+ EventsCollectorRequest eventsCollectorRequest = new
EventsCollectorRequest();
+ eventsCollectorRequest.setSessionId(node.get("sessionId").textValue());
+ eventsCollectorRequest.setEvents(filteredEvents);
+ return eventsCollectorRequest;
+ }
+}
diff --git a/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java
b/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java
index c260ffee0..2d43dcb0d 100644
--- a/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java
+++ b/rest/src/main/java/org/apache/unomi/rest/server/RestServer.java
@@ -17,6 +17,7 @@
package org.apache.unomi.rest.server;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import org.apache.cxf.Bus;
import org.apache.cxf.endpoint.Server;
@@ -28,10 +29,15 @@ import
org.apache.cxf.jaxrs.security.SimpleAuthorizingFilter;
import org.apache.cxf.jaxrs.validation.JAXRSBeanValidationInInterceptor;
import org.apache.cxf.jaxrs.validation.JAXRSBeanValidationOutInterceptor;
import org.apache.cxf.message.Message;
+import org.apache.unomi.api.ContextRequest;
+import org.apache.unomi.api.EventsCollectorRequest;
import org.apache.unomi.api.services.ConfigSharingService;
+import org.apache.unomi.api.services.SchemaService;
import org.apache.unomi.rest.authentication.AuthenticationFilter;
import org.apache.unomi.rest.authentication.AuthorizingInterceptor;
import org.apache.unomi.rest.authentication.RestAuthenticationConfig;
+import org.apache.unomi.rest.deserializers.ContextRequestDeserializer;
+import org.apache.unomi.rest.deserializers.EventCollectorRequestDeserializer;
import
org.apache.unomi.rest.server.provider.RetroCompatibilityParamConverterProvider;
import
org.apache.unomi.rest.validation.JAXRSBeanValidationInInterceptorOverride;
import org.apache.unomi.rest.validation.BeanValidationService;
@@ -52,11 +58,7 @@ import org.slf4j.LoggerFactory;
import javax.ws.rs.ext.ExceptionMapper;
import javax.xml.namespace.QName;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
+import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
@Component
@@ -75,6 +77,7 @@ public class RestServer {
private List<ExceptionMapper> exceptionMappers = new ArrayList<>();
private BeanValidationService beanValidationService;
private ConfigSharingService configSharingService;
+ private SchemaService schemaService;
// refresh
private long timeOfLastUpdate = System.currentTimeMillis();
@@ -83,6 +86,11 @@ public class RestServer {
private static final QName UNOMI_REST_SERVER_END_POINT_NAME = new
QName("http://rest.unomi.apache.org/", "UnomiRestServerEndPoint");
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ public void setSchemaService(SchemaService schemaService) {
+ this.schemaService = schemaService;
+ }
+
@Reference(cardinality = ReferenceCardinality.MANDATORY)
public void setServerBus(Bus serverBus) {
this.serverBus = serverBus;
@@ -203,8 +211,13 @@ public class RestServer {
List<Interceptor<? extends Message>> inInterceptors = new
ArrayList<>();
List<Interceptor<? extends Message>> outInterceptors = new
ArrayList<>();
+ Map<Class, StdDeserializer<?>> desers = new HashMap<>();
+ desers.put(ContextRequest.class, new
ContextRequestDeserializer(schemaService));
+ desers.put(EventsCollectorRequest.class, new
EventCollectorRequestDeserializer(schemaService));
+
+
// Build the server
- ObjectMapper objectMapper = new
org.apache.unomi.persistence.spi.CustomObjectMapper();
+ ObjectMapper objectMapper = new
org.apache.unomi.persistence.spi.CustomObjectMapper(desers);
JAXRSServerFactoryBean jaxrsServerFactoryBean = new
JAXRSServerFactoryBean();
jaxrsServerFactoryBean.setAddress("/");
jaxrsServerFactoryBean.setBus(serverBus);
diff --git
a/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java
b/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java
index 9dc65007d..d7b6f3e84 100644
---
a/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java
+++
b/rest/src/main/java/org/apache/unomi/rest/server/provider/RetroCompatibilityParamConverterProvider.java
@@ -20,9 +20,12 @@ import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.unomi.api.ContextRequest;
import org.apache.unomi.api.EventsCollectorRequest;
+import org.apache.unomi.rest.validation.request.InvalidRequestException;
+import javax.ws.rs.BadRequestException;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.ext.*;
+import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
@@ -62,7 +65,7 @@ public class RetroCompatibilityParamConverterProvider
implements ParamConverterP
try {
return
objectMapper.readValue(factory.createParser(value), rawType);
} catch (Exception e) {
- throw new ProcessingException(e);
+ throw new InvalidRequestException("Invalid received
data", "Invalid received data");
}
}
diff --git
a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
index 35a3d92a0..9f05bd6be 100644
---
a/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
+++
b/rest/src/main/java/org/apache/unomi/rest/service/impl/RestServiceUtilsImpl.java
@@ -87,11 +87,6 @@ public class RestServiceUtilsImpl implements
RestServiceUtils {
for (Event event : events) {
processedEventsCnt++;
if (event.getEventType() != null) {
- if (!eventService.isEventValid(event)) {
- logger.warn("Event is not valid : {}",
event.getEventType());
- continue;
- }
-
Event eventToSend = new Event(event.getEventType(),
session, profile, event.getSourceId(), event.getSource(),
event.getTarget(), event.getProperties(),
timestamp, event.isPersistent());
if (!eventService.isEventAllowed(event, thirdPartyId)) {
diff --git
a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
index 20fbdfee2..736de11cb 100644
---
a/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
+++
b/services/src/main/java/org/apache/unomi/services/impl/events/EventServiceImpl.java
@@ -17,6 +17,7 @@
package org.apache.unomi.services.impl.events;
+import com.fasterxml.jackson.databind.JsonNode;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressString;
import org.apache.commons.lang3.StringUtils;
@@ -31,6 +32,7 @@ import org.apache.unomi.api.actions.ActionPostExecutor;
import org.apache.unomi.api.conditions.Condition;
import org.apache.unomi.api.query.Query;
import org.apache.unomi.api.services.*;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
import org.apache.unomi.persistence.spi.PersistenceService;
import org.apache.unomi.persistence.spi.aggregate.TermsAggregate;
import org.apache.unomi.api.utils.ParserHelper;
@@ -138,10 +140,6 @@ public class EventServiceImpl implements EventService {
return true;
}
- public boolean isEventValid(Event event) {
- return schemaService.isValid(event,
"https://unomi.apache.org/schemas/json/events/" + event.getEventType() +
"/1-0-0");
- }
-
public String authenticateThirdPartyServer(String key, String ip) {
logger.debug("Authenticating third party server with key: " + key + "
and IP: " + ip);
if (key != null) {
diff --git
a/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaServiceImpl.java
b/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaServiceImpl.java
index c9c1e96da..dc6a339b9 100644
---
a/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaServiceImpl.java
+++
b/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaServiceImpl.java
@@ -108,7 +108,7 @@ public class SchemaServiceImpl implements SchemaService {
}
@Override
- public boolean isValid(Object object, String schemaId) {
+ public boolean isValid(JsonNode jsonNode, String schemaId) {
String schemaAsString;
JsonSchema jsonSchema = null;
try {
@@ -124,7 +124,7 @@ public class SchemaServiceImpl implements SchemaService {
}
if (jsonSchema != null) {
- JsonNode jsonNode =
CustomObjectMapper.getObjectMapper().convertValue(object, JsonNode.class);
+
Set<ValidationMessage> validationMessages =
jsonSchema.validate(jsonNode);
if (validationMessages == null || validationMessages.isEmpty()) {
return true;
diff --git
a/services/src/main/resources/META-INF/cxs/schemas/contextrequest.json
b/services/src/main/resources/META-INF/cxs/schemas/contextrequest.json
new file mode 100644
index 000000000..c81a4f765
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/contextrequest.json
@@ -0,0 +1,65 @@
+{
+ "$id": "https://unomi.apache.org/schemas/json/contextrequest/1-0-0",
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "title": "ContextRequest",
+ "type": "object",
+ "properties": {
+ "source": {
+ "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0"
+ },
+ "requireSegments": {
+ "type": ["null", "boolean"]
+ },
+ "requiredProfileProperties": {
+ "type": ["null", "array"],
+ "items": {
+ "type": "string"
+ }
+ },
+ "requiredSessionProperties": {
+ "type": ["null", "array"],
+ "items": {
+ "type": "string"
+ }
+ },
+ "requireScores": {
+ "type": ["null", "boolean"]
+ },
+ "events": {
+ "type": ["null", "array"],
+ "items": {
+ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0"
+ }
+ },
+ "filters": {
+ "type": ["null", "array"],
+ "items": {
+ "$ref":
"https://unomi.apache.org/schemas/json/personalization/personalizedcontent/1-0-0"
+ }
+ },
+ "personalizations": {
+ "type": ["null", "array"],
+ "items": {
+ "$ref":
"https://unomi.apache.org/schemas/json/personalization/personalizedrequest/1-0-0"
+ }
+ },
+ "profileOverrides": {
+ "$ref": "https://unomi.apache.org/schemas/json/profile/1-0-0"
+ },
+ "sessionPropertiesOverrides": {
+ "type": ["null", "object"],
+ "maxProperties": 50
+ },
+ "sessionId": {
+ "type": ["null", "string"],
+ "pattern" : "^(\\w|[-_@\\.]){0,60}$"
+ },
+ "profileId": {
+ "type": ["null", "string"],
+ "pattern" : "^(\\w|[-_@\\.]){0,60}$"
+ },
+ "clientId": {
+ "type": ["null", "string"]
+ }
+ }
+}
diff --git a/services/src/main/resources/META-INF/cxs/schemas/event.json
b/services/src/main/resources/META-INF/cxs/schemas/event.json
index 66cb8ad85..7c3b5bfd1 100644
--- a/services/src/main/resources/META-INF/cxs/schemas/event.json
+++ b/services/src/main/resources/META-INF/cxs/schemas/event.json
@@ -10,19 +10,19 @@
"pattern" : "^(\\w|[-_@\\.]){0,60}$"
},
"profileId" : {
- "type" : [ "null", "string"],
+ "type" : [ "string"],
"pattern" : "^(\\w|[-_@\\.]){0,60}$"
},
"sessionId" : {
- "type" : [ "null", "string"],
+ "type" : [ "string"],
"pattern" : "^(\\w|[-_@\\.]){0,60}$"
},
"scope" : {
- "type" : [ "null", "string"],
+ "type" : [ "string"],
"pattern" : "^(\\w|[-_@\\.]){0,60}$"
},
"sourceId" : {
- "type" : [ "null", "string"],
+ "type" : [ "string"],
"pattern" : "^(\\w|[-_@\\.]){0,60}$"
}
}
diff --git
a/services/src/main/resources/META-INF/cxs/schemas/eventscollectorrequest.json
b/services/src/main/resources/META-INF/cxs/schemas/eventscollectorrequest.json
new file mode 100644
index 000000000..39e98ff83
--- /dev/null
+++
b/services/src/main/resources/META-INF/cxs/schemas/eventscollectorrequest.json
@@ -0,0 +1,23 @@
+{
+ "$id": "https://unomi.apache.org/schemas/json/eventscollectorrequest/1-0-0",
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "title": "EventsCollectorRequest",
+ "type": "object",
+ "allOf": [
+ {
+ "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0"
+ }
+ ],
+ "properties": {
+ "events" : {
+ "type": ["null", "array"],
+ "items": {
+ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0"
+ }
+ },
+ "sessionId" : {
+ "type": ["null", "string"],
+ "pattern" : "^(\\w|[-_@\\.]){0,60}$"
+ }
+ }
+}
\ No newline at end of file
diff --git
a/services/src/main/resources/META-INF/cxs/schemas/personalization/filter.json
b/services/src/main/resources/META-INF/cxs/schemas/personalization/filter.json
new file mode 100644
index 000000000..112be929e
--- /dev/null
+++
b/services/src/main/resources/META-INF/cxs/schemas/personalization/filter.json
@@ -0,0 +1,21 @@
+{
+ "$id": "https://unomi.apache.org/schemas/json/personalization/filter/1-0-0",
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "title": "Filter",
+ "type": "object",
+ "properties": {
+ "appliesOn": {
+ "type": "array",
+ "items": {
+ "$ref":
"https://unomi.apache.org/schemas/json/personalization/target/1-0-0"
+ }
+ },
+ "condition": {
+ "$ref": "https://unomi.apache.org/schemas/json/condition/1-0-0"
+ },
+ "properties": {
+ "type": "object",
+ "maxProperties": 50
+ }
+ }
+}
\ No newline at end of file
diff --git
a/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizationrequest.json
b/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizationrequest.json
new file mode 100644
index 000000000..37e99f71c
--- /dev/null
+++
b/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizationrequest.json
@@ -0,0 +1,25 @@
+{
+ "$id":
"https://unomi.apache.org/schemas/json/personalization/personalizedrequest/1-0-0",
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "title": "PersonalizationRequest",
+ "type": "object",
+ "properties": {
+ "id" : {
+ "type" : "string"
+ },
+ "strategy" : {
+ "type" : "string"
+ },
+ "strategyOptions" : {
+ "type": "object",
+ "maxProperties": 50
+ },
+ "contents" : {
+ "type" : "array",
+ "items": {
+ "$ref":
"https://unomi.apache.org/schemas/json/personalization/personalizedcontent/1-0-0"
+ },
+ "maxProperties": 50
+ }
+ }
+}
\ No newline at end of file
diff --git
a/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizedcontent.json
b/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizedcontent.json
new file mode 100644
index 000000000..b8a29ef73
--- /dev/null
+++
b/services/src/main/resources/META-INF/cxs/schemas/personalization/personalizedcontent.json
@@ -0,0 +1,22 @@
+{
+ "$id":
"https://unomi.apache.org/schemas/json/personalization/personalizedcontent/1-0-0",
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "title": "PersonalizedContent",
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "filters": {
+ "type": "array",
+ "items": {
+ "$ref":
"https://unomi.apache.org/schemas/json/personalization/filter/1-0-0"
+ },
+ "maxProperties": 50
+ },
+ "properties": {
+ "type": "object",
+ "maxProperties": 50
+ }
+ }
+}
\ No newline at end of file
diff --git
a/services/src/main/resources/META-INF/cxs/schemas/personalization/target.json
b/services/src/main/resources/META-INF/cxs/schemas/personalization/target.json
new file mode 100644
index 000000000..4de017a47
--- /dev/null
+++
b/services/src/main/resources/META-INF/cxs/schemas/personalization/target.json
@@ -0,0 +1,17 @@
+{
+ "$id": "https://unomi.apache.org/schemas/json/personalization/target/1-0-0",
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "title": "Target",
+ "type": "object",
+ "properties": {
+ "target" : {
+ "type" : "string"
+ },
+ "values" : {
+ "type" : "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git
a/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
b/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
index 1d7c6ad3a..b4e1528d3 100644
--- a/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
+++ b/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
@@ -6,7 +6,7 @@
"allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0" }],
"properties" : {
"timeStamp" : {
- "type" : ["null","string"],
+ "type" : ["string"],
"format" : "date-time"
}
}