Skip to content

Commit

Permalink
0.5.5
Browse files Browse the repository at this point in the history
  • Loading branch information
redmitry committed Sep 7, 2024
1 parent 62b32f5 commit 5bd0eac
Show file tree
Hide file tree
Showing 14 changed files with 290 additions and 111 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
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
<dependencies>
<dependency>
<groupId>es.elixir.bsc.json.schema</groupId>
<artifactId>jaronuinga</artifactId>
<version>0.5.4</version>
<version>0.5.5</version>
</dependency>
...
<repositories>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

<groupId>es.elixir.bsc.json.schema</groupId>
<artifactId>jaronuinga</artifactId>
<version>0.5.5-SNAPSHOT</version>
<version>0.5.5</version>
<packaging>jar</packaging>

<organization>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,22 @@
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
*/

public class DefaultJsonSchemaParser implements JsonSubschemaParser {

private final JsonSchemaElementsCache cache = new JsonSchemaElementsCache();
private final Map<String, Object> properties;
private final Map<AbstractJsonSchema, AbstractJsonSchema> cache = new WeakHashMap();

public DefaultJsonSchemaParser(Map<String, Object> properties) {
this.properties = properties;
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<URI, AbstractJsonSchema> 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.
* <pre>
* 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'
* </pre>
* @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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "/";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 5bd0eac

Please sign in to comment.