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

bdelacretaz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git

commit beb26ecacb2c3de2c8194c2b8c52a257f6451efd
Author: Bertrand Delacretaz <[email protected]>
AuthorDate: Mon Nov 8 16:40:25 2021 +0100

    Initial schema storage
---
 json-store/README.md                               | 31 +++++++++---
 json-store/example-data/example-schema.json        | 23 +++++++++
 json-store/pom.xml                                 | 12 +++++
 .../org/apache/sling/jsonstore/api/JsonStore.java  |  7 +++
 .../sling/jsonstore/api/JsonStoreConstants.java    |  6 +++
 .../apache/sling/jsonstore/impl/JsonStoreImpl.java | 58 ++++++++++++++++++----
 ...{SitesParentServlet.java => SchemaServlet.java} | 34 +++++++++----
 .../{SitesParentServlet.java => SitesServlet.java} | 14 +++---
 8 files changed, 154 insertions(+), 31 deletions(-)

diff --git a/json-store/README.md b/json-store/README.md
index 3000e3f..df76f5e 100644
--- a/json-store/README.md
+++ b/json-store/README.md
@@ -1,18 +1,27 @@
 # Apache Sling JSON Store
 
-TODO: Explain more, a content store using Sling that introduces that
-stores content as JSON blobs validated by JSON schemas.
+This module stores content as JSON blobs validated by JSON schemas.
 
 ## Storage Model
 * A _site_ is the root of a subtree of content that belongs together.
 * Below the _site_ resource:
-** The _schema_ subtree stores JSON schema keyed by resource type
-** The _elements_ subtree stores validated reusable elements of content
-** The _content_ subtree stores the actual validated content: pages etc.
+  * The _schema_ subtree stores JSON schema keyed by resource type
+  * The _elements_ subtree stores validated reusable elements of content
+  * The _content_ subtree stores the actual validated content: pages etc.
 
 ## How to test this
 
-Install this bundle and create a "JSON store root" node:
+Install the following bundles, which you can get by running
+` mvn dependency:copy-dependencies` in this folder:
+
+    jackson-core-2.13.0.jar
+    jackson-annotations-2.13.0.jar
+    jackson-databind-2.13.0.jar
+    json-schema-validator-1.0.63.jar
+
+Install this bundle and verify that it is active.
+
+Create a "JSON store root" node:
 
     curl -u admin:admin -F sling:resourceType=sling/jsonstore/root 
http://localhost:8080/content/sites
 
@@ -21,4 +30,12 @@ POST to that resource to create a test site:
     curl -u admin:admin -F path=example.com http://localhost:8080/content/sites
 
 This creates the required structure under "sites/example.com" to store JSON 
schemas,
-elements and content.
\ No newline at end of file
+elements and content.
+
+POST a schema as follows:
+
+    curl -u admin:admin -Fjson=@example-data/example-schema.json 
-FresourceType=example http://localhost:8080/content/sites/example.com/schema
+
+And retrieve it as follows:
+
+    curl -u admin:admin 
http://localhost:8080/content/sites/example.com/schema/example.tidy.5.json
\ No newline at end of file
diff --git a/json-store/example-data/example-schema.json 
b/json-store/example-data/example-schema.json
new file mode 100644
index 0000000..e0e1737
--- /dev/null
+++ b/json-store/example-data/example-schema.json
@@ -0,0 +1,23 @@
+{
+    "$schema": "https://json-schema.org/draft/2019-09/schema";,
+    "$id": "https://example.com/product.schema.json";,
+    "title": "Product",
+    "description": "A product from Acme's catalog, example from 
http://json-schema.org/learn/getting-started-step-by-step.html";,
+    "type": "object",
+    "properties": {
+      "productId": {
+        "description": "The unique identifier for a product",
+        "type": "integer"
+      },
+      "productName": {
+        "description": "Name of the product",
+        "type": "string"
+      },
+      "price": {
+        "description": "The price of the product",
+        "type": "number",
+        "exclusiveMinimum": 0
+      }
+    },
+    "required": [ "productId", "productName", "price" ]
+  }
\ No newline at end of file
diff --git a/json-store/pom.xml b/json-store/pom.xml
index 950c14f..16935c1 100644
--- a/json-store/pom.xml
+++ b/json-store/pom.xml
@@ -75,6 +75,18 @@
       <scope>provided</scope>
     </dependency>
     <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <version>2.13.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.networknt</groupId>
+      <artifactId>json-schema-validator</artifactId>
+      <version>1.0.63</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
       <scope>provided</scope>
diff --git 
a/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStore.java 
b/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStore.java
index 060af7f..d544904 100644
--- a/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStore.java
+++ b/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStore.java
@@ -19,10 +19,17 @@
 
  package org.apache.sling.jsonstore.api;
 
+import java.io.IOException;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 
 public interface JsonStore {
     /** Create a Site and return its root */
     Resource createSite(Resource parent, String relativeSitePath) throws 
PersistenceException;
+
+    /** Create or update a Schema */
+    Resource createOrUpdateSchema(Resource parent, String resourceType, 
JsonNode schema) throws PersistenceException, IOException;
 }
diff --git 
a/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStoreConstants.java
 
b/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStoreConstants.java
index 63e781f..9b7d70f 100644
--- 
a/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStoreConstants.java
+++ 
b/json-store/src/main/java/org/apache/sling/jsonstore/api/JsonStoreConstants.java
@@ -23,6 +23,7 @@ public class JsonStoreConstants {
     public static final String STORE_ROOT_RESOURCE_TYPE = 
"sling/jsonstore/root";
     public static final String SITE_ROOT_RESOURCE_TYPE = 
"sling/jsonstore/site";
     public static final String SCHEMA_ROOT_RESOURCE_TYPE = 
"sling/jsonstore/schema/root";
+    public static final String SCHEMA_RESOURCE_TYPE = "sling/jsonstore/schema";
     public static final String ELEMENTS_ROOT_RESOURCE_TYPE = 
"sling/jsonstore/elements/root";
     public static final String CONTENT_ROOT_RESOURCE_TYPE = 
"sling/jsonstore/content/root";
 
@@ -30,6 +31,11 @@ public class JsonStoreConstants {
     public static final String ELEMENTS_ROOT_NAME = "elements";
     public static final String CONTENT_ROOT_NAME = "content";
 
+    public static final String JSON_BLOB_PROPERTY= "json";
+
     public static final String PARAM_PATH = "path";
+    public static final String PARAM_RESOURCE_TYPE = "resourceType";
+    public static final String PARAM_JSON = "json";
+
     private JsonStoreConstants() {}
 }
diff --git 
a/json-store/src/main/java/org/apache/sling/jsonstore/impl/JsonStoreImpl.java 
b/json-store/src/main/java/org/apache/sling/jsonstore/impl/JsonStoreImpl.java
index 4bf74a1..6f3a839 100644
--- 
a/json-store/src/main/java/org/apache/sling/jsonstore/impl/JsonStoreImpl.java
+++ 
b/json-store/src/main/java/org/apache/sling/jsonstore/impl/JsonStoreImpl.java
@@ -17,33 +17,73 @@
  * under the License.
  */
 
- package org.apache.sling.jsonstore.impl;
+package org.apache.sling.jsonstore.impl;
 
+import com.networknt.schema.SpecVersion;
 import static org.apache.sling.jsonstore.api.JsonStoreConstants.*;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.JsonSchemaFactory;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.jsonstore.api.JsonStore;
 import org.osgi.service.component.annotations.Component;
 
 @Component(service=JsonStore.class)
 public class JsonStoreImpl implements JsonStore {
-    
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
     public Resource createSite(Resource parent, String name) throws 
PersistenceException {
-        final Resource site = createChild(parent, name, 
SITE_ROOT_RESOURCE_TYPE);
-        createChild(site, SCHEMA_ROOT_NAME, SCHEMA_ROOT_RESOURCE_TYPE);
-        createChild(site, ELEMENTS_ROOT_NAME, ELEMENTS_ROOT_RESOURCE_TYPE);
-        createChild(site, CONTENT_ROOT_NAME, CONTENT_ROOT_RESOURCE_TYPE);
-        site.getResourceResolver().commit();
+        final ResourceResolver rr = parent.getResourceResolver();
+        final Resource site = rr.create(parent, name, 
getProps(SITE_ROOT_RESOURCE_TYPE));
+        rr.create(site, SCHEMA_ROOT_NAME, getProps(SCHEMA_ROOT_RESOURCE_TYPE));
+        rr.create(site, ELEMENTS_ROOT_NAME, 
getProps(ELEMENTS_ROOT_RESOURCE_TYPE));
+        rr.create(site, CONTENT_ROOT_NAME, 
getProps(CONTENT_ROOT_RESOURCE_TYPE));
+        rr.commit();
         return site;
     }
 
-    private Resource createChild(Resource parent, String name, String 
resourceType) throws PersistenceException {
+    private Map<String, Object> getProps(String resourceType) {
         final Map<String, Object> props = new HashMap<>();
         props.put("sling:resourceType", resourceType);
-        return parent.getResourceResolver().create(parent, name, props);
+        return props;
+    }
+
+    @Override
+    public Resource createOrUpdateSchema(Resource parent, String resourceType, 
JsonNode schema) throws PersistenceException,IOException {
+
+        // Validate the schema
+        final JsonSchemaFactory factory = 
JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909);
+        factory.getSchema(schema);
+
+        final ResourceResolver rr = parent.getResourceResolver();
+        final String path = buildSchemaPath(parent, resourceType);
+
+        // TODO stream this instead?
+        final String json = mapper.writeValueAsString(schema);
+        Resource r = rr.getResource(parent, path);
+        if(r == null) {
+            final Map<String, Object> props = getProps(SCHEMA_RESOURCE_TYPE);
+            props.put(JSON_BLOB_PROPERTY, json);
+            r = rr.create(parent, resourceType, props);
+        } else {
+            final ModifiableValueMap vm = r.adaptTo(ModifiableValueMap.class);
+            vm.put(JSON_BLOB_PROPERTY, json);
+        }
+        rr.commit();
+        return r;
+    }
+
+    static String buildSchemaPath(Resource parent, String resourceType) {
+        return String.format("%s/%s", parent.getPath(), resourceType);
     }
 }
diff --git 
a/json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesParentServlet.java
 b/json-store/src/main/java/org/apache/sling/jsonstore/impl/SchemaServlet.java
similarity index 57%
copy from 
json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesParentServlet.java
copy to 
json-store/src/main/java/org/apache/sling/jsonstore/impl/SchemaServlet.java
index 961b03b..47de14d 100644
--- 
a/json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesParentServlet.java
+++ 
b/json-store/src/main/java/org/apache/sling/jsonstore/impl/SchemaServlet.java
@@ -17,41 +17,57 @@
  * under the License.
  */
 
- package org.apache.sling.jsonstore.impl;
+package org.apache.sling.jsonstore.impl;
+
+import static org.apache.sling.jsonstore.api.JsonStoreConstants.*;
 
 import java.io.IOException;
 
 import javax.servlet.Servlet;
 import javax.servlet.http.HttpServletResponse;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.request.RequestParameter;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.jsonstore.api.JsonStore;
-import org.apache.sling.jsonstore.api.JsonStoreConstants;
 import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
 @Component(service = Servlet.class)
 @SlingServletResourceTypes(
-    resourceTypes=JsonStoreConstants.STORE_ROOT_RESOURCE_TYPE,
+    resourceTypes=SCHEMA_ROOT_RESOURCE_TYPE,
     methods= "POST"
 )
-public class SitesParentServlet extends SlingAllMethodsServlet {
+public class SchemaServlet extends SlingAllMethodsServlet {
     @Reference
     private JsonStore store;
 
     @Override
     public void doPost(SlingHttpServletRequest request, 
SlingHttpServletResponse response) throws IOException {
-        final String relativeSitePath = 
request.getParameter(JsonStoreConstants.PARAM_PATH);
-        if(relativeSitePath == null) {
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
String.format("Missing required parameter '%s'"));
+        final String resourceType = request.getParameter(PARAM_RESOURCE_TYPE);
+        if(resourceType == null) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
String.format("Missing required parameter '%s'", PARAM_RESOURCE_TYPE));
+            return;
         }
-        final Resource result = store.createSite(request.getResource(), 
relativeSitePath);
+
+        final RequestParameter json = request.getRequestParameter(PARAM_JSON);
+        if(json == null) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
String.format("Missing required parameter '%s'", PARAM_JSON));
+            return;
+        }
+
+        // Parse incoming JSON and store as a schema
+        final ObjectMapper mapper = new ObjectMapper();
+        final JsonNode schema = mapper.readTree(json.getInputStream());
+        final Resource storedSchema = 
store.createOrUpdateSchema(request.getResource(), resourceType, schema);
 
         // TODO set Location header etc.
-        response.getWriter().write(String.format("Created site %s", 
result.getPath()));
+        response.getWriter().write(String.format("Stored schema for resource 
type %s: %s", resourceType, storedSchema.getPath()));
     }
 }
diff --git 
a/json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesParentServlet.java
 b/json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesServlet.java
similarity index 84%
rename from 
json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesParentServlet.java
rename to 
json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesServlet.java
index 961b03b..6484ef2 100644
--- 
a/json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesParentServlet.java
+++ b/json-store/src/main/java/org/apache/sling/jsonstore/impl/SitesServlet.java
@@ -17,7 +17,9 @@
  * under the License.
  */
 
- package org.apache.sling.jsonstore.impl;
+package org.apache.sling.jsonstore.impl;
+
+import static org.apache.sling.jsonstore.api.JsonStoreConstants.*;
 
 import java.io.IOException;
 
@@ -29,25 +31,25 @@ import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;
 import org.apache.sling.jsonstore.api.JsonStore;
-import org.apache.sling.jsonstore.api.JsonStoreConstants;
 import org.apache.sling.servlets.annotations.SlingServletResourceTypes;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
 @Component(service = Servlet.class)
 @SlingServletResourceTypes(
-    resourceTypes=JsonStoreConstants.STORE_ROOT_RESOURCE_TYPE,
+    resourceTypes=STORE_ROOT_RESOURCE_TYPE,
     methods= "POST"
 )
-public class SitesParentServlet extends SlingAllMethodsServlet {
+public class SitesServlet extends SlingAllMethodsServlet {
     @Reference
     private JsonStore store;
 
     @Override
     public void doPost(SlingHttpServletRequest request, 
SlingHttpServletResponse response) throws IOException {
-        final String relativeSitePath = 
request.getParameter(JsonStoreConstants.PARAM_PATH);
+        final String relativeSitePath = request.getParameter(PARAM_PATH);
         if(relativeSitePath == null) {
-            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
String.format("Missing required parameter '%s'"));
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, 
String.format("Missing required parameter '%s'", PARAM_PATH));
+            return;
         }
         final Resource result = store.createSite(request.getResource(), 
relativeSitePath);
 

Reply via email to