diff --git a/README.md b/README.md index b5b04f4..f1fb60f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ -Java Json Schema validation library based on JSONP v1.1 Json parser. +# Java Json Schema validation library based on JSONP v1.1 Json parser. +###### compatibility +The library supports draft-4, draft-6, draft-7, draft-2019-09 and draft-2020-12 versions of Json Schema. + +###### maven repository import via maven: ```xml @@ -7,7 +11,7 @@ import via maven: es.elixir.bsc.json.schema jaronuinga - 0.5.4 + 0.5.5 ... diff --git a/pom.xml b/pom.xml index cd30108..9cec404 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ es.elixir.bsc.json.schema jaronuinga - 0.5.5-SNAPSHOT + 0.5.5 jar diff --git a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java index 961e417..950cf52 100644 --- a/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java +++ b/src/main/java/es/elixir/bsc/json/schema/impl/DefaultJsonSchemaParser.java @@ -55,12 +55,13 @@ import java.io.IOException; import java.net.URI; import java.util.Map; -import java.util.WeakHashMap; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonString; import javax.json.JsonValue; import javax.json.JsonValue.ValueType; +import static javax.json.JsonValue.ValueType.ARRAY; +import static javax.json.JsonValue.ValueType.STRING; /** * @author Dmitry Repchevsky @@ -68,8 +69,8 @@ public class DefaultJsonSchemaParser implements JsonSubschemaParser { + private final JsonSchemaElementsCache cache = new JsonSchemaElementsCache(); private final Map properties; - private final Map cache = new WeakHashMap(); public DefaultJsonSchemaParser(Map properties) { this.properties = properties; @@ -85,6 +86,11 @@ public AbstractJsonSchema parse(JsonSchemaLocator locator, AbstractJsonSchemaEle String jsonPointer, JsonValue value, JsonType type) throws JsonSchemaException { + AbstractJsonSchema schema = cache.get(locator, jsonPointer); + if (schema != null) { + return schema; + } + if (value.getValueType() == ValueType.TRUE || value.getValueType() == ValueType.FALSE) { return new BooleanJsonSchemaImpl(parent, locator, jsonPointer).read(this, value); @@ -94,7 +100,7 @@ public AbstractJsonSchema parse(JsonSchemaLocator locator, AbstractJsonSchemaEle throw new JsonSchemaException(new ParsingError( ParsingMessage.SCHEMA_OBJECT_ERROR, value.getValueType())); } - + final JsonObject object = value.asJsonObject(); final JsonValue jref = object.get(JsonReference.REF); @@ -110,6 +116,27 @@ public AbstractJsonSchema parse(JsonSchemaLocator locator, AbstractJsonSchemaEle } } + JsonValue $id = object.get(JsonSchema.ID); + if ($id == null) { + $id = object.get("id"); // draft4 + } + + if ($id != null) { + if ($id.getValueType() != JsonValue.ValueType.STRING) { + throw new JsonSchemaException(new ParsingError(ParsingMessage.INVALID_ATTRIBUTE_TYPE, + "id", $id.getValueType().name(), JsonValue.ValueType.STRING.name())); + } else if (!(parent instanceof JsonMultitypeSchemaWrapper)) { + // special case for wrapper parent - we already resolved $id there!!! + final String id = ((JsonString)$id).getString(); + try { + locator = locator.resolve(URI.create(id)); + locator.setSchema(object); + } catch(IllegalArgumentException ex) { + throw new JsonSchemaException(new ParsingError(ParsingMessage.INVALID_REFERENCE, id)); + } + } + } + final JsonValue type_value = object.get(TYPE); final ValueType vtype; if (type_value == null) { @@ -134,62 +161,37 @@ public AbstractJsonSchema parse(JsonSchemaLocator locator, AbstractJsonSchemaEle } if (type == null) { - return new JsonMultitypeSchemaWrapper(parent, locator, jsonPointer, - vtype == ValueType.ARRAY ? type_value.asJsonArray() : null) - .read(this, object); - } - - JsonValue $id = object.get(JsonSchema.ID); - if ($id == null) { - $id = object.get("id"); // draft4 - } - - if ($id != null) { - if ($id.getValueType() != JsonValue.ValueType.STRING) { - throw new JsonSchemaException(new ParsingError(ParsingMessage.INVALID_ATTRIBUTE_TYPE, - "id", $id.getValueType().name(), JsonValue.ValueType.STRING.name())); - } else { - final String id = ((JsonString)$id).getString(); - try { - locator = locator.resolve(URI.create(id)); - locator.setSchema(object); - } catch(IllegalArgumentException ex) { - throw new JsonSchemaException(new ParsingError(ParsingMessage.INVALID_REFERENCE, id)); + schema = new JsonMultitypeSchemaWrapper(parent, locator, jsonPointer, + vtype == ValueType.ARRAY ? type_value.asJsonArray() : null); + } else { + final JsonArray jenum = JsonSchemaUtil.check(object.get(ENUM), JsonValue.ValueType.ARRAY); + if (jenum != null) { + if (jenum.isEmpty()) { + throw new JsonSchemaException(new ParsingError(ParsingMessage.EMPTY_ENUM)); } + return new JsonEnumImpl(parent, locator, jsonPointer).read(this, object); } - } - - final JsonArray jenum = JsonSchemaUtil.check(object.get(ENUM), JsonValue.ValueType.ARRAY); - if (jenum != null) { - if (jenum.isEmpty()) { - throw new JsonSchemaException(new ParsingError(ParsingMessage.EMPTY_ENUM)); - } - return new JsonEnumImpl(parent, locator, jsonPointer).read(this, object); - } - final JsonValue jconst = object.get(CONST); - if (jconst != null) { - return new JsonConstImpl(parent, locator, jsonPointer).read(this, object); - } + final JsonValue jconst = object.get(CONST); + if (jconst != null) { + return new JsonConstImpl(parent, locator, jsonPointer).read(this, object); + } - final AbstractJsonSchema schema; - switch(type) { - case OBJECT: schema = new JsonObjectSchemaImpl(parent, locator, jsonPointer); break; - case ARRAY: schema = new JsonArraySchemaImpl(parent, locator, jsonPointer); break; - case STRING: schema = new JsonStringSchemaImpl(parent, locator, jsonPointer); break; - case NUMBER: schema = new JsonNumberSchemaImpl(parent, locator, jsonPointer); break; - case INTEGER: schema = new JsonIntegerSchemaImpl(parent, locator, jsonPointer); break; - case BOOLEAN: schema = new JsonBooleanSchemaImpl(parent, locator, jsonPointer); break; - case NULL: schema = new JsonNullSchemaImpl(parent, locator, jsonPointer); break; - default: return null; + switch(type) { + case OBJECT: schema = new JsonObjectSchemaImpl(parent, locator, jsonPointer); break; + case ARRAY: schema = new JsonArraySchemaImpl(parent, locator, jsonPointer); break; + case STRING: schema = new JsonStringSchemaImpl(parent, locator, jsonPointer); break; + case NUMBER: schema = new JsonNumberSchemaImpl(parent, locator, jsonPointer); break; + case INTEGER: schema = new JsonIntegerSchemaImpl(parent, locator, jsonPointer); break; + case BOOLEAN: schema = new JsonBooleanSchemaImpl(parent, locator, jsonPointer); break; + case NULL: schema = new JsonNullSchemaImpl(parent, locator, jsonPointer); break; + default: return null; + } } AbstractJsonSchema sch = cache.get(schema); if (sch == null) { - sch = schema.read(this, object); - if (!sch.isDynamicScope()) { - cache.put(sch, sch); - } + sch = cache.put(schema.read(this, object)); } return sch; } diff --git a/src/main/java/es/elixir/bsc/json/schema/impl/JsonSchemaElementsCache.java b/src/main/java/es/elixir/bsc/json/schema/impl/JsonSchemaElementsCache.java new file mode 100644 index 0000000..cd09785 --- /dev/null +++ b/src/main/java/es/elixir/bsc/json/schema/impl/JsonSchemaElementsCache.java @@ -0,0 +1,139 @@ +/** + * ***************************************************************************** + * Copyright (C) 2024 ELIXIR ES, Spanish National Bioinformatics Institute (INB) + * and Barcelona Supercomputing Center (BSC) + * + * Modifications to the initial code base are copyright of their respective + * authors, or their employers as appropriate. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + ***************************************************************************** + */ + +package es.elixir.bsc.json.schema.impl; + +import es.elixir.bsc.json.schema.JsonSchemaLocator; +import es.elixir.bsc.json.schema.model.JsonReference; +import es.elixir.bsc.json.schema.model.impl.AbstractJsonSchema; +import es.elixir.bsc.json.schema.model.impl.AbstractJsonSchemaElement; +import es.elixir.bsc.json.schema.model.impl.JsonMultitypeSchemaWrapper; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +/** + * Simple HashMap based elements storage implementation. + * + * The cache doesn't store JsonMultitypeSchemaWrapper's children because all + * they have the same $id as a 'wrapper' an thus cached together. + * + * @author Dmitry Repchevsky + */ + +public class JsonSchemaElementsCache { + private final Map cache = new HashMap(); + + /** + * Get previously parsed JSON (sub)schema. + * + * @param locator the locator to the JSON schema document + * @param jsonPointer JSON pointer to the (sub)schema ("/" for the root) + * + * @return either found JSON schema or null if not found + */ + public AbstractJsonSchema get(JsonSchemaLocator locator, String jsonPointer) { + return cache.get(resolve(locator.uri, jsonPointer)); + } + + /** + * Get previously parsed JSON (sub)schema. + * + * @param schema JSON (sub)schema template (actually we need an $id of it) + * + * @return either found JSON schema or null if not found + */ + public AbstractJsonSchema get(AbstractJsonSchema schema) { + return schema.getParent() instanceof JsonMultitypeSchemaWrapper ? null : cache.get(schema.getId()); + } + + /** + * Put the JSON (sub)schema into the cache. + * + * @param schema (sub)schema to be put + * + * @return the same document we passed to the method + */ + public AbstractJsonSchema put(AbstractJsonSchema schema) { + if (!schema.isDynamicScope() && + !(schema.getParent() instanceof JsonMultitypeSchemaWrapper)) { + final URI id = schema.getId(); + cache.put(id, schema); + + // syntheticId is a real document path which might differ + // from the contextual $id + final URI syntheticId = getSyntheticId(schema); + if (!id.equals(syntheticId)) { + cache.put(syntheticId, schema); + } + } + return schema; + } + + /** + * Unlike {@code JsonSchemaElement.getId()}, synthetic identifier is + * relative to the document root. + *
+     * Example:
+     *   Both URI point to the same 'items' element.
+     *   While first one is the Json Schema $id, second is a JSON pointer in the
+     *   'scope_change_defs2.json' JSON document.
+     *   'http://localhost:1234/draft2019-09/baseUriChangeFolderInSubschema/#/$defs/bar/items'
+     *   'http://localhost:1234/draft2019-09/scope_change_defs2.json#/$defs/baz/$defs/bar/items'
+     * 
+ * @return synthetic identifier used to resolve external $refs. + */ + private URI getSyntheticId(AbstractJsonSchemaElement e) { + final StringBuilder sb = new StringBuilder(e.jsonPointer); + while (e.getParent() != null && !(e.getParent() instanceof JsonReference)) { + e = e.getParent(); + if (!(e instanceof JsonMultitypeSchemaWrapper) && + e.jsonPointer != e.getJsonPointer() && + e.jsonPointer.length() > 1) { + sb.insert(0, e.jsonPointer); + } + } + return resolve(e.locator.uri, sb.toString()); + } + + /** + * Resolves identifier with JSON pointer fragment part. + * + * @param uri schema identifier + * @param jsonPointer JSON pointer to the schema element + * + * @return schema element identifier + */ + private URI resolve(URI uri, String jsonPointer) { + final String fragment = uri.getFragment(); + try { + return new URI(uri.getScheme(), uri.getSchemeSpecificPart(), + fragment == null && jsonPointer.length() > 1 ? jsonPointer : jsonPointer.length() > 1 + ? fragment + jsonPointer : fragment); + } catch (URISyntaxException ex) {} + return null; + } +} diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonReferenceImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonReferenceImpl.java index fcfc561..ff91c1a 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonReferenceImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonReferenceImpl.java @@ -81,7 +81,7 @@ protected void read(JsonSubschemaParser parser, JsonObject object, String tag) ref_locator = locator; } else { ref_locator = locator.resolve( - new URI(ref.getScheme(), ref.getSchemeSpecificPart(), null)); + new URI(ref.getScheme(), ref.getSchemeSpecificPart(), null)); } } else { ref_pointer = "/"; diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchema.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchema.java index 23d41c2..f24d9de 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchema.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchema.java @@ -37,7 +37,7 @@ import javax.json.JsonValue; /** - * This interface is used internally and exists only for the purpose to hide + * This class is used internally and exists only for the purpose to hide * actual validate() method from the outside. * * @author Dmitry Repchevsky diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java index a98923c..7772fdd 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/AbstractJsonSchemaElement.java @@ -28,7 +28,7 @@ import es.elixir.bsc.json.schema.JsonSchemaLocator; import es.elixir.bsc.json.schema.model.JsonSchemaElement; import java.net.URI; -import java.util.Objects; +import java.net.URISyntaxException; /** * This is an root class that any JSON Schema element inherits from. @@ -68,12 +68,22 @@ public AbstractJsonSchemaElement(AbstractJsonSchemaElement parent, @Override public final URI getId() { - return locator.uri; + final String pointer = getJsonPointer(); + final String fragment = locator.uri.getFragment(); + try { + return new URI(locator.uri.getScheme(), locator.uri.getSchemeSpecificPart(), + fragment == null && pointer.length() > 1 ? pointer : pointer.length() > 1 + ? fragment + pointer : fragment); + } catch (URISyntaxException ex) {} + return null; } @Override public String getJsonPointer() { - return parent == null || parent.locator != locator ? "/" : jsonPointer; + if (parent instanceof JsonMultitypeSchemaWrapper) { + return parent.getJsonPointer(); + } + return parent != null && parent.locator.uri.equals(locator.uri) ? jsonPointer : "/"; } @Override @@ -81,7 +91,6 @@ public AbstractJsonSchemaElement getParent() { return parent; } - /** * This is a marker whether this element is in the dynamic scope and * must not be cached. @@ -95,28 +104,6 @@ public boolean isDynamicScope() { protected void setDynamicScope(boolean isDynamicScope) { this.isDynamicScope = isDynamicScope; } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj instanceof AbstractJsonSchemaElement other && - this.getClass() == obj.getClass()) { - return Objects.equals(jsonPointer, other.jsonPointer) && - Objects.equals(locator.uri, other.locator.uri); - } - return false; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 31 * hash + Objects.hashCode(this.locator.uri); - hash = 31 * hash + Objects.hashCode(this.jsonPointer); - hash = 31 * hash + Objects.hashCode(this.getClass().hashCode()); - return hash; - } @Override public Object clone() throws CloneNotSupportedException { @@ -124,11 +111,12 @@ public Object clone() throws CloneNotSupportedException { } /** - * Predicate to create a clone of this element linked another parent. + * Predicate to create a clone of this element linked to another parent. + * If the element's parent is the same as provided parent, no clone is created. * * @param parent - the parent to be assigned to the cloned element * - * @return the clone of this element + * @return the clone of this element or element itself if parents are the same */ protected AbstractJsonSchemaElement relink(AbstractJsonSchemaElement parent) { if (this.parent != parent) { diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/BooleanJsonSchemaImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/BooleanJsonSchemaImpl.java index 359300a..0fbdfca 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/BooleanJsonSchemaImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/BooleanJsonSchemaImpl.java @@ -54,7 +54,7 @@ public BooleanJsonSchemaImpl(AbstractJsonSchemaElement parent, JsonSchemaLocator locator, String jsonPointer) { super(parent, locator, jsonPointer); } - + @Override public Stream getChildren() { return Stream.empty(); diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonDynamicReferenceImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonDynamicReferenceImpl.java index b7c8df1..e7aa0c2 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonDynamicReferenceImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonDynamicReferenceImpl.java @@ -72,37 +72,41 @@ public AbstractJsonSchemaElement getSchema() throws JsonSchemaException { e = parser.parse(ref_locator, null, "/", value, null); } schema = getSchema(e, ref); + + // if no default '$dynamicAnchor' found - skip further + // '$dynamicAnchor'(s) search - treat as usual '$ref'. if (schema != null) { final URI uri = new URI(null, null, fragment); + e = this; while ((e = e.getParent()) != null) { - final AbstractJsonSchemaElement s = getSchema(e, uri); - if (s != null) { - schema = s; + if ("/".equals(e.getJsonPointer())) { + final AbstractJsonSchemaElement s = getSchema(e, uri); + if (s != null) { + schema = s; + } } - } } } catch (IOException | URISyntaxException ex) {} } } - + if (schema == null && super.getSchema() == null) { throw new JsonSchemaException( new ParsingError(ParsingMessage.UNRESOLVABLE_REFERENCE, ref)); - } - + } return schema; } private AbstractJsonSchemaElement getSchema(AbstractJsonSchemaElement e, URI uri) throws IOException, JsonSchemaException { final String fragment = uri.getFragment(); - final JsonSchemaLocator l = e.locator.resolve(uri); + final JsonSchemaLocator l = e.locator.resolve(uri); final JsonValue value = l.getSchema("/"); if (value instanceof JsonObject jsubschema) { final String anchor = jsubschema.getString(DYNAMIC_ANCHOR, null); if (fragment.equals(anchor)) { - return parser.parse(l, this, e.getJsonPointer(), jsubschema, null); + return parser.parse(l, this, "/", jsubschema, null); } } return null; diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonObjectSchemaImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonObjectSchemaImpl.java index 1b209f0..6ce688b 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonObjectSchemaImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonObjectSchemaImpl.java @@ -121,7 +121,7 @@ public StringArray getRequired() { public JsonProperties getDependentSchemas() { if (dependentSchemas == null) { dependentSchemas = new JsonPropertiesImpl(this, - locator, jsonPointer + "/" + DEPENDENT_SCHEMAS); + locator, getJsonPointer() + "/" + DEPENDENT_SCHEMAS); } return dependentSchemas; } @@ -130,7 +130,7 @@ public JsonProperties getDependentSchemas() { public JsonDependentProperties getDependentRequired() { if (dependentRequired == null) { dependentRequired = new JsonDependentPropertiesImpl(this, - locator, jsonPointer + "/" + DEPENDENT_REQUIRED); + locator, getJsonPointer() + "/" + DEPENDENT_REQUIRED); } return dependentRequired; } @@ -173,7 +173,7 @@ public JsonObjectSchemaImpl read(JsonSubschemaParser parser, JsonObject object) final JsonObject jproperties = JsonSchemaUtil.check(object.get(PROPERTIES), ValueType.OBJECT); if (jproperties != null) { - properties = new JsonPropertiesImpl(this, locator, jsonPointer + "/" + PROPERTIES) + properties = new JsonPropertiesImpl(this, locator, getJsonPointer() + "/" + PROPERTIES) .read(parser, jproperties); } @@ -189,7 +189,7 @@ public JsonObjectSchemaImpl read(JsonSubschemaParser parser, JsonObject object) final JsonObject jpatternProperties = JsonSchemaUtil.check(object.get(PATTERN_PROPERTIES), ValueType.OBJECT); if (jpatternProperties != null) { - patternProperties = new JsonPropertiesImpl(this, locator, jsonPointer + "/" + PATTERN_PROPERTIES) + patternProperties = new JsonPropertiesImpl(this, locator, getJsonPointer() + "/" + PATTERN_PROPERTIES) .read(parser, jpatternProperties); } @@ -228,13 +228,13 @@ public JsonObjectSchemaImpl read(JsonSubschemaParser parser, JsonObject object) final JsonObject jdependentSchemas = JsonSchemaUtil.check(object.get(DEPENDENT_SCHEMAS), ValueType.OBJECT); if (jdependentSchemas != null) { dependentSchemas = new JsonPropertiesImpl(this, locator, - jsonPointer + "/" + DEPENDENT_SCHEMAS).read(parser, jdependentSchemas); + getJsonPointer() + "/" + DEPENDENT_SCHEMAS).read(parser, jdependentSchemas); } final JsonObject jdependentRequired = JsonSchemaUtil.check(object.get(DEPENDENT_REQUIRED), ValueType.OBJECT); if (jdependentRequired != null) { dependentRequired = new JsonDependentPropertiesImpl(this, locator, - jsonPointer + "/" + DEPENDENT_REQUIRED).read(parser, jdependentRequired); + getJsonPointer() + "/" + DEPENDENT_REQUIRED).read(parser, jdependentRequired); } final JsonObject jdependencies = JsonSchemaUtil.check(object.get(DEPENDENCIES), ValueType.OBJECT); diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonReferenceImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonReferenceImpl.java index 2571044..b20fdff 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonReferenceImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonReferenceImpl.java @@ -32,6 +32,8 @@ import es.elixir.bsc.json.schema.impl.JsonSubschemaParser; import es.elixir.bsc.json.schema.model.JsonReference; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.util.stream.Stream; import javax.json.JsonException; import javax.json.JsonObject; @@ -78,9 +80,24 @@ public AbstractJsonSchemaElement getSchema() throws JsonSchemaException { throw new JsonSchemaException( new ParsingError(ParsingMessage.UNRESOLVABLE_REFERENCE, ref)); } + + AbstractJsonSchemaElement root = this; - schema = parser.parse(ref_locator, this, ref_pointer, jsubschema, null); - } catch(IOException | JsonException | IllegalArgumentException ex) { + if (!locator.uri.equals(ref_locator.uri)) { + // if the reference goes inside other document or points to tha 'anchor', + // parse this document as a 'parent' + if (ref_pointer.length() > 1) { + final JsonValue val = ref_locator.getSchema("/"); + root = parser.parse(ref_locator, this, "/", val, null); + } else if (ref_locator.uri.getFragment() != null) { + final JsonValue val = ref_locator.getSchema("/"); + root = parser.parse(ref_locator.resolve( + new URI(ref_locator.uri.getScheme(), ref_locator.uri.getSchemeSpecificPart(), null)), + this, "/", val, null); + } + } + schema = parser.parse(ref_locator, root, ref_pointer, jsubschema, null); + } catch(IOException | JsonException | IllegalArgumentException | URISyntaxException ex) { throw new JsonSchemaException( new ParsingError(ParsingMessage.INVALID_REFERENCE, ref)); } diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonStringSchemaImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonStringSchemaImpl.java index 6929636..6d75dff 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonStringSchemaImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/JsonStringSchemaImpl.java @@ -151,7 +151,7 @@ public boolean validate(String jsonPointer, JsonValue value, JsonValue parent, return nerrors == errors.size(); } - + private void validate(String jsonPointer, String string, List errors) { if (minLength != null && string.codePointCount(0, string.length()) < minLength) { diff --git a/src/main/java/es/elixir/bsc/json/schema/model/impl/PrimitiveSchemaImpl.java b/src/main/java/es/elixir/bsc/json/schema/model/impl/PrimitiveSchemaImpl.java index 4796cf2..a6f92ce 100644 --- a/src/main/java/es/elixir/bsc/json/schema/model/impl/PrimitiveSchemaImpl.java +++ b/src/main/java/es/elixir/bsc/json/schema/model/impl/PrimitiveSchemaImpl.java @@ -229,7 +229,7 @@ public PrimitiveSchemaImpl read(JsonSubschemaParser parser, JsonObject object) final JsonArray jallOf = JsonSchemaUtil.check(object.get(ALL_OF), JsonValue.ValueType.ARRAY); if (jallOf != null) { - final JsonAllOfImpl _allOf = new JsonAllOfImpl(this, locator, jsonPointer + "/" + ALL_OF) + final JsonAllOfImpl _allOf = new JsonAllOfImpl(this, locator, getJsonPointer() + "/" + ALL_OF) .read(parser, jallOf); if (allOf == null) { allOf = _allOf; @@ -242,13 +242,13 @@ public PrimitiveSchemaImpl read(JsonSubschemaParser parser, JsonObject object) final JsonArray janyOf = JsonSchemaUtil.check(object.get(ANY_OF), JsonValue.ValueType.ARRAY); if (janyOf != null) { - anyOf = new JsonAnyOfImpl(this, locator, jsonPointer + "/" + ANY_OF); + anyOf = new JsonAnyOfImpl(this, locator, getJsonPointer() + "/" + ANY_OF); anyOf.read(parser, janyOf); } final JsonArray joneOf = JsonSchemaUtil.check(object.get(ONE_OF), JsonValue.ValueType.ARRAY); if (joneOf != null) { - oneOf = new JsonOneOfImpl(this, locator, jsonPointer + "/" + ONE_OF); + oneOf = new JsonOneOfImpl(this, locator, getJsonPointer() + "/" + ONE_OF); oneOf.read(parser, joneOf); } @@ -257,7 +257,7 @@ public PrimitiveSchemaImpl read(JsonSubschemaParser parser, JsonObject object) switch(jnot.getValueType()) { case OBJECT: case TRUE: - case FALSE: not = new JsonNotImpl(this, locator, jsonPointer + "/" + NOT) + case FALSE: not = new JsonNotImpl(this, locator, getJsonPointer() + "/" + NOT) .read(parser, jnot); break; default: throw new JsonSchemaException(new ParsingError(ParsingMessage.INVALID_ATTRIBUTE_TYPE, diff --git a/src/test/java/es/elixir/bsc/json/schema/org/tests/JsonSchemaRefRemoteTest.java b/src/test/java/es/elixir/bsc/json/schema/org/tests/JsonSchemaRefRemoteTest.java index ea19e58..a6ad30b 100644 --- a/src/test/java/es/elixir/bsc/json/schema/org/tests/JsonSchemaRefRemoteTest.java +++ b/src/test/java/es/elixir/bsc/json/schema/org/tests/JsonSchemaRefRemoteTest.java @@ -25,6 +25,7 @@ package es.elixir.bsc.json.schema.org.tests; +import es.elixir.bsc.json.schema.JsonSchemaVersion; import org.junit.Test; /** @@ -33,9 +34,33 @@ public class JsonSchemaRefRemoteTest extends JsonSchemaOrgTest { private final static String JSON_DRAFT4_TEST_FILE = "json-schema-org/tests/draft4/refRemote.json"; + private final static String JSON_DRAFT6_TEST_FILE = "json-schema-org/tests/draft6/refRemote.json"; + private final static String JSON_DRAFT7_TEST_FILE = "json-schema-org/tests/draft7/refRemote.json"; + private final static String JSON_DRAFT201909_TEST_FILE = "json-schema-org/tests/draft2019-09/refRemote.json"; + private final static String JSON_DRAFT202012_TEST_FILE = "json-schema-org/tests/draft2020-12/refRemote.json"; -// @Test -// public void test_draft4() { -// test(JSON_DRAFT4_TEST_FILE); -// } + @Test + public void test_draft4() { + test(JSON_DRAFT4_TEST_FILE); + } + + @Test + public void test_draft6() { + test(JSON_DRAFT6_TEST_FILE, JsonSchemaVersion.SCHEMA_DRAFT_06); + } + + @Test + public void test_draft7() { + test(JSON_DRAFT7_TEST_FILE, JsonSchemaVersion.SCHEMA_DRAFT_07); + } + + @Test + public void test_draft201909() { + test(JSON_DRAFT201909_TEST_FILE, JsonSchemaVersion.SCHEMA_DRAFT_2019_09); + } + + @Test + public void test_draft202012() { + test(JSON_DRAFT202012_TEST_FILE, JsonSchemaVersion.SCHEMA_DRAFT_2020_12); + } }