-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1094 from /issues/1011-merge-with-852
1011 - Added Object Model Support for JSon Schemas
- Loading branch information
Showing
16 changed files
with
848 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
platform/base/core/src/main/java/com/peregrine/jsonschema/Constants.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.peregrine.jsonschema; | ||
|
||
public class Constants { | ||
public static final String JSON_SCHEMA_SERVICE_USER_NAME = "jschema-serviceuser"; | ||
public static final String EQUALS = "="; | ||
|
||
public static final String JACKSON = "jackson"; | ||
public static final String JSON = "json"; | ||
} |
146 changes: 146 additions & 0 deletions
146
platform/base/core/src/main/java/com/peregrine/jsonschema/servlet/SchemaProviderServlet.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package com.peregrine.jsonschema.servlet; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.node.ArrayNode; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import com.peregrine.commons.servlets.AbstractBaseServlet; | ||
import com.peregrine.jsonschema.specification.Schema; | ||
import com.peregrine.jsonschema.specification.SchemaLoaderService; | ||
import org.apache.sling.api.resource.ResourceResolver; | ||
import org.osgi.service.component.annotations.Component; | ||
import org.osgi.service.component.annotations.Reference; | ||
|
||
import javax.servlet.Servlet; | ||
import javax.servlet.ServletException; | ||
|
||
import java.io.IOException; | ||
import java.util.Iterator; | ||
import java.util.Map.Entry; | ||
|
||
import static com.peregrine.commons.util.PerConstants.JSON; | ||
import static com.peregrine.commons.util.PerConstants.JSON_MIME_TYPE; | ||
import static com.peregrine.commons.util.PerUtil.EQUALS; | ||
import static com.peregrine.commons.util.PerUtil.GET; | ||
import static com.peregrine.commons.util.PerUtil.PER_PREFIX; | ||
import static com.peregrine.commons.util.PerUtil.PER_VENDOR; | ||
import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_METHODS; | ||
import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES; | ||
import static org.osgi.framework.Constants.SERVICE_DESCRIPTION; | ||
import static org.osgi.framework.Constants.SERVICE_VENDOR; | ||
|
||
|
||
@Component( | ||
service = Servlet.class, | ||
property = { | ||
SERVICE_DESCRIPTION + EQUALS + PER_PREFIX + "JSon Schema Provider Servlet", | ||
SERVICE_VENDOR + EQUALS + PER_VENDOR, | ||
SLING_SERVLET_METHODS + EQUALS + GET, | ||
SLING_SERVLET_RESOURCE_TYPES + EQUALS + "perapi/public/schema" | ||
} | ||
) | ||
public class SchemaProviderServlet extends AbstractBaseServlet { | ||
|
||
private static final String PROPERTIES_FIELD_NAME = "properties"; | ||
private static final String REQUIRED_FIELD_NAME = "required"; | ||
private static final String REFERENCE_FIELD_NAME = "$ref"; | ||
|
||
@Reference | ||
private SchemaLoaderService schemaLoaderService; | ||
|
||
@Override | ||
protected Response handleRequest(Request request) throws IOException, ServletException { | ||
Response answer; | ||
String path = request.getSuffix(); | ||
if(path == null || path.isEmpty()) { | ||
// Fallback if the Servlet is called by the source directly | ||
path = request.getRequestPath(); | ||
} | ||
Schema schema = schemaLoaderService.getSchema(request.getResourceResolver(), path); | ||
|
||
if(schema == null) { | ||
answer = new ErrorResponse().setErrorMessage("Failed to obtain Schema from Path").setRequestPath(path); | ||
} else { | ||
String source = schema.getSource(); | ||
ObjectMapper mapper = new ObjectMapper(); | ||
try { | ||
JsonNode node = mapper.readTree(source); | ||
if (node.has(PROPERTIES_FIELD_NAME)) { | ||
JsonNode properties = node.get(PROPERTIES_FIELD_NAME); | ||
// Get the Schema, get the content, parse it, go through properties and find references | ||
// load references content, parse it, obtain properties, add to parent, add required to required section | ||
// rinse and repeat | ||
// Serialize JSon into string answer | ||
handleProperties(request.getResourceResolver(), mapper, (ObjectNode) node, (ObjectNode) properties); | ||
} | ||
answer = new TextResponse(JSON, JSON_MIME_TYPE).write(mapper.writeValueAsString(node)); | ||
} catch(JsonProcessingException e) { | ||
answer = new ErrorResponse().setErrorMessage("Failed to parse Schema").setRequestPath(path).setException(e); | ||
} | ||
} | ||
return answer; | ||
} | ||
|
||
private void handleProperties( | ||
ResourceResolver resourceResolver, ObjectMapper mapper, ObjectNode root, ObjectNode properties | ||
) throws JsonProcessingException { | ||
// Loop over all properties entries and look for a reference in that entry's properties | ||
Iterator<Entry<String, JsonNode>> it = properties.fields(); | ||
while(it.hasNext()) { | ||
Entry<String, JsonNode> entry = it.next(); | ||
JsonNode property = entry.getValue(); | ||
if(property.has(REFERENCE_FIELD_NAME)) { | ||
JsonNode ref = property.get(REFERENCE_FIELD_NAME); | ||
String reference = ref.textValue(); | ||
if(reference != null && !reference.isEmpty()) { | ||
// Load the Schema | ||
Schema schema = schemaLoaderService.getSchema(resourceResolver, reference); | ||
if(schema != null) { | ||
properties.remove(entry.getKey()); | ||
// Now add the properties from the referenced schema | ||
JsonNode refRoot = mapper.readTree(schema.getSource()); | ||
mergeSchemas(resourceResolver, mapper, root, refRoot); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void mergeSchemas(ResourceResolver resourceResolver, ObjectMapper mapper, ObjectNode root, JsonNode reference) throws JsonProcessingException { | ||
boolean containsRef = false; | ||
if(reference.has(PROPERTIES_FIELD_NAME)) { | ||
if(!root.has(PROPERTIES_FIELD_NAME)) { | ||
root.putObject(PROPERTIES_FIELD_NAME); | ||
} | ||
containsRef = mergeProperties((ObjectNode) root.get(PROPERTIES_FIELD_NAME), reference.get(PROPERTIES_FIELD_NAME)); | ||
} | ||
if(reference.has(REQUIRED_FIELD_NAME)) { | ||
if(!root.has(REQUIRED_FIELD_NAME)) { | ||
root.putArray(REQUIRED_FIELD_NAME); | ||
} | ||
mergeRequired((ArrayNode) root.get(REQUIRED_FIELD_NAME), reference.get(REQUIRED_FIELD_NAME)); | ||
} | ||
if(containsRef) { | ||
handleProperties(resourceResolver, mapper, root, (ObjectNode) reference.get(PROPERTIES_FIELD_NAME)); | ||
} | ||
} | ||
|
||
private boolean mergeProperties(ObjectNode rootProperties, JsonNode referenceProperties) { | ||
boolean answer = false; | ||
Iterator<Entry<String, JsonNode>> it = referenceProperties.fields(); | ||
while (it.hasNext()) { | ||
Entry<String, JsonNode> entry = it.next(); | ||
JsonNode value = entry.getValue(); | ||
if(value.has(REFERENCE_FIELD_NAME)) { | ||
answer = true; | ||
} | ||
rootProperties.putIfAbsent(entry.getKey(), value); | ||
} | ||
return answer; | ||
} | ||
|
||
private void mergeRequired(ArrayNode rootRequired, JsonNode referenceRequired) { | ||
rootRequired.addAll((ArrayNode) referenceRequired); | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
...e/core/src/main/java/com/peregrine/jsonschema/specification/PerSchemaContentResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.peregrine.jsonschema.specification; | ||
|
||
import com.fasterxml.jackson.core.JsonFactory; | ||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import org.apache.sling.api.resource.Resource; | ||
import org.apache.sling.api.resource.ResourceResolver; | ||
import org.apache.sling.api.resource.ValueMap; | ||
import org.jsonschema2pojo.ContentResolver; | ||
|
||
import java.net.URI; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
|
||
import static com.peregrine.commons.util.PerConstants.JCR_CONTENT; | ||
import static com.peregrine.commons.util.PerConstants.JCR_DATA; | ||
import static java.util.Arrays.asList; | ||
import static org.apache.commons.lang3.StringUtils.removeStart; | ||
|
||
public class PerSchemaContentResolver | ||
extends ContentResolver | ||
{ | ||
private static final Set<String> PEREGRINE_SCHEMES = new HashSet<>(asList("per")); | ||
|
||
private final ResourceResolver resourceResolver; | ||
private final ObjectMapper objectMapper; | ||
private final List<String> references; | ||
|
||
public PerSchemaContentResolver(ResourceResolver resourceResolver, List<String> references) { | ||
this(resourceResolver, references, null); | ||
} | ||
|
||
public PerSchemaContentResolver(ResourceResolver resourceResolver, List<String> references, JsonFactory jsonFactory) { | ||
super(jsonFactory); | ||
this.resourceResolver = resourceResolver; | ||
this.references = references; | ||
this.objectMapper = new ObjectMapper(jsonFactory) | ||
.enable(JsonParser.Feature.ALLOW_COMMENTS) | ||
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); | ||
} | ||
|
||
public JsonNode resolve(URI uri) { | ||
|
||
if (PEREGRINE_SCHEMES.contains(uri.getScheme())) { | ||
return resolveFromJCR(uri, true); | ||
} | ||
if(uri.toString().startsWith("/")) { | ||
return resolveFromJCR(uri, false); | ||
} | ||
return super.resolve(uri); | ||
} | ||
|
||
private JsonNode resolveFromJCR(URI uri, boolean perScheme) { | ||
String path; | ||
if(perScheme) { | ||
path = removeStart(removeStart(uri.toString(), uri.getScheme() + ":"), "/"); | ||
path = path.startsWith("/") ? path.substring(1) : path; | ||
String siteName = path.substring(0, path.indexOf('/')); | ||
String objectDefinitionsName = path.substring(path.indexOf('/') + 1); | ||
path = "/content/" + siteName + "/object-definitions/" + objectDefinitionsName + "/schema.json"; | ||
} else { | ||
path = uri.toString(); | ||
} | ||
Resource resource = resourceResolver.getResource(path); | ||
if(resource == null) { | ||
throw new IllegalArgumentException("Couldn't read content from the JCR, node not found: " + path); | ||
} | ||
if(references != null) { | ||
references.add(resource.getPath()); | ||
} | ||
Resource contentResource = resource.getChild(JCR_CONTENT); | ||
if(contentResource == null) { | ||
throw new IllegalArgumentException("Couldn't find jcr:content child of node: " + path); | ||
} | ||
ValueMap properties = contentResource.getValueMap(); | ||
String content = properties.get(JCR_DATA, String.class); | ||
try { | ||
return objectMapper.readTree(content); | ||
} catch (JsonProcessingException e) { | ||
throw new IllegalArgumentException("Error parsing document: " + uri, e); | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
platform/base/core/src/main/java/com/peregrine/jsonschema/specification/Property.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.peregrine.jsonschema.specification; | ||
|
||
import java.util.List; | ||
|
||
public interface Property { | ||
|
||
String getName(); | ||
String getType(); | ||
boolean isRequired(); | ||
List<Property> getDependencies(); | ||
} |
48 changes: 48 additions & 0 deletions
48
platform/base/core/src/main/java/com/peregrine/jsonschema/specification/PropertyImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.peregrine.jsonschema.specification; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class PropertyImpl | ||
implements Property | ||
{ | ||
String name; | ||
String type; | ||
boolean required; | ||
List<Property> dependencies = new ArrayList<>(); | ||
|
||
public PropertyImpl(String name, String type) { | ||
this.name = name; | ||
this.type = type; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return name; | ||
} | ||
|
||
@Override | ||
public String getType() { | ||
return type; | ||
} | ||
|
||
@Override | ||
public boolean isRequired() { | ||
return required; | ||
} | ||
|
||
@Override | ||
public List<Property> getDependencies() { | ||
return dependencies; | ||
} | ||
|
||
public PropertyImpl setRequired(boolean required) { | ||
this.required = required; | ||
return this; | ||
} | ||
|
||
public PropertyImpl addDependency(Property dependency) { | ||
this.dependencies.add(dependency); | ||
return this; | ||
} | ||
} |
Oops, something went wrong.