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

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

commit 1d44d76b73b87352a63a768a23978496bbcb7114
Author: Serge Huber <[email protected]>
AuthorDate: Tue Oct 12 14:43:04 2021 +0200

    JSON Schema integration
---
 .../main/java/org/apache/unomi/api/SchemaType.java |  60 ++++++
 .../apache/unomi/api/services/SchemaRegistry.java  |  32 +++
 services/pom.xml                                   |  10 +
 .../services/impl/schemas/PropertyTypeKeyword.java | 110 ++++++++++
 .../services/impl/schemas/SchemaRegistryImpl.java  | 224 +++++++++++++++++++++
 .../resources/META-INF/cxs/schemas/condition.json  |  15 ++
 .../META-INF/cxs/schemas/conditiontype.json        |  22 ++
 .../resources/META-INF/cxs/schemas/consent.json    |  25 +++
 .../META-INF/cxs/schemas/consentType.json          |  20 ++
 .../resources/META-INF/cxs/schemas/customitem.json |  20 ++
 .../META-INF/cxs/schemas/customitems/page.json     |  66 ++++++
 .../META-INF/cxs/schemas/customitems/site.json     |  13 ++
 .../main/resources/META-INF/cxs/schemas/event.json |  29 +++
 .../META-INF/cxs/schemas/events/modifyConsent.json |  20 ++
 .../META-INF/cxs/schemas/events/view.json          |  20 ++
 .../main/resources/META-INF/cxs/schemas/goal.json  |  18 ++
 .../main/resources/META-INF/cxs/schemas/item.json  |  31 +++
 .../resources/META-INF/cxs/schemas/metadata.json   |  44 ++++
 .../META-INF/cxs/schemas/metadataitem.json         |  14 ++
 .../resources/META-INF/cxs/schemas/parameter.json  |  20 ++
 .../resources/META-INF/cxs/schemas/profile.json    |  41 ++++
 .../resources/META-INF/cxs/schemas/session.json    |  41 ++++
 .../META-INF/cxs/schemas/timestampeditem.json      |  13 ++
 .../META-INF/cxs/schemas/values/boolean.json       |   6 +
 .../META-INF/cxs/schemas/values/date.json          |   7 +
 .../META-INF/cxs/schemas/values/email.json         |   7 +
 .../META-INF/cxs/schemas/values/integer.json       |   6 +
 .../META-INF/cxs/schemas/values/long.json          |   6 +
 .../resources/META-INF/cxs/schemas/values/set.json |   7 +
 .../META-INF/cxs/schemas/values/string.json        |   6 +
 .../resources/OSGI-INF/blueprint/blueprint.xml     |   8 +
 31 files changed, 961 insertions(+)

diff --git a/api/src/main/java/org/apache/unomi/api/SchemaType.java 
b/api/src/main/java/org/apache/unomi/api/SchemaType.java
new file mode 100644
index 0000000..a3bd37a
--- /dev/null
+++ b/api/src/main/java/org/apache/unomi/api/SchemaType.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.api;
+
+import java.util.Map;
+
+public class SchemaType implements PluginType {
+
+    private transient long pluginId;
+    private String schemaId;
+    private String target;
+    private Map<String,Object> schemaTree;
+
+    public long getPluginId() {
+        return pluginId;
+    }
+
+    public void setPluginId(long pluginId) {
+        this.pluginId = pluginId;
+    }
+
+    public String getSchemaId() {
+        return schemaId;
+    }
+
+    public void setSchemaId(String schemaId) {
+        this.schemaId = schemaId;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
+    public Map<String, Object> getSchemaTree() {
+        return schemaTree;
+    }
+
+    public void setSchemaTree(Map<String, Object> schemaTree) {
+        this.schemaTree = schemaTree;
+    }
+}
diff --git 
a/api/src/main/java/org/apache/unomi/api/services/SchemaRegistry.java 
b/api/src/main/java/org/apache/unomi/api/services/SchemaRegistry.java
new file mode 100644
index 0000000..03a234e
--- /dev/null
+++ b/api/src/main/java/org/apache/unomi/api/services/SchemaRegistry.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.api.services;
+
+import org.apache.unomi.api.SchemaType;
+
+import java.util.List;
+
+public interface SchemaRegistry {
+
+    boolean isValid(Object object, String schemaId);
+
+    SchemaType getSchema(String schemaId);
+
+    List<SchemaType> getTargetSchemas(String target);
+
+}
diff --git a/services/pom.xml b/services/pom.xml
index 9438cc9..c4acbb2 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -172,6 +172,12 @@
             <scope>provided</scope>
         </dependency>
 
+        <dependency>
+            <groupId>com.networknt</groupId>
+            <artifactId>json-schema-validator</artifactId>
+            <version>1.0.49</version>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -185,6 +191,10 @@
                         
<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
                         <Import-Package>
                             sun.misc;resolution:=optional,
+                            org.jcodings;resolution:=optional,
+                            org.jcodings.specific;resolution:=optional,
+                            org.joni;resolution:=optional,
+                            org.joni.exception;resolution:=optional,
                             *
                         </Import-Package>
                     </instructions>
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/schemas/PropertyTypeKeyword.java
 
b/services/src/main/java/org/apache/unomi/services/impl/schemas/PropertyTypeKeyword.java
new file mode 100644
index 0000000..68afb41
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/schemas/PropertyTypeKeyword.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.services.impl.schemas;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.networknt.schema.*;
+import org.apache.unomi.api.PropertyType;
+import org.apache.unomi.api.services.ProfileService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.MessageFormat;
+import java.util.*;
+
+class PropertyTypeKeyword extends AbstractKeyword {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(PropertyTypeKeyword.class);
+
+    private final ProfileService profileService;
+    private final SchemaRegistryImpl schemaRegistry;
+
+    private static final class PropertyTypeJsonValidator extends 
AbstractJsonValidator {
+
+        String schemaPath;
+        JsonNode schemaNode;
+        JsonSchema parentSchema;
+        ValidationContext validationContext;
+        ProfileService profileService;
+        SchemaRegistryImpl schemaRegistry;
+
+        public PropertyTypeJsonValidator(String keyword, String schemaPath, 
JsonNode schemaNode, JsonSchema parentSchema, ValidationContext 
validationContext, ProfileService profileService, SchemaRegistryImpl 
schemaRegistry) {
+            super(keyword);
+            this.schemaPath = schemaPath;
+            this.schemaNode = schemaNode;
+            this.parentSchema = parentSchema;
+            this.validationContext = validationContext;
+            this.profileService = profileService;
+            this.schemaRegistry = schemaRegistry;
+        }
+
+        @Override
+        public Set<ValidationMessage> validate(JsonNode node, JsonNode 
rootNode, String at) {
+            Set<ValidationMessage> validationMessages = new HashSet<>();
+            Iterator<String> fieldNames = node.fieldNames();
+            while (fieldNames.hasNext()) {
+                String fieldName = fieldNames.next();
+                PropertyType propertyType = getPropertyType(fieldName);
+                if (propertyType == null) {
+                    
validationMessages.add(buildValidationMessage(CustomErrorMessageType.of("property-not-found",
 new MessageFormat("{0} : Couldn''t find property type with id={1}")), at, 
fieldName));
+                } else {
+                    // @todo further validation, if it can be used in this 
context (event, profile, session)
+                    String valueTypeId = propertyType.getValueTypeId();
+                    JsonSchema jsonSchema = 
schemaRegistry.getJsonSchema("https://unomi.apache.org/schemas/json/values/"; + 
valueTypeId + ".json");
+                    if (jsonSchema == null) {
+                        
validationMessages.add(buildValidationMessage(CustomErrorMessageType.of("value-schema-not-found",
 new MessageFormat("{0} : Couldn''t find schema type with id={1}")), at, 
"https://unomi.apache.org/schemas/json/values/"; + valueTypeId + ".json"));
+                    } else {
+                        Set<ValidationMessage> propertyValidationMessages = 
jsonSchema.validate(node.get(fieldName));
+                        if (propertyValidationMessages != null) {
+                            
validationMessages.addAll(propertyValidationMessages);
+                        }
+                    }
+                }
+            }
+            return validationMessages;
+        }
+
+        private PropertyType getPropertyType(String fieldName) {
+            Map<String, PropertyType> propertyTypes = new HashMap<>();
+            if (schemaNode.size() > 0) {
+                for (Iterator<JsonNode> it = schemaNode.iterator(); 
it.hasNext(); ) {
+                    JsonNode target = it.next();
+                    if ("_all".equals(target.asText())) {
+                        return profileService.getPropertyType(fieldName);
+                    } else {
+                        Collection<PropertyType> targetPropertyTypes = 
profileService.getTargetPropertyTypes(target.asText());
+                        targetPropertyTypes.stream().map(propertyType -> 
propertyTypes.put(propertyType.getItemId(), propertyType));
+                    }
+                }
+                return propertyTypes.get(fieldName);
+            } else {
+                return profileService.getPropertyType(fieldName);
+            }
+        }
+    }
+
+    public PropertyTypeKeyword(ProfileService profileService, 
SchemaRegistryImpl schemaRegistry) {
+        super("propertyTypes");
+        this.profileService = profileService;
+        this.schemaRegistry = schemaRegistry;
+    }
+
+    @Override
+    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, 
JsonSchema parentSchema, ValidationContext validationContext) throws 
JsonSchemaException, Exception {
+        return new PropertyTypeJsonValidator(this.getValue(), schemaPath, 
schemaNode, parentSchema, validationContext, profileService, schemaRegistry);
+    }
+}
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaRegistryImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaRegistryImpl.java
new file mode 100644
index 0000000..ee13aab
--- /dev/null
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/schemas/SchemaRegistryImpl.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.unomi.services.impl.schemas;
+
+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.lang3.StringUtils;
+import org.apache.unomi.api.SchemaType;
+import org.apache.unomi.api.services.ProfileService;
+import org.apache.unomi.api.services.SchemaRegistry;
+import org.apache.unomi.persistence.spi.CustomObjectMapper;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URL;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class SchemaRegistryImpl implements SchemaRegistry, 
SynchronousBundleListener {
+
+    private static final String URI = 
"https://json-schema.org/draft/2019-09/schema";;
+
+    private static final Logger logger = 
LoggerFactory.getLogger(SchemaRegistryImpl.class.getName());
+
+    private final Map<Long, List<SchemaType>> schemaTypesByBundle = new 
HashMap<>();
+    private final Map<String, SchemaType> schemaTypesById = new HashMap<>();
+
+    private final Map<String, JsonSchema> jsonSchemasById = new 
LinkedHashMap<>();
+
+    private final Map<String, Long> bundleIdBySchemaId = new HashMap<>();
+
+    private BundleContext bundleContext;
+
+    private ProfileService profileService;
+
+    public void bundleChanged(BundleEvent event) {
+        switch (event.getType()) {
+            case BundleEvent.STARTED:
+                processBundleStartup(event.getBundle().getBundleContext());
+                break;
+            case BundleEvent.STOPPING:
+                processBundleStop(event.getBundle().getBundleContext());
+                break;
+        }
+    }
+
+    public void init() {
+        processBundleStartup(bundleContext);
+
+        // process already started bundles
+        for (Bundle bundle : bundleContext.getBundles()) {
+            if (bundle.getBundleContext() != null && bundle.getBundleId() != 
bundleContext.getBundle().getBundleId()) {
+                processBundleStartup(bundle.getBundleContext());
+            }
+        }
+
+        bundleContext.addBundleListener(this);
+        logger.info("Schema registry initialized.");
+    }
+
+    public void destroy() {
+        bundleContext.removeBundleListener(this);
+        logger.info("Schema registry shutdown.");
+    }
+
+    public void setBundleContext(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+    }
+
+    public void setProfileService(ProfileService profileService) {
+        this.profileService = profileService;
+    }
+
+    public boolean isValid(Object object, String schemaId) {
+        JsonSchema jsonSchema = jsonSchemasById.get(schemaId);
+        if (jsonSchema != null) {
+            try {
+                // this is a workaround to get the JsonNode from the object, 
maybe there is a better way (more efficient) to do this ?
+                StringWriter stringWriter = new StringWriter();
+                CustomObjectMapper.getObjectMapper().writeValue(stringWriter, 
object);
+                JsonNode jsonNode = 
CustomObjectMapper.getObjectMapper().readTree(stringWriter.toString());
+                Set<ValidationMessage> validationMessages = 
jsonSchema.validate(jsonNode);
+                if (validationMessages == null || 
validationMessages.isEmpty()) {
+                    return true;
+                }
+                for (ValidationMessage validationMessage : validationMessages) 
{
+                    logger.error("Error validating object against schema {}: 
{}", schemaId, validationMessage);
+                }
+                return false;
+            } catch (IOException e) {
+                logger.error("Error validating object with schema {}", 
schemaId, e);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public List<SchemaType> getTargetSchemas(String target) {
+        return schemaTypesById.values().stream().filter(schemaType -> 
schemaType.getTarget().equals(target)).collect(Collectors.toList());
+    }
+
+    @Override
+    public SchemaType getSchema(String schemaId) {
+        return schemaTypesById.get(schemaId);
+    }
+
+    private void loadPredefinedSchemas(BundleContext bundleContext) {
+        Enumeration<URL> predefinedSchemas = 
bundleContext.getBundle().findEntries("META-INF/cxs/schemas", "*.json", true);
+        if (predefinedSchemas == null) {
+            return;
+        }
+
+        ObjectMapper objectMapper = new ObjectMapper();
+
+        List<SchemaType> schemaTypes = 
this.schemaTypesByBundle.get(bundleContext.getBundle().getBundleId());
+
+        while (predefinedSchemas.hasMoreElements()) {
+            URL predefinedSchemaURL = predefinedSchemas.nextElement();
+            logger.debug("Found predefined JSON schema at " + 
predefinedSchemaURL + ", loading... ");
+
+            try {
+                JsonMetaSchema jsonMetaSchema = JsonMetaSchema.builder(URI, 
JsonMetaSchema.getV201909())
+                        .addKeyword(new PropertyTypeKeyword(profileService, 
this)).build();
+                JsonSchemaFactory jsonSchemaFactory = 
JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909))
+                        .addMetaSchema(jsonMetaSchema)
+                        .defaultMetaSchemaURI(URI)
+                        .uriFetcher(getBundleUriFetcher(bundleContext), 
"https", "http").build();
+                InputStream schemaInputStream = 
predefinedSchemaURL.openStream();
+                JsonSchema jsonSchema = 
jsonSchemaFactory.getSchema(schemaInputStream);
+                String schemaId = 
jsonSchema.getSchemaNode().get("$id").asText();
+                jsonSchemasById.put(schemaId, jsonSchema);
+                bundleIdBySchemaId.put(schemaId, 
bundleContext.getBundle().getBundleId());
+                SchemaType schemaType = new SchemaType();
+                
schemaType.setPluginId(bundleContext.getBundle().getBundleId());
+                schemaType.setSchemaId(schemaId);
+                Map<String, Object> schemaTree = (Map<String, Object>) 
objectMapper.treeToValue(jsonSchema.getSchemaNode(), Map.class);
+                schemaType.setSchemaTree(schemaTree);
+                String[] splitPath = predefinedSchemaURL.getPath().split("/");
+                if (splitPath.length > 5) {
+                    String target = splitPath[4];
+                    if (StringUtils.isNotBlank(target)) {
+                        schemaType.setTarget(target);
+                    }
+                }
+                schemaTypes.add(schemaType);
+                schemaTypesById.put(schemaId, schemaType);
+                schemaInputStream.close();
+            } catch (Exception e) {
+                logger.error("Error while loading schema definition " + 
predefinedSchemaURL, e);
+            }
+        }
+
+    }
+
+    private URIFetcher getBundleUriFetcher(BundleContext bundleContext) {
+        return uri -> {
+            logger.debug("Fetching schema {}", uri);
+            Long bundleId = bundleIdBySchemaId.get(uri.toString());
+            if (bundleId == null) {
+                logger.error("Couldn't find bundle for schema {}", uri);
+                return null;
+            }
+            String uriPath = uri.getPath().substring("/schemas/json".length());
+            URL schemaURL = 
bundleContext.getBundle(bundleId).getResource("META-INF/cxs/schemas" + uriPath);
+            if (schemaURL != null) {
+                return schemaURL.openStream();
+            } else {
+                logger.error("Couldn't find resource {} in bundle {}", 
"META-INF/cxs/schemas" + uriPath, bundleId);
+                return null;
+            }
+        };
+    }
+
+    private void processBundleStartup(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        schemaTypesByBundle.put(bundleContext.getBundle().getBundleId(), new 
ArrayList<>());
+        loadPredefinedSchemas(bundleContext);
+    }
+
+    private void processBundleStop(BundleContext bundleContext) {
+        if (bundleContext == null) {
+            return;
+        }
+        List<SchemaType> schemaTypes = 
schemaTypesByBundle.remove(bundleContext.getBundle().getBundleId());
+        if (schemaTypes != null) {
+            for (SchemaType schemaType : schemaTypes) {
+                jsonSchemasById.remove(schemaType.getSchemaId());
+                bundleIdBySchemaId.remove(schemaType.getSchemaId());
+                schemaTypesById.remove(schemaType.getSchemaId());
+            }
+        }
+    }
+
+    protected JsonSchema getJsonSchema(String schemaId) {
+        return jsonSchemasById.get(schemaId);
+    }
+
+}
diff --git a/services/src/main/resources/META-INF/cxs/schemas/condition.json 
b/services/src/main/resources/META-INF/cxs/schemas/condition.json
new file mode 100644
index 0000000..000ee1d
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/condition.json
@@ -0,0 +1,15 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/condition.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Condition",
+  "type": "object",
+  "properties" : {
+    "conditionTypeId" : {
+      "type" : "string"
+    },
+    "parameterValues" : {
+      "type" : "object",
+      "maxProperties": 50
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/conditiontype.json 
b/services/src/main/resources/META-INF/cxs/schemas/conditiontype.json
new file mode 100644
index 0000000..85e78c8
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/conditiontype.json
@@ -0,0 +1,22 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/conditiontype.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "ConditionType",
+  "type": "object",
+  "properties" : {
+    "conditionEvaluator" : {
+      "type" : "string"
+    },
+    "queryBuilder" : {
+      "type" : "string"
+    },
+    "parentCondition" : {
+      "$ref" : "https://unomi.apache.org/schemas/json/condition.json";
+    },
+    "parameters" : {
+      "type" : "object",
+      "additionalProperties": { "$ref" : 
"https://unomi.apache.org/schemas/json/parameter.json"; },
+      "maxProperties": 50
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/consent.json 
b/services/src/main/resources/META-INF/cxs/schemas/consent.json
new file mode 100644
index 0000000..c1eb229
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/consent.json
@@ -0,0 +1,25 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/consent.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Consent",
+  "type": "object",
+  "properties" : {
+    "scope" : {
+      "type" : "string"
+    },
+    "typeIdentifier" : {
+      "type" : "string"
+    },
+    "status" : {
+      "enum" : [ "GRANTED", "DENIED", "REVOKED" ]
+    },
+    "statusDate" : {
+      "type" : "string",
+      "format" : "date-time"
+    },
+    "revokeDate" : {
+      "type" : "string",
+      "format" : "date-time"
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/consentType.json 
b/services/src/main/resources/META-INF/cxs/schemas/consentType.json
new file mode 100644
index 0000000..19f4bef
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/consentType.json
@@ -0,0 +1,20 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/consentType.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "ConsentType",
+  "type": "object",
+  "properties" : {
+    "typeIdentifier" : {
+      "type" : "string"
+    },
+    "activated" : {
+      "type" : [ "null", "boolean" ]
+    },
+    "title" : {
+      "type" : ["null", "string"]
+    },
+    "description" : {
+      "type" : ["null", "string"]
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/customitem.json 
b/services/src/main/resources/META-INF/cxs/schemas/customitem.json
new file mode 100644
index 0000000..0c59480
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/customitem.json
@@ -0,0 +1,20 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/customitem.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "CustomItem",
+  "type": "object",
+  "allOf": [
+    {
+      "$ref": "https://unomi.apache.org/schemas/json/item.json";
+    }
+  ],
+  "properties": {
+    "customItemType" : {
+      "type" : "string"
+    },
+    "properties" : {
+      "type" : "object",
+      "maxProperties": 50
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/customitems/page.json 
b/services/src/main/resources/META-INF/cxs/schemas/customitems/page.json
new file mode 100644
index 0000000..252a51d
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/customitems/page.json
@@ -0,0 +1,66 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/customitems/page.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "PageCustomItem",
+  "type": "object",
+  "allOf": [
+    {
+      "$ref": "https://unomi.apache.org/schemas/json/customitem.json";
+    }
+  ],
+  "properties": {
+    "pageInfo": {
+      "type" : "object",
+      "properties" : {
+        "templateName": {
+          "type" : "string"
+        },
+        "language": {
+          "type" : "string",
+          "maxLength": 5
+        },
+        "destinationURL": {
+          "type" : "string",
+          "format" : "uri"
+        },
+        "categories": {
+          "type" : "array",
+          "items" : { "type" : "string"}
+        },
+        "pageID": {
+          "type" : "string",
+          "pattern" : "^(\\w|[-_@\\.]){0,50}$"
+        },
+        "nodeType": {
+          "type" : "string"
+        },
+        "pagePath": {
+          "type" : "string"
+        },
+        "pageName": {
+          "type" : "string"
+        },
+        "referringURL": {
+          "type" : "string",
+          "format" : "uri"
+        },
+        "tags": {
+          "type" : "array",
+          "items" : { "type" : "string" }
+        },
+        "isContentTemplate": {
+          "type" : "boolean"
+        }
+      }
+    },
+    "attributes": {
+      "type" : ["null", "object"]
+    },
+    "consentTypes": {
+      "type" : ["null", "array"],
+      "items" : {
+        "$href" : "https://unomi.apache.org/schemas/json/consentType.json";
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/customitems/site.json 
b/services/src/main/resources/META-INF/cxs/schemas/customitems/site.json
new file mode 100644
index 0000000..b6de454
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/customitems/site.json
@@ -0,0 +1,13 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/customitems/site.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "SiteCustomItem",
+  "type": "object",
+  "allOf": [
+    {
+      "$ref": "https://unomi.apache.org/schemas/json/customitem.json";
+    }
+  ],
+  "properties": {
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/event.json 
b/services/src/main/resources/META-INF/cxs/schemas/event.json
new file mode 100644
index 0000000..a0e71b8
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/event.json
@@ -0,0 +1,29 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/event.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Event",
+  "type": "object",
+  "allOf": [{ "$ref": 
"https://unomi.apache.org/schemas/json/timestampeditem.json"; }],
+  "properties" : {
+    "eventType" : {
+      "type" : "string",
+      "pattern" : "^(\\w|[-_@\\.]){0,50}$"
+    },
+    "profileId" : {
+      "type" : [ "null", "string"],
+      "pattern" : "^(\\w|[-_@\\.]){0,50}$"
+    },
+    "sessionId" : {
+      "type" : [ "null", "string"],
+      "pattern" : "^(\\w|[-_@\\.]){0,50}$"
+    },
+    "scope" : {
+      "type" : [ "null", "string"],
+      "pattern" : "^(\\w|[-_@\\.]){0,50}$"
+    },
+    "sourceId" : {
+      "type" : [ "null", "string"],
+      "pattern" : "^(\\w|[-_@\\.]){0,50}$"
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/events/modifyConsent.json 
b/services/src/main/resources/META-INF/cxs/schemas/events/modifyConsent.json
new file mode 100644
index 0000000..e203d47
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/events/modifyConsent.json
@@ -0,0 +1,20 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/events/modifyConsent.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "ModifyConsentEvent",
+  "type": "object",
+  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event.json"; }],
+  "properties" : {
+    "properties" : {
+      "consent" : {
+        "$ref" : "https://unomi.apache.org/schemas/json/consent.json";
+      }
+    },
+    "source" : {
+        "$ref" : "https://unomi.apache.org/schemas/json/customitems/page.json";
+    },
+    "target" : {
+        "$ref" : "https://unomi.apache.org/schemas/json/customitem.json";
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/events/view.json 
b/services/src/main/resources/META-INF/cxs/schemas/events/view.json
new file mode 100644
index 0000000..5ea14a9
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/events/view.json
@@ -0,0 +1,20 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/events/view.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "ViewEvent",
+  "type": "object",
+  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event.json"; }],
+  "properties" : {
+    "properties" : {
+      "type" : "object",
+      "propertyTypes" : [ "events" ],
+      "maxProperties": 50
+    },
+    "source" : {
+      "$ref" : "https://unomi.apache.org/schemas/json/customitems/site.json";
+    },
+    "target" : {
+      "$ref" : "https://unomi.apache.org/schemas/json/customitems/page.json";
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/goal.json 
b/services/src/main/resources/META-INF/cxs/schemas/goal.json
new file mode 100644
index 0000000..ee7c715
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/goal.json
@@ -0,0 +1,18 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/goal.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Goal",
+  "type": "object",
+  "allOf": [{ "$ref": 
"https://unomi.apache.org/schemas/json/metadataitem.json"; }],
+  "properties" : {
+    "startEvent" : {
+      "$ref" : "https://unomi.apache.org/schemas/json/condition.json";
+    },
+    "targetEvent" : {
+      "$ref" : "https://unomi.apache.org/schemas/json/condition.json";
+    },
+    "campaignId" : {
+      "type" : "string"
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/item.json 
b/services/src/main/resources/META-INF/cxs/schemas/item.json
new file mode 100644
index 0000000..3a4f148
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/item.json
@@ -0,0 +1,31 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/item.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Item",
+  "type": "object",
+  "properties" : {
+    "itemId" : {
+      "type" : "string",
+      "pattern" : "^(\\w|[-_@\\.]){0,50}$",
+      "description" : "The identifier for the item"
+    },
+    "itemType" : {
+      "type" : "string",
+      "description" : "The type for the item"
+    },
+    "scope" : {
+      "type" : ["null","string"],
+      "description" : "The item's scope"
+    },
+    "version" : {
+      "type" : ["null","integer"],
+      "minimum" : 0,
+      "description" : "The item's version number"
+    },
+    "systemMetadata" : {
+      "type" : ["null","object"],
+      "description" : "Any system metadata for the item",
+      "maxProperties" : 50
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/metadata.json 
b/services/src/main/resources/META-INF/cxs/schemas/metadata.json
new file mode 100644
index 0000000..2116bba
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/metadata.json
@@ -0,0 +1,44 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/metadata.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Metadata",
+  "type": "object",
+  "properties" : {
+    "id" : {
+      "type" : "string"
+    },
+    "name" : {
+      "type" : "string"
+    },
+    "description" : {
+      "type" : "string"
+    },
+    "scope" : {
+      "type" : "string"
+    },
+    "tags" : {
+      "type" : "array",
+      "items" : {
+        "type" : "string"
+      }
+    },
+    "systemTags" : {
+      "type" : "array",
+      "items" : {
+        "type" : "string"
+      }
+    },
+    "enabled" : {
+      "type" : "boolean"
+    },
+    "missingPlugins" : {
+      "type" : "boolean"
+    },
+    "hidden" : {
+      "type" : "boolean"
+    },
+    "readOnly" : {
+      "type" : "boolean"
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/metadataitem.json 
b/services/src/main/resources/META-INF/cxs/schemas/metadataitem.json
new file mode 100644
index 0000000..325dafe
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/metadataitem.json
@@ -0,0 +1,14 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/metadataitem.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "MetadataItem",
+  "type": "object",
+  "allOf": [
+    { "$ref": "https://unomi.apache.org/schemas/json/item.json"; }
+  ],
+  "properties" : {
+    "metadata" : {
+      "$ref" : "https://unomi.apache.org/schemas/json/metadata.json";
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/parameter.json 
b/services/src/main/resources/META-INF/cxs/schemas/parameter.json
new file mode 100644
index 0000000..abea6ae
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/parameter.json
@@ -0,0 +1,20 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/parameter.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Parameter",
+  "type": "object",
+  "properties" : {
+    "id" : {
+      "type" : "string"
+    },
+    "type" : {
+      "type" : "string"
+    },
+    "multivalued" : {
+      "type" : "boolean"
+    },
+    "defaultValue" : {
+      "type" : "string"
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/profile.json 
b/services/src/main/resources/META-INF/cxs/schemas/profile.json
new file mode 100644
index 0000000..2381b81
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/profile.json
@@ -0,0 +1,41 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/profile.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Profile",
+  "type": "object",
+  "allOf": [
+    {
+      "$ref": "https://unomi.apache.org/schemas/json/item.json";
+    }
+  ],
+  "properties": {
+    "properties" : {
+      "type" : "object",
+      "propertyTypes" : [ "profile" ],
+      "maxProperties": 50
+    },
+    "systemProperties" : {
+      "type" : "object",
+      "maxProperties": 50
+    },
+    "segments" : {
+      "type" : "array",
+      "items" : {
+        "type" : "string"
+      }
+    },
+    "scores" : {
+      "type" : "object",
+      "additionalProperties": { "type": "integer" },
+      "maxProperties": 100
+    },
+    "mergedWith" : {
+      "type" : "string"
+    },
+    "consents" : {
+      "type" : "object",
+      "additionalProperties": { "$ref" : 
"https://unomi.apache.org/schemas/json/consent.json"; },
+      "maxProperties": 50
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/session.json 
b/services/src/main/resources/META-INF/cxs/schemas/session.json
new file mode 100644
index 0000000..b2534b4
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/session.json
@@ -0,0 +1,41 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/session.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Session",
+  "type": "object",
+  "allOf": [
+    {
+      "$ref": "https://unomi.apache.org/schemas/json/timestampeditem.json";
+    }
+  ],
+  "properties": {
+    "profileId" : {
+      "type" : "string"
+    },
+    "profile" : {
+      "$ref" : "https://unomi.apache.org/schemas/json/profile.json";
+    },
+    "properties" : {
+      "type" : "object",
+      "maxProperties": 50
+    },
+    "systemProperties" : {
+      "type" : "object",
+      "maxProperties": 50
+    },
+    "timeStamp" : {
+      "type" : "string",
+      "format" : "date-time"
+    },
+    "lastEventDate" : {
+      "type" : "string",
+      "format" : "date-time"
+    },
+    "size" : {
+      "type" : "integer"
+    },
+    "duration" : {
+      "type" : "integer"
+    }
+  }
+}
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json 
b/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
new file mode 100644
index 0000000..bee6c8f
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/timestampeditem.json
@@ -0,0 +1,13 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/timestampeditem.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "TimestampedItem",
+  "type": "object",
+  "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/item.json"; }],
+  "properties" : {
+    "timeStamp" : {
+      "type" : ["null","string"],
+      "format" : "date-time"
+    }
+  }
+}
\ No newline at end of file
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/values/boolean.json 
b/services/src/main/resources/META-INF/cxs/schemas/values/boolean.json
new file mode 100644
index 0000000..8a3ed95
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/values/boolean.json
@@ -0,0 +1,6 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/values/boolean.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Boolean",
+  "type": "boolean"
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/date.json 
b/services/src/main/resources/META-INF/cxs/schemas/values/date.json
new file mode 100644
index 0000000..7a39489
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/values/date.json
@@ -0,0 +1,7 @@
+{
+    "$id": "https://unomi.apache.org/schemas/json/values/date.json";,
+    "$schema": "https://json-schema.org/draft/2019-09/schema";,
+    "title": "Date",
+    "type": "string",
+    "format" : "date-time"
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/email.json 
b/services/src/main/resources/META-INF/cxs/schemas/values/email.json
new file mode 100644
index 0000000..705f2f6
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/values/email.json
@@ -0,0 +1,7 @@
+{
+    "$id": "https://unomi.apache.org/schemas/json/values/email.json";,
+    "$schema": "https://json-schema.org/draft/2019-09/schema";,
+    "title": "Email",
+    "type": "string",
+    "format" : "email"
+}
\ No newline at end of file
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/values/integer.json 
b/services/src/main/resources/META-INF/cxs/schemas/values/integer.json
new file mode 100644
index 0000000..f5cb7b9
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/values/integer.json
@@ -0,0 +1,6 @@
+{
+    "$id": "https://unomi.apache.org/schemas/json/values/integer.json";,
+    "$schema": "https://json-schema.org/draft/2019-09/schema";,
+    "title": "Integer",
+    "type": "integer"
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/long.json 
b/services/src/main/resources/META-INF/cxs/schemas/values/long.json
new file mode 100644
index 0000000..769c78a
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/values/long.json
@@ -0,0 +1,6 @@
+{
+  "$id": "https://unomi.apache.org/schemas/json/values/long.json";,
+  "$schema": "https://json-schema.org/draft/2019-09/schema";,
+  "title": "Long",
+  "type": "integer"
+}
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/cxs/schemas/values/set.json 
b/services/src/main/resources/META-INF/cxs/schemas/values/set.json
new file mode 100644
index 0000000..34b8611
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/values/set.json
@@ -0,0 +1,7 @@
+{
+    "$id": "https://unomi.apache.org/schemas/json/values/set.json";,
+    "$schema": "https://json-schema.org/draft/2019-09/schema";,
+    "title": "Set",
+    "type": "object",
+    "maxProperties": 50
+}
\ No newline at end of file
diff --git 
a/services/src/main/resources/META-INF/cxs/schemas/values/string.json 
b/services/src/main/resources/META-INF/cxs/schemas/values/string.json
new file mode 100644
index 0000000..6d5e0e8
--- /dev/null
+++ b/services/src/main/resources/META-INF/cxs/schemas/values/string.json
@@ -0,0 +1,6 @@
+{
+    "$id": "https://unomi.apache.org/schemas/json/values/string.json";,
+    "$schema": "https://json-schema.org/draft/2019-09/schema";,
+    "title": "String",
+    "type": "string"
+}
\ No newline at end of file
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml 
b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 771c740..ed8b398 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -104,12 +104,20 @@
     </bean>
     <service id="eventTypeRegistry" ref="eventTypeRegistryImpl" 
interface="org.apache.unomi.api.services.EventTypeRegistry"/>
 
+    <bean id="schemaRegistryImpl" 
class="org.apache.unomi.services.impl.schemas.SchemaRegistryImpl" 
init-method="init"
+          destroy-method="destroy">
+        <property name="bundleContext" ref="blueprintBundleContext"/>
+        <property name="profileService" ref="profileServiceImpl" />
+    </bean>
+    <service id="schemaRegistry" ref="schemaRegistryImpl" 
interface="org.apache.unomi.api.services.SchemaRegistry"/>
+
     <bean id="eventServiceImpl" 
class="org.apache.unomi.services.impl.events.EventServiceImpl">
         <property name="persistenceService" ref="persistenceService"/>
         <property name="definitionsService" ref="definitionsServiceImpl"/>
         <property name="sourceService" ref="sourceServiceImpl"/>
         <property name="bundleContext" ref="blueprintBundleContext"/>
         <property name="eventTypeRegistry" ref="eventTypeRegistryImpl"/>
+        <property name="schemaRegistry" ref="schemaRegistryImpl" />
         <property name="predefinedEventTypeIds">
             <set>
                 <value>view</value>

Reply via email to