Skip to content

Commit

Permalink
Support GraalVM and refactor (#972)
Browse files Browse the repository at this point in the history
* Add native-image properties

* Add meta schema mapper

* Only create objects if needed

* Defer some processing

* Refactor

* Refactor

* Refactor

* Refactor

* Refactor
  • Loading branch information
justin-tay authored Feb 19, 2024
1 parent bd085ed commit 53a3402
Show file tree
Hide file tree
Showing 21 changed files with 381 additions and 349 deletions.
6 changes: 3 additions & 3 deletions src/main/java/com/networknt/schema/AbstractJsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public abstract class AbstractJsonValidator implements JsonValidator {
private final SchemaLocation schemaLocation;
private final JsonNode schemaNode;
private final JsonNodePath evaluationPath;
private final Keyword keyword;
private final String keyword;

/**
* Constructor.
Expand All @@ -41,7 +41,7 @@ public abstract class AbstractJsonValidator implements JsonValidator {
public AbstractJsonValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, Keyword keyword, JsonNode schemaNode) {
this.schemaLocation = schemaLocation;
this.evaluationPath = evaluationPath;
this.keyword = keyword;
this.keyword = keyword.getValue();
this.schemaNode = schemaNode;
}

Expand All @@ -57,7 +57,7 @@ public JsonNodePath getEvaluationPath() {

@Override
public String getKeyword() {
return keyword.getValue();
return keyword;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

/**
* {@link JsonValidator} for additionalProperties.
Expand All @@ -34,7 +41,7 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
private final boolean allowAdditionalProperties;
private final JsonSchema additionalPropertiesSchema;
private final Set<String> allowedProperties;
private final List<RegularExpression> patternProperties = new ArrayList<>();
private final List<RegularExpression> patternProperties;

private Boolean hasUnevaluatedPropertiesValidator;

Expand Down Expand Up @@ -64,9 +71,12 @@ public AdditionalPropertiesValidator(SchemaLocation schemaLocation, JsonNodePath

JsonNode patternPropertiesNode = parentSchema.getSchemaNode().get(PatternPropertiesValidator.PROPERTY);
if (patternPropertiesNode != null) {
this.patternProperties = new ArrayList<>();
for (Iterator<String> it = patternPropertiesNode.fieldNames(); it.hasNext(); ) {
patternProperties.add(RegularExpression.compile(it.next(), validationContext));
}
} else {
this.patternProperties = Collections.emptyList();
}
}

Expand All @@ -93,8 +103,9 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo

Set<ValidationMessage> errors = null;

for (Iterator<String> it = node.fieldNames(); it.hasNext(); ) {
String pname = it.next();
for (Iterator<Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
Entry<String, JsonNode> entry = it.next();
String pname = entry.getKey();
// skip the context items
if (pname.startsWith("#")) {
continue;
Expand All @@ -120,15 +131,18 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
if (additionalPropertiesSchema != null) {
ValidatorState state = executionContext.getValidatorState();
if (state != null && state.isWalkEnabled()) {
Set<ValidationMessage> results = additionalPropertiesSchema.walk(executionContext, node.get(pname), rootNode, instanceLocation.append(pname), state.isValidationEnabled());
Set<ValidationMessage> results = additionalPropertiesSchema.walk(executionContext,
entry.getValue(), rootNode, instanceLocation.append(pname),
state.isValidationEnabled());
if (!results.isEmpty()) {
if (errors == null) {
errors = new LinkedHashSet<>();
}
errors.addAll(results);
}
} else {
Set<ValidationMessage> results = additionalPropertiesSchema.validate(executionContext, node.get(pname), rootNode, instanceLocation.append(pname));
Set<ValidationMessage> results = additionalPropertiesSchema.validate(executionContext,
entry.getValue(), rootNode, instanceLocation.append(pname));
if (!results.isEmpty()) {
if (errors == null) {
errors = new LinkedHashSet<>();
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/com/networknt/schema/AnnotationKeyword.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2016 Network New Technologies Inc.
*
* 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 com.networknt.schema;

import com.fasterxml.jackson.databind.JsonNode;

import java.util.Collections;
import java.util.Set;

/**
* Used for Keywords that have no validation aspect, but are part of the metaschema, where annotations may need to be collected.
*/
public class AnnotationKeyword extends AbstractKeyword {

private static final class Validator extends AbstractJsonValidator {
public Validator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
JsonSchema parentSchema, ValidationContext validationContext, Keyword keyword) {
super(schemaLocation, evaluationPath, keyword, schemaNode);
}

@Override
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
if (collectAnnotations(executionContext)) {
Object value = getAnnotationValue(getSchemaNode());
if (value != null) {
putAnnotation(executionContext,
annotation -> annotation.instanceLocation(instanceLocation).value(value));
}
}
return Collections.emptySet();
}

protected Object getAnnotationValue(JsonNode schemaNode) {
if (schemaNode.isTextual()) {
return schemaNode.textValue();
} else if (schemaNode.isNumber()) {
return schemaNode.numberValue();
} else if (schemaNode.isObject()) {
return schemaNode;
}
return null;
}
}

public AnnotationKeyword(String keyword) {
super(keyword);
}

@Override
public JsonValidator newValidator(SchemaLocation schemaLocation, JsonNodePath evaluationPath, JsonNode schemaNode,
JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception {
return new Validator(schemaLocation, evaluationPath, schemaNode, parentSchema, validationContext, this);
}
}
28 changes: 28 additions & 0 deletions src/main/java/com/networknt/schema/BaseJsonValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,40 @@ protected void preloadJsonSchemas(final Collection<JsonSchema> schemas) {
}
}

public static class JsonNodePathLegacy {
private static final JsonNodePath INSTANCE = new JsonNodePath(PathType.LEGACY);
public static JsonNodePath getInstance() {
return INSTANCE;
}
}

public static class JsonNodePathJsonPointer {
private static final JsonNodePath INSTANCE = new JsonNodePath(PathType.JSON_POINTER);
public static JsonNodePath getInstance() {
return INSTANCE;
}
}

public static class JsonNodePathJsonPath {
private static final JsonNodePath INSTANCE = new JsonNodePath(PathType.JSON_PATH);
public static JsonNodePath getInstance() {
return INSTANCE;
}
}

/**
* Get the root path.
*
* @return The path.
*/
protected JsonNodePath atRoot() {
if (this.validationContext.getConfig().getPathType().equals(PathType.JSON_POINTER)) {
return JsonNodePathJsonPointer.getInstance();
} else if (this.validationContext.getConfig().getPathType().equals(PathType.LEGACY)) {
return JsonNodePathLegacy.getInstance();
} else if (this.validationContext.getConfig().getPathType().equals(PathType.JSON_PATH)) {
return JsonNodePathJsonPath.getInstance();
}
return new JsonNodePath(this.validationContext.getConfig().getPathType());
}

Expand Down
30 changes: 23 additions & 7 deletions src/main/java/com/networknt/schema/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
*/
public class ExecutionContext {
private ExecutionConfig executionConfig;
private CollectorContext collectorContext;
private CollectorContext collectorContext = null;
private ValidatorState validatorState = null;
private Stack<DiscriminatorContext> discriminatorContexts = new Stack<>();
private JsonNodeAnnotations annotations = new JsonNodeAnnotations();
private JsonNodeResults results = new JsonNodeResults();
private Stack<DiscriminatorContext> discriminatorContexts = null;
private JsonNodeAnnotations annotations = null;
private JsonNodeResults results = null;

/**
* This is used during the execution to determine if the validator should fail fast.
Expand All @@ -43,7 +43,7 @@ public class ExecutionContext {
* Creates an execution context.
*/
public ExecutionContext() {
this(new CollectorContext());
this(new ExecutionConfig(), null);
}

/**
Expand All @@ -61,7 +61,7 @@ public ExecutionContext(CollectorContext collectorContext) {
* @param executionConfig the execution configuration
*/
public ExecutionContext(ExecutionConfig executionConfig) {
this(executionConfig, new CollectorContext());
this(executionConfig, null);
}

/**
Expand All @@ -81,7 +81,10 @@ public ExecutionContext(ExecutionConfig executionConfig, CollectorContext collec
* @return the collector context
*/
public CollectorContext getCollectorContext() {
return collectorContext;
if (this.collectorContext == null) {
this.collectorContext = new CollectorContext();
}
return this.collectorContext;
}

/**
Expand Down Expand Up @@ -112,10 +115,16 @@ public void setExecutionConfig(ExecutionConfig executionConfig) {
}

public JsonNodeAnnotations getAnnotations() {
if (this.annotations == null) {
this.annotations = new JsonNodeAnnotations();
}
return annotations;
}

public JsonNodeResults getResults() {
if (this.results == null) {
this.results = new JsonNodeResults();
}
return results;
}

Expand Down Expand Up @@ -163,13 +172,20 @@ public void setValidatorState(ValidatorState validatorState) {
}

public DiscriminatorContext getCurrentDiscriminatorContext() {
if (this.discriminatorContexts == null) {
return null;
}

if (!this.discriminatorContexts.empty()) {
return this.discriminatorContexts.peek();
}
return null; // this is the case when we get on a schema that has a discriminator, but it's not used in anyOf
}

public void enterDiscriminatorContext(final DiscriminatorContext ctx, @SuppressWarnings("unused") JsonNodePath instanceLocation) {
if (this.discriminatorContexts == null) {
this.discriminatorContexts = new Stack<>();
}
this.discriminatorContexts.push(ctx);
}

Expand Down
28 changes: 17 additions & 11 deletions src/main/java/com/networknt/schema/JsonSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;

/**
Expand Down Expand Up @@ -451,10 +459,11 @@ private List<JsonValidator> read(JsonNode schemaNode) {
} else {
JsonValidator refValidator = null;

Iterator<String> pnames = schemaNode.fieldNames();
while (pnames.hasNext()) {
String pname = pnames.next();
JsonNode nodeToUse = schemaNode.get(pname);
Iterator<Entry<String, JsonNode>> iterator = schemaNode.fields();
while (iterator.hasNext()) {
Entry<String, JsonNode> entry = iterator.next();
String pname = entry.getKey();
JsonNode nodeToUse = entry.getValue();

JsonNodePath path = getEvaluationPath().append(pname);
SchemaLocation schemaPath = getSchemaLocation().append(pname);
Expand Down Expand Up @@ -536,15 +545,14 @@ private long activeDialect() {

@Override
public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, JsonNodePath instanceLocation) {
if (validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
ObjectNode discriminator = (ObjectNode) schemaNode.get("discriminator");
if (null != discriminator && null != executionContext.getCurrentDiscriminatorContext()) {
executionContext.getCurrentDiscriminatorContext().registerDiscriminator(schemaLocation,
discriminator);
}
}

SchemaValidatorsConfig config = this.validationContext.getConfig();
SetView<ValidationMessage> errors = null;
// Set the walkEnabled and isValidationEnabled flag in internal validator state.
setValidatorState(executionContext, false, true);
Expand All @@ -566,7 +574,7 @@ public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNo
}
}

if (config.isOpenAPI3StyleDiscriminators()) {
if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
ObjectNode discriminator = (ObjectNode) this.schemaNode.get("discriminator");
if (null != discriminator) {
final DiscriminatorContext discriminatorContext = executionContext
Expand Down Expand Up @@ -1137,15 +1145,13 @@ public boolean isRecursiveAnchor() {
*/
public ExecutionContext createExecutionContext() {
SchemaValidatorsConfig config = validationContext.getConfig();
CollectorContext collectorContext = new CollectorContext();

// Copy execution config defaults from validation config
ExecutionConfig executionConfig = new ExecutionConfig();
executionConfig.setLocale(config.getLocale());
executionConfig.setFormatAssertionsEnabled(config.getFormatAssertionsEnabled());
executionConfig.setFailFast(config.isFailFast());

ExecutionContext executionContext = new ExecutionContext(executionConfig, collectorContext);
ExecutionContext executionContext = new ExecutionContext(executionConfig);
if(config.getExecutionContextCustomizer() != null) {
config.getExecutionContextCustomizer().customize(executionContext, validationContext);
}
Expand Down
Loading

0 comments on commit 53a3402

Please sign in to comment.