From 7ac9856b28d3fee02fcf05d26ee3ebd527486412 Mon Sep 17 00:00:00 2001 From: Greg McFall Date: Tue, 5 Nov 2013 08:51:45 -0800 Subject: [PATCH] Allow @context to appear anywhere in JSON-LD document --- semantictools-jsonld/pom.xml | 2 +- .../org/semantictools/jsonld/LdObject.java | 2 + .../org/semantictools/jsonld/LdProcessor.java | 3 +- .../jsonld/impl/LdObjectImpl.java | 6 +- .../jsonld/impl/LdTreeReader.java | 272 +++++++++++++ .../jsonld/impl/LdValidationServiceImpl.java | 16 +- .../jsonld/io/LdContextReader.java | 4 + .../io/impl/EnhancedLdContextReader.java | 7 + .../jsonld/io/impl/LdContextReaderImpl.java | 372 ++++++++++++++++++ .../jsonld/impl/LdTreeReaderTest.java | 147 +++++++ .../impl/LdValidationServiceImplTest.java | 24 +- .../resources/GradebookItemEventContext.json | 42 ++ 12 files changed, 882 insertions(+), 15 deletions(-) create mode 100644 semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdTreeReader.java create mode 100644 semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdTreeReaderTest.java create mode 100644 semantictools-jsonld/src/test/resources/GradebookItemEventContext.json diff --git a/semantictools-jsonld/pom.xml b/semantictools-jsonld/pom.xml index 104042c..15c2bf6 100644 --- a/semantictools-jsonld/pom.xml +++ b/semantictools-jsonld/pom.xml @@ -19,7 +19,7 @@ org.semantictools semantictools-jsonld jar - 1.22 + 1.24 SemanticTools JSON-LD A library used for JSON-LD processing diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdObject.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdObject.java index 0aa810d..12b9a63 100644 --- a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdObject.java +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdObject.java @@ -19,6 +19,8 @@ public interface LdObject extends LdNode { + void setContext(LdContext context); + /** * Returns the JSON-LD context that governs this object. */ diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdProcessor.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdProcessor.java index 54b6c65..e6d74ef 100644 --- a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdProcessor.java +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/LdProcessor.java @@ -32,6 +32,7 @@ import org.semantictools.jsonld.impl.LdContextManagerImpl; import org.semantictools.jsonld.impl.LdParserImpl; import org.semantictools.jsonld.impl.LdPublisherPipeline; +import org.semantictools.jsonld.impl.LdTreeReader; import org.semantictools.jsonld.impl.LdValidationServiceImpl; import org.semantictools.jsonld.io.ErrorHandler; import org.semantictools.jsonld.io.LdContextReader; @@ -160,7 +161,7 @@ private LdContextWriter getContextWriter() { private LdParser getLdParser() { if (jsonldParser == null) { LdContextReader reader = new EnhancedLdContextReader(getContextEnhancer(), getContextReader()); - jsonldParser = new LdParserImpl(reader); + jsonldParser = new LdTreeReader(reader); } return jsonldParser; } diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdObjectImpl.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdObjectImpl.java index fc779b6..4ae4ffb 100644 --- a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdObjectImpl.java +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdObjectImpl.java @@ -84,10 +84,14 @@ public LdObject asObject() throws ClassCastException { @Override public LdContext getContext() { + if (context == null && owner != null && owner.getOwner()!=null) { + return owner.getOwner().getContext(); + } return context; } - void setContext(LdContext context) { + @Override + public void setContext(LdContext context) { this.context = context; } diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdTreeReader.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdTreeReader.java new file mode 100644 index 0000000..927c5d3 --- /dev/null +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdTreeReader.java @@ -0,0 +1,272 @@ +package org.semantictools.jsonld.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.Map.Entry; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.JsonParser.Feature; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.node.ArrayNode; +import org.codehaus.jackson.node.ObjectNode; +import org.semantictools.jsonld.LdContainerType; +import org.semantictools.jsonld.LdContext; +import org.semantictools.jsonld.LdField; +import org.semantictools.jsonld.LdLiteral; +import org.semantictools.jsonld.LdNode; +import org.semantictools.jsonld.LdObject; +import org.semantictools.jsonld.LdTerm; +import org.semantictools.jsonld.io.LdContextReader; +import org.semantictools.jsonld.io.LdParseException; +import org.semantictools.jsonld.io.LdParser; + +public class LdTreeReader implements LdParser { + private LdContextReader contextReader; + + public LdTreeReader(LdContextReader contextReader) { + this.contextReader = contextReader; + } + + @Override + public LdNode parse(InputStream input) throws LdParseException, IOException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(Feature.ALLOW_COMMENTS, true); + JsonNode node = mapper.readTree(input); + if (! (node instanceof ObjectNode)) { + throw new LdParseException("JSON-LD document must have an object as the root element"); + } + + LdNode result = parseNode(node, null, null, null); + + return result; + } + + LdNode parseNode(JsonNode node, LdObjectImpl parent, LdTerm term, LdField owner) throws JsonParseException, IOException, LdParseException { + LdNode result = null; + + if ( node instanceof ObjectNode) { + result = parseObject((ObjectNode) node, parent, owner); + } else if (node.isBoolean()) { + LdLiteral literal = new LdLiteral(); + literal.setBooleanValue(node.getBooleanValue()); + result = literal; + } else if (node.isFloatingPointNumber()) { + LdLiteral literal = new LdLiteral(); + literal.setDoubleValue(node.getDoubleValue()); + result = literal; + } else if (node.isLong() || node.isInt()) { + LdLiteral literal = new LdLiteral(); + literal.setLongValue(node.getLongValue()); + result = literal; + } else if (node.isTextual()) { + result = parseStringNode(node.getTextValue(), parent, term); + } else if (node.isArray()) { + result = parseArray((ArrayNode)node, parent, term, owner); + } + + return result; + } + + private LdNode parseStringNode(String text, LdObject parent, LdTerm term) throws JsonParseException, IOException { + LdNode result = null; + String type = (term==null) ? null : term.getRawTypeIRI(); + if ("@id".equals(type)) { + LdContext context = (parent==null) ? null : parent.getContext(); + LdObjectImpl object = new LdObjectImpl(context); + object.setId(text); + result = object; + } else { + + LdLiteral literal = new LdLiteral(); + literal.setStringValue(text); + result = literal; + + } + return result; + } + + private LdNode parseArray(ArrayNode array, LdObjectImpl parent, LdTerm term, LdField owner) throws JsonParseException, IOException, LdParseException { + LdList list = new LdList(LdContainerType.SET); + parseElements(array, term, parent, list, owner); + + return list; + } + + private void parseElements(ArrayNode array, LdTerm term, LdObjectImpl parent, LdList list, + LdField field) throws JsonParseException, IOException, LdParseException { + + for (int i=0; i> sequence = object.getFields(); + while (sequence.hasNext()) { + Entry entry = sequence.next(); + String fieldName = entry.getKey(); + JsonNode node = entry.getValue(); + + if (fieldName.equals("@language")) { + literal.setLanguage(node.getTextValue()); + } else if (fieldName.equals("@type")) { + literal.setType(node.getTextValue()); + } else if ("@value".equals(fieldName)) { + if (node.isBoolean()) { + literal.setBooleanValue(node.getBooleanValue()); + } else if (node.isFloatingPointNumber()) { + literal.setDoubleValue(node.getDoubleValue()); + } else if (node.isLong()) { + literal.setLongValue(literal.getLongValue()); + } else if (node.isTextual()) { + literal.setStringValue(node.getTextValue()); + } + } + } + + return literal; + } + + private void parseFields(ObjectNode json, LdObjectImpl object, ObjectNode node, + LdObjectImpl parent) throws JsonParseException, IOException, LdParseException { + + FieldList fieldList = new FieldList(); + object.setFieldList(fieldList); + LdContext context = object.getContext(); + + Iterator> sequence = json.getFields(); + while (sequence.hasNext()) { + Entry entry = sequence.next(); + String fieldName = entry.getKey(); + JsonNode value = entry.getValue(); + + if ("@context".equals(fieldName)) { + continue; + } else if ("@id".equals(fieldName)) { + object.setId(value.getTextValue()); + } else if ("@type".equals(fieldName)) { + object.setRawType(value.getTextValue()); + } else { + + LdTerm term = (context==null) ? null : context.getTerm(fieldName); + LinkedLdField field = new LinkedLdField(object); + + // If the field name contains a '#', '/', or ':' + // then the simple name is substring after that delimiter. + + int delim = fieldName.lastIndexOf('#'); + if (delim < 0) { + delim = fieldName.lastIndexOf('/'); + } + if (delim < 0) { + delim = fieldName.lastIndexOf(':'); + } + if (delim >= 0) { + field.setLocalName(fieldName.substring(delim+1)); + } else { + field.setLocalName(fieldName); + } + + + String propertyIRI = (context == null) ? fieldName : context.expand(fieldName); + field.setPropertyURI(propertyIRI); + + LdNode v = parseNode(value, object, term, field); + field.setValue(v); + setValueOwner(field); + + setType(field, fieldName, context); + object.fieldList.add(field); + } + + + + } + + } + + /** + * Set the type of the given field based on the given context, but only if the + * field does not already have the type declared. + */ + private void setType(LinkedLdField field, String fieldName, LdContext context) { + if (context == null) return; + + LdNode value = field.getValue(); + if (value == null) return; + + LdTerm term = context.getTerm(fieldName); + if (term == null) return; + + if (value instanceof LdObjectImpl) { + LdObjectImpl object = (LdObjectImpl) value; + if (object.getTypeIRI() != null) return; + object.setTypeIRI(term.getTypeIRI()); + + } else if (value instanceof LdLiteral) { + LdLiteral literal = (LdLiteral) value; + if (literal.getType() == null) { + literal.setType(term.getTypeIRI()); + } + } + + } + + private void setValueOwner(LdField field) { + + LdNode value = field.getValue(); + + if (value instanceof LdObjectImpl) { + LdObjectImpl object = (LdObjectImpl) value; + object.setOwner(field); + } else if (value instanceof LdContainerImpl) { + LdContainerImpl container = (LdContainerImpl) value; + container.setOwner(field); + } + + } + + @Override + public void setStreaming(boolean streaming) { + + } + + @Override + public boolean isStreaming() { + return false; + } + +} diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdValidationServiceImpl.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdValidationServiceImpl.java index 1a34d6d..8578f70 100644 --- a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdValidationServiceImpl.java +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/impl/LdValidationServiceImpl.java @@ -168,19 +168,19 @@ private void validateObject(String path, RandomAccessObject object, LdClass dr) Integer minCardinality = restriction.getMinCardinality(); Integer maxCardinality = restriction.getMaxCardinality(); - + LdContext context = object.getNode().getContext(); int cardinality = getCardinality(field); if (minCardinality != null) { - validateMinCardinality(object.getNode(), propertyURI, fieldPath, minCardinality, cardinality); + validateMinCardinality(context, object.getNode(), propertyURI, fieldPath, minCardinality, cardinality); } if (maxCardinality != null) { validateMaxCardinality(fieldPath, maxCardinality, cardinality); } - validateQualifiedRestrictions(fieldPath, restriction, field, cardinality); + validateQualifiedRestrictions(context, fieldPath, restriction, field, cardinality); } @@ -190,6 +190,7 @@ private void validateObject(String path, RandomAccessObject object, LdClass dr) private void validateQualifiedRestrictions( + LdContext context, String fieldPath, LdRestriction restriction, LdField field, int cardinality) { List list = restriction.listQualifiedRestrictions(); @@ -199,9 +200,14 @@ private void validateQualifiedRestrictions( Integer minCardinality = qr.getMinCardinality(); Integer maxCardinality = qr.getMaxCardinality(); + + LdObject owner = field==null ? null : field.getOwner(); + String propertyURI = field==null ? qr.getRestriction().getPropertyURI() : field.getPropertyURI(); + + LdContext c = (owner==null) ? context : owner.getContext(); if (minCardinality != null) { - validateMinCardinality(field.getOwner(), field.getPropertyURI(), fieldPath, minCardinality, cardinality); + validateMinCardinality(c, owner, propertyURI, fieldPath, minCardinality, cardinality); } if (maxCardinality != null) { @@ -500,13 +506,13 @@ private String getLocalName(String propertyURI) { private void validateMinCardinality( + LdContext context, LdObject object, String propertyURI, String fieldPath, int minCardinality, int cardinality) { /** * Check to see if the minimum cardinality has an override in the * JSON-LD context. */ - LdContext context = object.getContext(); LdTerm term = context.getTerm(propertyURI); if (term.getMinCardinality() != null) { minCardinality = term.getMinCardinality(); diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/LdContextReader.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/LdContextReader.java index 87f712e..b5db927 100644 --- a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/LdContextReader.java +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/LdContextReader.java @@ -19,8 +19,10 @@ import java.io.InputStream; import java.io.Reader; +import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.node.ObjectNode; import org.semantictools.jsonld.LdContext; import org.semantictools.jsonld.LdContextParseException; @@ -44,6 +46,8 @@ public interface LdContextReader { */ LdContext parseContextField(JsonParser parser) throws LdContextParseException, IOException; + LdContext parseContext(JsonNode node) throws LdContextParseException, IOException; + void setErrorHandler(ErrorHandler handler); ErrorHandler getErrorHandler(); diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/EnhancedLdContextReader.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/EnhancedLdContextReader.java index 184b984..6c48d45 100644 --- a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/EnhancedLdContextReader.java +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/EnhancedLdContextReader.java @@ -19,6 +19,7 @@ import java.io.InputStream; import java.io.Reader; +import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParser; import org.semantictools.jsonld.LdContext; import org.semantictools.jsonld.LdContextEnhancer; @@ -81,4 +82,10 @@ public ErrorHandler getErrorHandler() { return reader.getErrorHandler(); } + @Override + public LdContext parseContext(JsonNode node) throws LdContextParseException { + // TODO Auto-generated method stub + return null; + } + } diff --git a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/LdContextReaderImpl.java b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/LdContextReaderImpl.java index 1790b8c..9026c6e 100644 --- a/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/LdContextReaderImpl.java +++ b/semantictools-jsonld/src/main/java/org/semantictools/jsonld/io/impl/LdContextReaderImpl.java @@ -19,13 +19,18 @@ import java.io.InputStream; import java.io.Reader; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Map.Entry; import java.util.regex.Pattern; import org.codehaus.jackson.JsonFactory; +import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; +import org.codehaus.jackson.node.ArrayNode; +import org.codehaus.jackson.node.ObjectNode; import org.semantictools.jsonld.LdClass; import org.semantictools.jsonld.LdContainerType; import org.semantictools.jsonld.LdContext; @@ -721,5 +726,372 @@ public ErrorHandler getErrorHandler() { return errorHandler; } + @Override + public LdContext parseContext(JsonNode node) throws LdContextParseException, IOException { + LdContext context = null; + boolean external = false; + + if (node.isTextual()) { + context = loadExternalContext(node.asText()); + external = true; + + } else if (node instanceof ObjectNode) { + context = new LdContext(); + parseContextObject((ObjectNode) node, context); + } else if (node instanceof ArrayNode) { + context = new LdContext(); + parseContextArray((ArrayNode)node, context); + } + + if (context != null) { + context.close(); + prepareOwlClasses(context); + + // We don't need to resolve references if the context is external + // and obtained from the LdContextManager. + // + if (!external && context.isEnhanced()) { + resolveRefereces(context); + } + } + + return context; + } + + private void parseContextArray(ArrayNode array, LdContext context) throws LdContextParseException, IOException { + + for (int i=0; i> sequence = node.getFields(); + while (sequence.hasNext()) { + Entry entry = sequence.next(); + parseField(context, entry.getKey(), entry.getValue()); + } + } + + private void parseField(LdContext context, String fieldName, JsonNode value) throws JsonParseException, LdContextParseException { + LdTerm term = new LdTerm(); + term.setShortName(fieldName); + context.add(term); + + if (value.isTextual()) { + term.setRawIRI(value.getTextValue()); + + } else if (value instanceof ObjectNode) { + parseTermFields((ObjectNode)value, term); + + } else { + throw new JsonParseException("Expected IRI value or object definition of term " + fieldName, null); + } + + } + + private void parseTermFields(ObjectNode json, LdTerm term) throws LdContextParseException { + JsonToken token = null; + + Iterator> sequence = json.getFields(); + while (sequence.hasNext()) { + Entry entry = sequence.next(); + String fieldName = entry.getKey(); + JsonNode value = entry.getValue(); + + if ("@id".equals(fieldName)) { + term.setRawIRI(value.getTextValue()); + + } else if ("@type".equals(fieldName)) { + term.setRawTypeIRI(value.getTextValue()); + + } else if ("@language".equals(fieldName)) { + term.setLanguage(value.getTextValue()); + + } else if ("@container".equals(fieldName)) { + term.setContainerType(readContainerType(value)); + + } else if ("@minCardinality".equals(fieldName)) { + term.setMinCardinality(value.getIntValue()); + + } else if ("datatype".equals(fieldName)) { + parseDatatype(value, term); + + } else if ("class".equals(fieldName)) { + parseClass(value, term); + + } else if("property".equals(fieldName)) { + parseProperty(value, term); + + } + } + + + } + + + private void parseProperty(JsonNode value, LdTerm term) { + // TODO Auto-generated method stub + + } + + private void parseClass(JsonNode value, LdTerm term) throws LdContextParseException { + if ( ! (value instanceof ObjectNode)) { + throw new LdContextParseException("Expected class definition to be an object in term " + term.getRawIRI()); + } + ObjectNode object = (ObjectNode) value; + LdClass rdfClass = new LdClass(null); + term.setRdfClass(rdfClass); + + Iterator> sequence = object.getFields(); + + while ( sequence.hasNext() ) { + Entry entry = sequence.next(); + String fieldName = entry.getKey(); + value = entry.getValue(); + + if ("supertype".equals(fieldName)) { + parserSupertype(value, rdfClass); + + } else if ("restriction".equals(fieldName)) { + parseRestrictionArray(value, rdfClass); + + } + + } + + } + + private void parseRestrictionArray(JsonNode value, LdClass rdfClass) throws LdContextParseException { + + if (! (value instanceof ArrayNode)) { + throw new LdContextParseException("Expected restriction definition to be an array"); + } + ArrayNode array = (ArrayNode) value; + for (int i=0; i> sequence = object.getFields(); + + while (sequence.hasNext()) { + Entry entry = sequence.next(); + + String fieldName = entry.getKey(); + value = entry.getValue(); + + if ("onProperty".equals(fieldName)) { + restriction.setPropertyURI(value.getTextValue()); + + } else if ("maxCardinality".equals(fieldName)) { + restriction.setMaxCardinality(value.getIntValue()); + + } else if ("minCardinality".equals(fieldName)) { + restriction.setMinCardinality(value.getIntValue()); + + } else if ("qualifiedRestriction".equals(fieldName)) { + parseQualifiedRestrictionArray(value, restriction); + + } + + } + + } + + private void parseQualifiedRestrictionArray(JsonNode value, + LdRestriction restriction) throws LdContextParseException { + + if (!(value instanceof ArrayNode)) { + throw new LdContextParseException("Expected qualified restriction definition to be an array"); + } + ArrayNode array = (ArrayNode) value; + + for (int i=0; i> sequence = object.getFields(); + + while (sequence.hasNext()) { + Entry entry = sequence.next(); + value = entry.getValue(); + String fieldName = entry.getKey(); + if ("onClass".equals(fieldName)) { + qr.setRangeURI(value.getTextValue()); + + } else if ("maxCardinality".equals(fieldName)) { + qr.setMaxCardinality(value.getIntValue()); + + } else if ("minCardinality".equals(fieldName)) { + qr.setMinCardinality(value.getIntValue()); + + } + } + + } + + private void parserSupertype(JsonNode value, LdClass rdfClass) throws LdContextParseException { + + if (! (value instanceof ArrayNode)) { + throw new LdContextParseException("Expected supertype value to be an array"); + } + ArrayNode array = (ArrayNode) value; + for (int i=0; i> sequence = object.getFields(); + while (sequence.hasNext()) { + Entry entry = sequence.next(); + String fieldName = entry.getKey(); + value = entry.getValue(); + + if (fieldName.equals("base")) { + String baseURI = value.getTextValue(); + LdDatatype base = new LdDatatype(); + base.setUri(baseURI); + datatype.setBase(base); + + } else if ("length".equals(fieldName)) { + datatype.setLength(value.getIntValue()); + + } else if ("minLength".equals(fieldName)) { + datatype.setMinLength(value.getIntValue()); + + } else if ("maxLength".equals(fieldName)) { + datatype.setMaxLength(value.getIntValue()); + + } else if ("pattern".equals(fieldName)) { + datatype.setPattern(Pattern.compile(value.getTextValue())); + + } else if ("whitespace".equals(fieldName)) { + String name = value.getTextValue(); + + Whitespace ws = + "collapse".equals(name) ? Whitespace.COLLAPSE : + "replace".equals(name) ? Whitespace.REPLACE : + "preserve".equals(name) ? Whitespace.PRESERVE : + null; + + if (ws == null) { + throw new LdContextParseException("Unrecognized whitespace '" + name + "' in term " + term.getIRI()); + } + datatype.setWhitespace(ws); + + } else if ("maxInclusive".equals(fieldName)) { + datatype.setMaxInclusive(readNumber(value)); + + } else if ("minInclusive".equals(fieldName)) { + datatype.setMinInclusive(readNumber(value)); + + } else if ("maxExclusive".equals(fieldName)) { + datatype.setMaxExclusive(readNumber(value)); + + } else if ("minExclusive".equals(fieldName)) { + datatype.setMinExclusive(readNumber(value)); + + } else if ("totalDigits".equals(fieldName)) { + datatype.setTotalDigits(value.getIntValue()); + + } else if ("fractionDigits".equals(fieldName)) { + datatype.setFractionDigits(value.getIntValue()); + + } + + } + + } + + private Number readNumber(JsonNode value) throws LdContextParseException { + if (value.isInt()) { + return new Integer(value.getIntValue()); + } + if (value.isLong()) { + return new Long(value.getLongValue()); + } + if (value.isDouble()) { + return new Double(value.getDoubleValue()); + } + throw new LdContextParseException("Expected a numeric value"); + } + + private LdContainerType readContainerType(JsonNode value) { + String text = value.getTextValue(); + + return + "@set".equals(text) ? LdContainerType.SET : + "@list".equals(text) ? LdContainerType.LIST : + LdContainerType.UNDEFINED; + } + } diff --git a/semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdTreeReaderTest.java b/semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdTreeReaderTest.java new file mode 100644 index 0000000..e538f60 --- /dev/null +++ b/semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdTreeReaderTest.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright 2012 Pearson Education + * + * Licensed 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.semantictools.jsonld.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Iterator; + +import org.junit.Before; +import org.junit.Test; +import org.semantictools.jsonld.LdContainer; +import org.semantictools.jsonld.LdContext; +import org.semantictools.jsonld.LdField; +import org.semantictools.jsonld.LdNode; +import org.semantictools.jsonld.LdObject; +import org.semantictools.jsonld.io.LdParseException; +import org.semantictools.jsonld.io.impl.LdContextReaderImpl; + +public class LdTreeReaderTest { + + private String jsonDocument = "GradebookItemEventSample.json"; + private LdTreeReader parser; + + @Before + public void setUp() throws Exception { + + // For the purposes of this test, we are going to use an InMemoryLdContextManager, + // and we will preload the JSON-LD context into memory. + + String contextURI = "http://purl.org/pearson/core/v1/ctx/outcomes/GradebookItemEvent"; + InputStream contextStream = getClass().getClassLoader().getResourceAsStream("gdx/GradebookItemEventContext.json"); + + InMemoryLdContextManager contextManager = new InMemoryLdContextManager(); + + LdContextReaderImpl contextParser = new LdContextReaderImpl(contextManager); + LdContext context = contextParser.parseExternalContext(contextURI, contextStream); + contextManager.add(context); + + parser = new LdTreeReader(contextParser); + } + + + @Test + public void test() throws Exception { + InputStream input = getClass().getClassLoader().getResourceAsStream(jsonDocument); + + LdNode node = parser.parse(input); + + assertTrue(node.isObject()); + + LdObject object = node.asObject(); + Iterator sequence = object.fields(); + + verifyMetadata(sequence.next()); + verifyBody(sequence.next()); + } + + private void verifyBody(LdField field) { + assertEquals("body", field.getLocalName()); + Iterator fieldSequence = field.getValue().asObject().fields(); + + + assertStringField(fieldSequence.next(), "label", "Chapter 3 Quiz"); + assertIriField(fieldSequence.next(), "integrationContract", "urn:pso:vendor/pearson.com/product/econ_xl"); + assertIriField(fieldSequence.next(), "context", "urn:udson:pearson.com/sms/prod:course/jsmith38271"); + verifyAssignment(fieldSequence.next()); + + + } + + private void verifyAssignment(LdField assignmentField) { + + assertEquals("assignment", assignmentField.getLocalName()); + LdObject object = assignmentField.getValue().asObject(); + + assertEquals("http://mathxl.com/assignments/94722", object.getIRI()); + Iterator field = object.fields(); + + verifyTaxons(field.next()); + + } + + private void verifyTaxons(LdField taxonField) { + + assertEquals("taxon", taxonField.getLocalName()); + LdContainer container = taxonField.getValue().asContainer(); + Iterator elements = container.iterator(); + assertIRI("http://purl.org/ASN/resources/S114362E", elements.next()); + assertIRI("http://purl.org/ASN/resources/S114362F", elements.next()); + assertTrue(!elements.hasNext()); + + } + + private void assertStringNode(String expected, LdNode actual) { + assertEquals(expected, actual.asLiteral().getStringValue()); + + } + + private void assertIRI(String expected, LdNode actual) { + assertEquals(expected, actual.asIRI().getValue()); + + } + + private void verifyMetadata(LdField metadata) { + + LdObject object = metadata.getValue().asObject(); + Iterator fieldSequence = object.fields(); + + assertEquals("http://purl.org/pearson/core/v1/vocab/message#metadata", metadata.getPropertyURI()); + assertStringField(fieldSequence.next(), "version", "1.0"); + assertStringField(fieldSequence.next(), "msgId", "11ecf52f-d88b-4ce1-a33b-5566d2f53733"); + assertStringField(fieldSequence.next(), "timestamp", "2012-04-11T12:38:59-04:00"); + assertStringField(fieldSequence.next(), "sourceId", "urn:udson:pearson.com/xl"); + assertStringField(fieldSequence.next(), "destinationId", "urn:udson:pearson.com/eclg"); + assertTrue(!fieldSequence.hasNext()); + + } + + private void assertStringField(LdField field, String fieldName, String fieldValue) { + assertTrue(field != null); + assertEquals(fieldName, field.getLocalName()); + assertEquals(fieldValue, field.getValue().asLiteral().getStringValue()); + } + + private void assertIriField(LdField field, String fieldName, String fieldValue) { + assertTrue(field != null); + assertEquals(fieldName, field.getLocalName()); + assertEquals(fieldValue, field.getValue().asIRI().getValue()); + } + +} diff --git a/semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdValidationServiceImplTest.java b/semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdValidationServiceImplTest.java index abb795e..a653644 100644 --- a/semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdValidationServiceImplTest.java +++ b/semantictools-jsonld/src/test/java/org/semantictools/jsonld/impl/LdValidationServiceImplTest.java @@ -15,6 +15,7 @@ ******************************************************************************/ package org.semantictools.jsonld.impl; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -25,15 +26,9 @@ import org.junit.Before; import org.junit.Test; import org.semantictools.jsonld.LdContext; -import org.semantictools.jsonld.LdContextManager; import org.semantictools.jsonld.LdNode; import org.semantictools.jsonld.LdValidationMessage; import org.semantictools.jsonld.LdValidationReport; -import org.semantictools.jsonld.impl.InMemoryLdContextManager; -import org.semantictools.jsonld.impl.InMemoryVocabularyManager; -import org.semantictools.jsonld.impl.LdContextEnhancerImpl; -import org.semantictools.jsonld.impl.LdParserImpl; -import org.semantictools.jsonld.impl.LdValidationServiceImpl; import org.semantictools.jsonld.io.LdParser; import org.semantictools.jsonld.io.impl.HtmlReportWriter; import org.semantictools.jsonld.io.impl.LdContextReaderImpl; @@ -43,6 +38,7 @@ public class LdValidationServiceImplTest { private String contextURI = "http://purl.org/pearson/core/v1/ctx/outcomes/GradebookItemEvent"; private String contextPath = "gdx/GradebookItemEventContext.json"; private String gradebookItemEventInvalid = "GradebookItemEventInvalid.json"; + private String gradebookItemEventContext = "GradebookItemEventContext.json"; private String noBody = "GradebookItemEventNoBody.json"; private LdValidationServiceImpl validator; @@ -69,7 +65,7 @@ public void setUp() throws Exception { contextManager.add(context); - parser = new LdParserImpl(contextParser); + parser = new LdTreeReader(contextParser); validator = new LdValidationServiceImpl(); } @@ -100,6 +96,20 @@ public void testNoBody() throws Exception { } + /** + * Verify that the validator works even if the JSON-LD context + * is defined at the end of the JSON document. + */ + @Test + public void testContext() throws Exception { + + InputStream documentStream = getClass().getClassLoader().getResourceAsStream(gradebookItemEventContext); + LdNode node = parser.parse(documentStream); + LdValidationReport report = validator.validate(node); + + assertEquals(0, report.listMessages().size()); + } + public static void validateReport(LdValidationReport report) { assertMessage(report, "ERROR metadata.msgId Expected minCardinality=1, but found cardinality=0"); diff --git a/semantictools-jsonld/src/test/resources/GradebookItemEventContext.json b/semantictools-jsonld/src/test/resources/GradebookItemEventContext.json new file mode 100644 index 0000000..decf0a9 --- /dev/null +++ b/semantictools-jsonld/src/test/resources/GradebookItemEventContext.json @@ -0,0 +1,42 @@ +{ + "@type" : "GradebookItemEvent", + + "metadata" : { + "version" : "1.0", + "msgId" : "11ecf52f-d88b-4ce1-a33b-5566d2f53733", + "timestamp" : "2012-04-11T12:38:59-04:00", + "sourceId" : "urn:udson:pearson.com/xl", + "destinationId" : "urn:udson:pearson.com/eclg" + }, + + "body" : { + "label" : "Chapter 3 Quiz", + "integrationContract" : "urn:pso:vendor/pearson.com/product/econ_xl", + "context" : "urn:udson:pearson.com/sms/prod:course/jsmith38271", + "assignment" : { + "@id" : "http://mathxl.com/assignments/94722", + "taxon" : [ + "http://purl.org/ASN/resources/S114362E", + "http://purl.org/ASN/resources/S114362F" + ] + }, + "assignmentAdministration" : { + "assignmentType" : { + "@id" : "http://mathxl.com/assignmentTypes#QUIZ", + "label" : "Quiz" + }, + "contentLocation" : ["urn:lss:ecollege.unit:1"], + "startDateTime" : "2012-04-11T12:38:59-04:00", + "endDateTime" : "2012-05-11T12:38:59-04:00" + }, + "resultValueSpec" : { + "@type" : "NumericLimits", + "totalMaximum" : 110, + "normalMaximum" : 100, + "extraCreditMaximum" : 10 + }, + "includedInGrade" : true, + "lastModified" : "2012-03-09T12:38:59-04:00" + }, + "@context" : "http://purl.org/pearson/core/v1/ctx/outcomes/GradebookItemEvent" +}