Skip to content

Commit

Permalink
Added JsonValidator#preloadJsonSchema() to allow eager preloading of …
Browse files Browse the repository at this point in the history
…schemas. (#410)

Co-authored-by: Florian Wiesner <Florian [email protected]>
  • Loading branch information
FWiesner and Florian Wiesner authored Jun 21, 2021
1 parent faec7f1 commit ed8d440
Show file tree
Hide file tree
Showing 24 changed files with 249 additions and 57 deletions.
4 changes: 4 additions & 0 deletions doc/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ class Sample extends BaseJsonSchemaValidatorTest {
JsonNode schemaNode = getJsonNodeFromStringContent(
"{\"$schema\": \"http://json-schema.org/draft-06/schema#\", \"properties\": { \"id\": {\"type\": \"number\"}}}");
JsonSchema schema = getJsonSchemaFromJsonNodeAutomaticVersion(schemaNode);

schema.initializeValidators(); // by default all schemas are loaded lazily. You can load them eagerly via
// initializeValidators()

JsonNode node = getJsonNodeFromStringContent("{\"id\": \"2\"}");
Set<ValidationMessage> errors = schema.validate(node);
assertThat(errors.size(), is(1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,8 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
return Collections.unmodifiableSet(errors);
}

@Override
public void preloadJsonSchema() {
additionalPropertiesSchema.initializeValidators();
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/networknt/schema/AllOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,8 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
return Collections.unmodifiableSet(validationMessages);
}

@Override
public void preloadJsonSchema() {
preloadJsonSchemas(schemas);
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/networknt/schema/AnyOfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,9 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
}
return Collections.unmodifiableSet(allErrors);
}

@Override
public void preloadJsonSchema() {
preloadJsonSchemas(schemas);
}
}
13 changes: 12 additions & 1 deletion src/main/java/com/networknt/schema/BaseJsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.networknt.schema;

import java.net.URI;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
Expand Down Expand Up @@ -164,6 +165,17 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
return validationMessages;
}

@Override
public void preloadJsonSchema() {
// do nothing by default - to be overridden in subclasses
}

protected void preloadJsonSchemas(final Collection<JsonSchema> schemas) {
for (final JsonSchema schema: schemas) {
schema.initializeValidators();
}
}

protected boolean isPartOfOneOfMultipleType() {
return parentSchema.schemaPath.equals(ValidatorTypeCode.ONE_OF.getValue());
}
Expand Down Expand Up @@ -285,5 +297,4 @@ private static boolean noExplicitDiscriminatorKeyOverride(final JsonNode discrim
}
return true;
}

}
10 changes: 9 additions & 1 deletion src/main/java/com/networknt/schema/ContainsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
public class ContainsValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(ContainsValidator.class);

private JsonSchema schema;
private final JsonSchema schema;

public ContainsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.CONTAINS, validationContext);
if (schemaNode.isObject() || schemaNode.isBoolean()) {
schema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), schemaNode, parentSchema);
} else {
schema = null;
}

parseErrorCode(getValidatorType().getErrorCodeKey());
Expand Down Expand Up @@ -70,4 +72,10 @@ private Set<ValidationMessage> buildErrorMessageSet(String at) {
return Collections.singleton(buildValidationMessage(at, schema.getSchemaNode().toString()));
}

@Override
public void preloadJsonSchema() {
if (null != schema) {
schema.initializeValidators();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
public class DependenciesValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(DependenciesValidator.class);
private final Map<String, List<String>> propertyDeps = new HashMap<String, List<String>>();
private Map<String, JsonSchema> schemaDeps = new HashMap<String, JsonSchema>();
private final Map<String, JsonSchema> schemaDeps = new HashMap<String, JsonSchema>();

public DependenciesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {

Expand Down Expand Up @@ -75,4 +75,8 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
return Collections.unmodifiableSet(errors);
}

@Override
public void preloadJsonSchema() {
preloadJsonSchemas(schemaDeps.values());
}
}
32 changes: 26 additions & 6 deletions src/main/java/com/networknt/schema/IfValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,31 @@ public class IfValidator extends BaseJsonValidator implements JsonValidator {

private static final ArrayList<String> KEYWORDS = new ArrayList<String>(Arrays.asList("if", "then", "else"));

private JsonSchema ifSchema;
private JsonSchema thenSchema;
private JsonSchema elseSchema;
private final JsonSchema ifSchema;
private final JsonSchema thenSchema;
private final JsonSchema elseSchema;

public IfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.IF_THEN_ELSE, validationContext);

JsonSchema foundIfSchema = null;
JsonSchema foundThenSchema = null;
JsonSchema foundElseSchema = null;

for (final String keyword : KEYWORDS) {
final JsonNode node = schemaNode.get(keyword);
if (keyword.equals("if")) {
ifSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema);
foundIfSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema);
} else if (keyword.equals("then") && node != null) {
thenSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema);
foundThenSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema);
} else if (keyword.equals("else") && node != null) {
elseSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema);
foundElseSchema = new JsonSchema(validationContext, getValidatorType().getValue(), parentSchema.getCurrentUri(), node, parentSchema);
}
}

ifSchema = foundIfSchema;
thenSchema = foundThenSchema;
elseSchema = foundElseSchema;
}

public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
Expand All @@ -61,4 +69,16 @@ public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String
return Collections.unmodifiableSet(errors);
}

@Override
public void preloadJsonSchema() {
if(null != ifSchema) {
ifSchema.initializeValidators();
}
if(null != thenSchema) {
thenSchema.initializeValidators();
}
if(null != elseSchema) {
elseSchema.initializeValidators();
}
}
}
32 changes: 24 additions & 8 deletions src/main/java/com/networknt/schema/ItemsValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,24 @@ public class ItemsValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(ItemsValidator.class);
private static final String PROPERTY_ADDITIONAL_ITEMS = "additionalItems";

private JsonSchema schema;
private List<JsonSchema> tupleSchema;
private final JsonSchema schema;
private final List<JsonSchema> tupleSchema;
private boolean additionalItems = true;
private JsonSchema additionalSchema;
private WalkListenerRunner arrayItemWalkListenerRunner;
private ValidationContext validationContext;
private final JsonSchema additionalSchema;
private final WalkListenerRunner arrayItemWalkListenerRunner;
private final ValidationContext validationContext;

public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
tupleSchema = new ArrayList<JsonSchema>();
JsonSchema foundSchema = null;
JsonSchema foundAdditionalSchema = null;

if (schemaNode.isObject() || schemaNode.isBoolean()) {
schema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode,
foundSchema = new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), schemaNode,
parentSchema);
} else {
tupleSchema = new ArrayList<JsonSchema>();
for (JsonNode s : schemaNode) {
tupleSchema.add(
new JsonSchema(validationContext, schemaPath, parentSchema.getCurrentUri(), s, parentSchema));
Expand All @@ -54,7 +57,7 @@ public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
if (addItemNode.isBoolean()) {
additionalItems = addItemNode.asBoolean();
} else if (addItemNode.isObject()) {
additionalSchema = new JsonSchema(validationContext, parentSchema.getCurrentUri(), addItemNode);
foundAdditionalSchema = new JsonSchema(validationContext, parentSchema.getCurrentUri(), addItemNode);
}
}
}
Expand All @@ -63,6 +66,9 @@ public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentS
this.validationContext = validationContext;

parseErrorCode(getValidatorType().getErrorCodeKey());

this.schema = foundSchema;
this.additionalSchema = foundAdditionalSchema;
}

public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
Expand Down Expand Up @@ -168,4 +174,14 @@ public JsonSchema getSchema() {
return schema;
}

@Override
public void preloadJsonSchema() {
if (null != schema) {
schema.initializeValidators();
}
preloadJsonSchemas(tupleSchema);
if (null != additionalSchema) {
additionalSchema.initializeValidators();
}
}
}
37 changes: 28 additions & 9 deletions src/main/java/com/networknt/schema/JsonSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,26 @@

package com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.ValidationContext.DiscriminatorContext;
import com.networknt.schema.walk.DefaultKeywordWalkListenerRunner;
import com.networknt.schema.walk.JsonSchemaWalker;
import com.networknt.schema.walk.WalkListenerRunner;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.networknt.schema.ValidationContext.DiscriminatorContext;
import com.networknt.schema.walk.DefaultKeywordWalkListenerRunner;
import com.networknt.schema.walk.JsonSchemaWalker;
import com.networknt.schema.walk.WalkListenerRunner;

/**
* This is the core of json constraint implementation. It parses json constraint
* file and generates JsonValidators. The class is thread safe, once it is
Expand Down Expand Up @@ -253,7 +258,7 @@ public Set<ValidationMessage> validate(JsonNode jsonNode, JsonNode rootNode, Str
// used for validation before allOf validation has kicked in
discriminatorContext.registerDiscriminator(schemaPath, discriminator);
discriminatorToUse = discriminator;
} else{
} else {
discriminatorToUse = discriminatorFromContext;
}

Expand Down Expand Up @@ -408,4 +413,18 @@ public Map<String, JsonValidator> getValidators() {
return validators;
}

/**
* Initializes the validators' {@link com.networknt.schema.JsonSchema} instances.
* For avoiding issues with concurrency, in 1.0.49 the {@link com.networknt.schema.JsonSchema} instances affiliated with
* validators were modified to no more preload the schema and lazy loading is used instead.
* <p>This comes with the issue that this way you cannot rely on validating important schema features, in particular
* <code>$ref</code> resolution at instantiation from {@link com.networknt.schema.JsonSchemaFactory}.</p>
* <p>By calling <code>initializeValidators</code> you can enforce preloading of the {@link com.networknt.schema.JsonSchema}
* instances of the validators.</p>
*/
public void initializeValidators() {
for (final JsonValidator validator : getValidators().values()) {
validator.preloadJsonSchema();
}
}
}
17 changes: 12 additions & 5 deletions src/main/java/com/networknt/schema/JsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@

package com.networknt.schema;

import java.util.Set;

import com.fasterxml.jackson.databind.JsonNode;
import com.networknt.schema.walk.JsonSchemaWalker;

import java.util.Set;

/**
* Standard json validator interface, implemented by all validators and JsonSchema.
*/
public interface JsonValidator extends JsonSchemaWalker {
public static final String AT_ROOT = "$";

String AT_ROOT = "$";

/**
* Validate the given root JsonNode, starting at the root of the data path.
Expand All @@ -48,6 +47,14 @@ public interface JsonValidator extends JsonSchemaWalker {
* list if there is no error.
*/
Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at);


/**
* In case the {@link com.networknt.schema.JsonValidator} has a related {@link com.networknt.schema.JsonSchema} or several
* ones, calling preloadJsonSchema will actually load the schema document(s) eagerly.
*
* @throws JsonSchemaException (a {@link java.lang.RuntimeException}) in case the {@link com.networknt.schema.JsonSchema} or nested schemas
* are invalid (like <code>$ref</code> not resolving)
* @since 1.0.54
*/
void preloadJsonSchema() throws JsonSchemaException;
}
5 changes: 5 additions & 0 deletions src/main/java/com/networknt/schema/NonValidationKeyword.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ private Validator(String keyword) {
public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
return Collections.emptySet();
}

@Override
public void preloadJsonSchema() {
// not used and the Validator is not extending from BaseJsonValidator
}
}

public NonValidationKeyword(String keyword) {
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/networknt/schema/NotValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
public class NotValidator extends BaseJsonValidator implements JsonValidator {
private static final Logger logger = LoggerFactory.getLogger(RequiredValidator.class);

private JsonSchema schema;
private final JsonSchema schema;

public NotValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT, validationContext);
Expand All @@ -51,4 +51,10 @@ public Set<ValidationMessage> walk(JsonNode node, JsonNode rootNode, String at,
return schema.walk(node, rootNode, at, shouldValidateSchema);
}

@Override
public void preloadJsonSchema() {
if (null != schema) {
schema.initializeValidators();
}
}
}
Loading

0 comments on commit ed8d440

Please sign in to comment.