Repository: networknt/json-schema-validator
Pull request #1056 (2024-06-11 00:26:07)

Release delegate held by cached supplier
Change to allow the delegate to be garbage collected.

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1056?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 78.74%. Comparing base [(`48ca3c2`)](https://app.codecov.io/gh/networknt/json-schema-validator/commit/48ca3c22995e32797e9defdb8b77869f0d15caf3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) to head [(`9872251`)](https://app.codecov.io/gh/networknt/json-schema-validator/commit/9872251448b8f4eefbee3a88f18d99092f7d0aee?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).
> Report is 21 commits behind head on master.


:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #1056      +/-   ##
============================================
- Coverage     78.90%   78.74%   -0.16%     
- Complexity     1965     2033      +68     
============================================
  Files           172      191      +19     
  Lines          6352     6601     +249     
  Branches       1255     1261       +6     
============================================
+ Hits           5012     5198     +186     
- Misses          867      927      +60     
- Partials        473      476       +3     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1056?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).


Patch:
diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
index 48b5f7d..01fb252 100644
--- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
@@ -80,7 +80,14 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
         }
     }
 
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+    @Override
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
         debug(logger, node, rootNode, instanceLocation);
         if (!node.isObject()) {
             // ignore no object
@@ -110,15 +117,7 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
             if (pname.startsWith("#")) {
                 continue;
             }
-            boolean handledByPatternProperties = false;
-            for (RegularExpression pattern : patternProperties) {
-                if (pattern.matches(pname)) {
-                    handledByPatternProperties = true;
-                    break;
-                }
-            }
-
-            if (!allowedProperties.contains(pname) && !handledByPatternProperties) {
+            if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) {
                 if (!allowAdditionalProperties) {
                     if (errors == null) {
                         errors = new LinkedHashSet<>();
@@ -129,26 +128,16 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
                             .failFast(executionContext.isFailFast()).arguments(pname).build());
                 } else {
                     if (additionalPropertiesSchema != null) {
-                        ValidatorState state = executionContext.getValidatorState();
-                        if (state != null && state.isWalkEnabled()) {
-                            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,
-                                    entry.getValue(), rootNode, instanceLocation.append(pname));
-                            if (!results.isEmpty()) {
-                                if (errors == null) {
-                                    errors = new LinkedHashSet<>();
-                                }
-                                errors.addAll(results);
+                        Set<ValidationMessage> results = !walk
+                                ? additionalPropertiesSchema.validate(executionContext, entry.getValue(), rootNode,
+                                        instanceLocation.append(pname))
+                                : additionalPropertiesSchema.walk(executionContext, entry.getValue(), rootNode,
+                                        instanceLocation.append(pname), true);
+                        if (!results.isEmpty()) {
+                            if (errors == null) {
+                                errors = new LinkedHashSet<>();
                             }
+                            errors.addAll(results);
                         }
                     }
                 }
@@ -166,7 +155,7 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema && node != null) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
 
         if (node == null || !node.isObject()) {
@@ -181,21 +170,11 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
             if (pname.startsWith("#")) {
                 continue;
             }
-            boolean handledByPatternProperties = false;
-            for (RegularExpression pattern : patternProperties) {
-                if (pattern.matches(pname)) {
-                    handledByPatternProperties = true;
-                    break;
-                }
-            }
-
-            if (!allowedProperties.contains(pname) && !handledByPatternProperties) {
+            if (!allowedProperties.contains(pname) && !handledByPatternProperties(pname)) {
                 if (allowAdditionalProperties) {
                     if (additionalPropertiesSchema != null) {
-                        ValidatorState state = executionContext.getValidatorState();
-                        if (state != null && state.isWalkEnabled()) {
-                           additionalPropertiesSchema.walk(executionContext, node.get(pname), rootNode, instanceLocation.append(pname), state.isValidationEnabled());
-                        }
+                        additionalPropertiesSchema.walk(executionContext, node.get(pname), rootNode,
+                                instanceLocation.append(pname), shouldValidateSchema);
                     }
                 }
             }
@@ -203,6 +182,15 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator {
         return Collections.emptySet();
     }
 
+    private boolean handledByPatternProperties(String pname) {
+        for (RegularExpression pattern : this.patternProperties) {
+            if (pattern.matches(pname)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean collectAnnotations() {
         return hasUnevaluatedPropertiesValidator();
     }
diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java
index b451982..2d0e09d 100644
--- a/src/main/java/com/networknt/schema/AllOfValidator.java
+++ b/src/main/java/com/networknt/schema/AllOfValidator.java
@@ -52,17 +52,18 @@ public class AllOfValidator extends BaseJsonValidator {
 
     @Override
     public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
 
-        // get the Validator state object storing validation data
-        ValidatorState state = executionContext.getValidatorState();
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean walk) {
+        debug(logger, node, rootNode, instanceLocation);
 
         SetView<ValidationMessage> childSchemaErrors = null;
 
         for (JsonSchema schema : this.schemas) {
             Set<ValidationMessage> localErrors = null;
 
-            if (!state.isWalkEnabled()) {
+            if (!walk) {
                 localErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
             } else {
                 localErrors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
@@ -111,7 +112,7 @@ public class AllOfValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
         for (JsonSchema schema : this.schemas) {
             // Walk through the schema
diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java
index 5dac253..ba31050 100644
--- a/src/main/java/com/networknt/schema/AnyOfValidator.java
+++ b/src/main/java/com/networknt/schema/AnyOfValidator.java
@@ -53,18 +53,18 @@ public class AnyOfValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
-        debug(logger, node, rootNode, instanceLocation);
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
 
-        // get the Validator state object storing validation data
-        ValidatorState state = executionContext.getValidatorState();
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
+        debug(logger, node, rootNode, instanceLocation);
 
         if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
             executionContext.enterDiscriminatorContext(new DiscriminatorContext(), instanceLocation);
         }
-
-        boolean initialHasMatchedNode = state.hasMatchedNode();
-
         SetView<ValidationMessage> allErrors = null;
 
         int numberOfValidSubSchemas = 0;
@@ -75,8 +75,6 @@ public class AnyOfValidator extends BaseJsonValidator {
                 executionContext.setFailFast(false);
                 for (JsonSchema schema : this.schemas) {
                     Set<ValidationMessage> errors = Collections.emptySet();
-                    state.setMatchedNode(initialHasMatchedNode);
-
                     TypeValidator typeValidator = schema.getTypeValidator();
                     if (typeValidator != null) {
                         // If schema has type validator and node type doesn't match with schemaType then
@@ -90,7 +88,7 @@ public class AnyOfValidator extends BaseJsonValidator {
                             continue;
                         }
                     }
-                    if (!state.isWalkEnabled()) {
+                    if (!walk) {
                         errors = schema.validate(executionContext, node, rootNode, instanceLocation);
                     } else {
                         errors = schema.walk(executionContext, node, rootNode, instanceLocation, true);
@@ -98,11 +96,6 @@ public class AnyOfValidator extends BaseJsonValidator {
 
                     // check if any validation errors have occurred
                     if (errors.isEmpty()) {
-                        // check whether there are no errors HOWEVER we have validated the exact
-                        // validator
-                        if (!state.hasMatchedNode()) {
-                            continue;
-                        }
                         // we found a valid subschema, so increase counter
                         numberOfValidSubSchemas++;
                     }
@@ -159,9 +152,6 @@ public class AnyOfValidator extends BaseJsonValidator {
             if (this.validationContext.getConfig().isOpenAPI3StyleDiscriminators()) {
                 executionContext.leaveDiscriminatorContextImmediately(instanceLocation);
             }
-            if (allErrors == null || allErrors.isEmpty()) {
-                state.setMatchedNode(true);
-            }
         }
         if (numberOfValidSubSchemas >= 1) {
             return Collections.emptySet();
@@ -172,7 +162,7 @@ public class AnyOfValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
         for (JsonSchema schema : this.schemas) {
             schema.walk(executionContext, node, rootNode, instanceLocation, false);
diff --git a/src/main/java/com/networknt/schema/CachedSupplier.java b/src/main/java/com/networknt/schema/CachedSupplier.java
index 109f36c..4411bdd 100644
--- a/src/main/java/com/networknt/schema/CachedSupplier.java
+++ b/src/main/java/com/networknt/schema/CachedSupplier.java
@@ -23,8 +23,8 @@ import java.util.function.Supplier;
  * @param <T> the type cached
  */
 public class CachedSupplier<T> implements Supplier<T> {
-    private final Supplier<T> delegate;
-    private T cache = null;
+    private volatile Supplier<T> delegate;
+    private volatile T cache = null;
 
     public CachedSupplier(Supplier<T> delegate) {
         this.delegate = delegate;
@@ -32,10 +32,16 @@ public class CachedSupplier<T> implements Supplier<T> {
 
     @Override
     public T get() {
-        if (cache == null) {
-            cache = delegate.get();
+        if (this.delegate == null) {
+            return this.cache;
         }
-        return cache;
-    }
 
+        synchronized(this) {
+            if (this.delegate != null) {
+                this.cache = this.delegate.get();
+                this.delegate = null;
+            }
+        }
+        return this.cache;
+    }
 }
diff --git a/src/main/java/com/networknt/schema/DependentSchemas.java b/src/main/java/com/networknt/schema/DependentSchemas.java
index 253ed04..5ab4fe7 100644
--- a/src/main/java/com/networknt/schema/DependentSchemas.java
+++ b/src/main/java/com/networknt/schema/DependentSchemas.java
@@ -44,7 +44,13 @@ public class DependentSchemas extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
         debug(logger, node, rootNode, instanceLocation);
 
         Set<ValidationMessage> errors = null;
@@ -52,7 +58,9 @@ public class DependentSchemas extends BaseJsonValidator {
             String pname = it.next();
             JsonSchema schema = this.schemaDependencies.get(pname);
             if (schema != null) {
-                Set<ValidationMessage> schemaDependenciesErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
+                Set<ValidationMessage> schemaDependenciesErrors = !walk
+                        ? schema.validate(executionContext, node, rootNode, instanceLocation)
+                        : schema.walk(executionContext, node, rootNode, instanceLocation, true);
                 if (!schemaDependenciesErrors.isEmpty()) {
                     if (errors == null) {
                         errors = new LinkedHashSet<>();
@@ -72,7 +80,7 @@ public class DependentSchemas extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
         for (JsonSchema schema : this.schemaDependencies.values()) {
             schema.walk(executionContext, node, rootNode, instanceLocation, false);
diff --git a/src/main/java/com/networknt/schema/ExecutionContext.java b/src/main/java/com/networknt/schema/ExecutionContext.java
index 8ca79cd..f2ca8e1 100644
--- a/src/main/java/com/networknt/schema/ExecutionContext.java
+++ b/src/main/java/com/networknt/schema/ExecutionContext.java
@@ -27,7 +27,6 @@ import java.util.Stack;
 public class ExecutionContext {
     private ExecutionConfig executionConfig;
     private CollectorContext collectorContext = null;
-    private ValidatorState validatorState = null;
     private Stack<DiscriminatorContext> discriminatorContexts = null;
     private JsonNodeAnnotations annotations = null;
     private JsonNodeResults results = null;
@@ -153,24 +152,6 @@ public class ExecutionContext {
         this.failFast = failFast;
     }
 
-    /**
-     * Gets the validator state.
-     * 
-     * @return the validator state
-     */
-    public ValidatorState getValidatorState() {
-        return validatorState;
-    }
-
-    /**
-     * Sets the validator state.
-     * 
-     * @param validatorState the validator state
-     */
-    public void setValidatorState(ValidatorState validatorState) {
-        this.validatorState = validatorState;
-    }
-
     public DiscriminatorContext getCurrentDiscriminatorContext() {
         if (this.discriminatorContexts == null) {
             return null;
diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
index d766123..7257681 100644
--- a/src/main/java/com/networknt/schema/JsonSchema.java
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -58,7 +58,6 @@ public class JsonSchema extends BaseJsonValidator {
     private boolean validatorsLoaded = false;
     private boolean recursiveAnchor = false;
 
-    private JsonValidator requiredValidator = null;
     private TypeValidator typeValidator;
 
     private final String id;
@@ -161,7 +160,6 @@ public class JsonSchema extends BaseJsonValidator {
         this.validators = copy.validators;
         this.validatorsLoaded = copy.validatorsLoaded;
         this.recursiveAnchor = copy.recursiveAnchor;
-        this.requiredValidator = copy.requiredValidator;
         this.typeValidator = copy.typeValidator;
         this.id = copy.id;
     }
@@ -189,7 +187,6 @@ public class JsonSchema extends BaseJsonValidator {
         copy.evaluationParentSchema = refEvaluationParentSchema;
         // Validator state is reset due to the changes in evaluation path
         copy.validatorsLoaded = false;
-        copy.requiredValidator = null;
         copy.typeValidator = null;
         copy.validators = null;
         return copy;
@@ -204,7 +201,6 @@ public class JsonSchema extends BaseJsonValidator {
                     copy.getValidationContext().getSchemaResources(),
                     copy.getValidationContext().getDynamicAnchors());
             copy.validatorsLoaded = false;
-            copy.requiredValidator = null;
             copy.typeValidator = null;
             copy.validators = null;
             return copy;
@@ -472,8 +468,6 @@ public class JsonSchema extends BaseJsonValidator {
 
                     if ("$ref".equals(pname)) {
                         refValidator = validator;
-                    } else if ("required".equals(pname)) {
-                        this.requiredValidator = validator;
                     } else if ("type".equals(pname)) {
                         if (validator instanceof TypeValidator) {
                             this.typeValidator = (TypeValidator) validator;
@@ -537,9 +531,6 @@ public class JsonSchema extends BaseJsonValidator {
         }
 
         SetView<ValidationMessage> errors = null;
-        // Set the walkEnabled and isValidationEnabled flag in internal validator state.
-        setValidatorState(executionContext, false, true);
-
         for (JsonValidator v : getValidators()) {
             Set<ValidationMessage> results = null;
 
@@ -894,8 +885,6 @@ public class JsonSchema extends BaseJsonValidator {
      * @return ValidationResult
      */
     private ValidationResult validateAndCollect(ExecutionContext executionContext, JsonNode jsonNode, JsonNode rootNode, JsonNodePath instanceLocation) {
-        // Set the walkEnabled and isValidationEnabled flag in internal validator state.
-        setValidatorState(executionContext, false, true);
         // Validate.
         Set<ValidationMessage> errors = validate(executionContext, jsonNode, rootNode, instanceLocation);
 
@@ -990,8 +979,6 @@ public class JsonSchema extends BaseJsonValidator {
 
     private ValidationResult walkAtNodeInternal(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
             JsonNodePath instanceLocation, boolean shouldValidateSchema) {
-        // Set the walkEnabled flag in internal validator state.
-        setValidatorState(executionContext, true, shouldValidateSchema);
         // Walk through the schema.
         Set<ValidationMessage> errors = walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
 
@@ -1041,30 +1028,11 @@ public class JsonSchema extends BaseJsonValidator {
     }
 
     /************************ END OF WALK METHODS **********************************/
-
-    private static void setValidatorState(ExecutionContext executionContext, boolean isWalkEnabled,
-            boolean shouldValidateSchema) {
-        // Get the Validator state object storing validation data
-        ValidatorState validatorState = executionContext.getValidatorState();
-        if (validatorState == null) {
-            // If one has not been created, instantiate one
-            executionContext.setValidatorState(new ValidatorState(isWalkEnabled, shouldValidateSchema));
-        }
-    }
-
     @Override
     public String toString() {
         return "\"" + getEvaluationPath() + "\" : " + getSchemaNode().toString();
     }
 
-    public boolean hasRequiredValidator() {
-        return this.requiredValidator != null;
-    }
-
-    public JsonValidator getRequiredValidator() {
-        return this.requiredValidator;
-    }
-
     public boolean hasTypeValidator() {
         return getTypeValidator() != null;
     }
diff --git a/src/main/java/com/networknt/schema/NotValidator.java b/src/main/java/com/networknt/schema/NotValidator.java
index a2d7f8a..8ca348b 100644
--- a/src/main/java/com/networknt/schema/NotValidator.java
+++ b/src/main/java/com/networknt/schema/NotValidator.java
@@ -37,7 +37,13 @@ public class NotValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
         Set<ValidationMessage> errors = null;
         debug(logger, node, rootNode, instanceLocation);
 
@@ -45,7 +51,8 @@ public class NotValidator extends BaseJsonValidator {
         boolean failFast = executionContext.isFailFast();
         try {
             executionContext.setFailFast(false);
-            errors = this.schema.validate(executionContext, node, rootNode, instanceLocation);
+            errors = !walk ? this.schema.validate(executionContext, node, rootNode, instanceLocation)
+                    : this.schema.walk(executionContext, node, rootNode, instanceLocation, true);
         } finally {
             // Restore flag
             executionContext.setFailFast(failFast);
@@ -62,7 +69,7 @@ public class NotValidator extends BaseJsonValidator {
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         if (shouldValidateSchema) {
-            return validate(executionContext, node, rootNode, instanceLocation);
+            return validate(executionContext, node, rootNode, instanceLocation, true);
         }
 
         Set<ValidationMessage> errors = this.schema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java
index 1a49d98..2e5df8c 100644
--- a/src/main/java/com/networknt/schema/OneOfValidator.java
+++ b/src/main/java/com/networknt/schema/OneOfValidator.java
@@ -52,16 +52,16 @@ public class OneOfValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
         Set<ValidationMessage> errors = null;
 
         debug(logger, node, rootNode, instanceLocation);
-
-        ValidatorState state = executionContext.getValidatorState();
-
-        // this is a complex validator, we set the flag to true
-        state.setComplexValidator(true);
-
         int numberOfValidSchema = 0;
         int index = 0;
         SetView<ValidationMessage> childErrors = null;
@@ -82,24 +82,15 @@ public class OneOfValidator extends BaseJsonValidator {
             executionContext.setFailFast(false);
             for (JsonSchema schema : this.schemas) {
                 Set<ValidationMessage> schemaErrors = Collections.emptySet();
-
-                // Reset state in case the previous validator did not match
-                state.setMatchedNode(true);
-
-                if (!state.isWalkEnabled()) {
+                if (!walk) {
                     schemaErrors = schema.validate(executionContext, node, rootNode, instanceLocation);
                 } else {
                     schemaErrors = schema.walk(executionContext, node, rootNode, instanceLocation,
-                            state.isValidationEnabled());
+                            true);
                 }
 
                 // check if any validation errors have occurred
                 if (schemaErrors.isEmpty()) {
-                    // check whether there are no errors HOWEVER we have validated the exact
-                    // validator
-                    if (!state.hasMatchedNode()) {
-                        continue;
-                    }
                     numberOfValidSchema++;
                     if (indexes == null) {
                         indexes = new ArrayList<>();
@@ -194,15 +185,6 @@ public class OneOfValidator extends BaseJsonValidator {
                 errors = Collections.singleton(message);
             }
         }
-
-        // Make sure to signal parent handlers we matched
-        if (errors == null || errors.isEmpty()) {
-            state.setMatchedNode(true);
-        }
-
-        // reset the ValidatorState object
-        resetValidatorState(executionContext);
-
         return errors != null ? errors : Collections.emptySet();
     }
 
@@ -232,17 +214,11 @@ public class OneOfValidator extends BaseJsonValidator {
         return this.canShortCircuit;
     }
 
-    private static void resetValidatorState(ExecutionContext executionContext) {
-        ValidatorState state = executionContext.getValidatorState();
-        state.setComplexValidator(false);
-        state.setMatchedNode(true);
-    }
-
     @Override
     public Set<ValidationMessage> walk(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation, boolean shouldValidateSchema) {
         HashSet<ValidationMessage> validationMessages = new LinkedHashSet<>();
         if (shouldValidateSchema) {
-            validationMessages.addAll(validate(executionContext, node, rootNode, instanceLocation));
+            validationMessages.addAll(validate(executionContext, node, rootNode, instanceLocation, true));
         } else {
             for (JsonSchema schema : this.schemas) {
                 schema.walk(executionContext, node, rootNode, instanceLocation, shouldValidateSchema);
diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java
index f378905..92267c0 100644
--- a/src/main/java/com/networknt/schema/PropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/PropertiesValidator.java
@@ -56,15 +56,17 @@ public class PropertiesValidator extends BaseJsonValidator {
     }
 
     @Override
-    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode, JsonNodePath instanceLocation) {
+    public Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation) {
+        return validate(executionContext, node, rootNode, instanceLocation, false);
+    }
+
+    protected Set<ValidationMessage> validate(ExecutionContext executionContext, JsonNode node, JsonNode rootNode,
+            JsonNodePath instanceLocation, boolean walk) {
         debug(logger, node, rootNode, instanceLocation);
 
         SetView<ValidationMessage> errors = null;
 
-        // get the Validator state object storing validation data
-        ValidatorState state = executionContext.getValidatorState();
-
-        Set<ValidationMessage> requiredErrors = null;
         Set<String> matchedInstancePropertyNames = null;
         boolean collectAnnotations = collectAnnotations() || collectAnnotations(executionContext);
         for (Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
@@ -77,15 +79,7 @@ public class PropertiesValidator extends BaseJsonValidator {
                     }
                     matchedInstancePropertyNames.add(entry.getKey());
                 }
-                // check whether this is a complex validator. save the state
-                boolean isComplex = state.isComplexValidator();
-               // if this is a complex validator, the node has matched, and all it's child elements, if available, are to be validated
-                if (isComplex) {
-                    state.setMatchedNode(true);
-                    // reset the complex validator for child element validation, and reset it after the return from the recursive call
-                    state.setComplexValidator(false);
-                }
-                if (!state.isWalkEnabled()) {
+                if (!walk) {
                     //validate the child element(s)
                     Set<ValidationMessage> result = entry.getValue().validate(executionContext, propertyNode, rootNode,
                             path);
@@ -100,17 +94,10 @@ public class PropertiesValidator extends BaseJsonValidator {
                     if (errors == null) {
                         errors = new SetView<>();
                     }
-                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
-                }
-
-                // reset the complex flag to the original value before the recursive call
-                if (isComplex) {
-                    state.setComplexValidator(isComplex);
-                    // if this was a complex validator, the node has matched and has been validated
-                    state.setMatchedNode(true);
+                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
                 }
             } else {
-                if (state.isWalkEnabled()) {
+                if (walk) {
                     // This tries to make the walk listener consistent between when validation is
                     // enabled or disabled as when validation is disabled it will walk where node is
                     // null.
@@ -119,30 +106,7 @@ public class PropertiesValidator extends BaseJsonValidator {
                     if (errors == null) {
                         errors = new SetView<>();
                     }
-                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, state.isValidationEnabled(), errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
-                }
-
-                // check whether the node which has not matched was mandatory or not
-
-                // the node was mandatory, decide which behavior to employ when validator has
-                // not matched
-                if (state.isComplexValidator()) {
-                    if (getParentSchema().hasRequiredValidator()) {
-
-                        // The required validator runs for all properties in the node and not just the
-                        // current propertyNode
-                        if (requiredErrors == null) {
-                            // Note that the results of the required validator shouldn't be added to the errors here, the required validator
-                            // will still trigger normally in the schema
-                            requiredErrors = getParentSchema().getRequiredValidator().validate(executionContext, node,
-                                    rootNode, instanceLocation);
-                        }
-                        if (!requiredErrors.isEmpty()) {
-                            // this was a complex validator (ex oneOf) and the node has not been matched
-                            state.setMatchedNode(false);
-                            return Collections.emptySet();
-                        }
-                    }
+                    walkSchema(executionContext, entry, node, rootNode, instanceLocation, true, errors, this.validationContext.getConfig().getPropertyWalkListenerRunner());
                 }
             }
         }
@@ -167,7 +131,7 @@ public class PropertiesValidator extends BaseJsonValidator {
         }
         if (shouldValidateSchema) {
             validationMessages.union(validate(executionContext, node == null ? MissingNode.getInstance() : node, rootNode,
-                    instanceLocation));
+                    instanceLocation, true));
         } else {
             WalkListenerRunner propertyWalkListenerRunner = this.validationContext.getConfig().getPropertyWalkListenerRunner();
             for (Map.Entry<String, JsonSchema> entry : this.schemas.entrySet()) {
diff --git a/src/main/java/com/networknt/schema/ValidatorState.java b/src/main/java/com/networknt/schema/ValidatorState.java
deleted file mode 100644
index b522bd8..0000000
--- a/src/main/java/com/networknt/schema/ValidatorState.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (c) 2020 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;
-
-public class ValidatorState {
-    /**
-     * Flag set when a node has matched Works in conjunction with the next flag:
-     * isComplexValidator, to be used for complex validators such as oneOf, for ex
-     */
-    private boolean matchedNode = true;
-
-    /**
-     * Flag set if complex validators such as oneOf, for ex, neeed to have their
-     * properties validated. The PropertiesValidator is not aware generally of a
-     * complex validator is being validated or a simple poperty tree
-     */
-    private boolean isComplexValidator = false;
-
-    /**
-     * Flag to check if walking is enabled.
-     */
-    private boolean isWalkEnabled = false;
-
-    /**
-     * Flag to check if validation is enabled while walking.
-     */
-    private boolean isValidationEnabled = false;
-
-    /**
-     * Constructor for validation state.
-     */
-    public ValidatorState() {
-    }
-
-    /**
-     * Constructor for validation state.
-     * 
-     * @param walkEnabled       whether walk is enabled
-     * @param validationEnabled whether validation is enabled
-     */
-    public ValidatorState(boolean walkEnabled, boolean validationEnabled) {
-        this.isWalkEnabled = walkEnabled;
-        this.isValidationEnabled = validationEnabled;
-    }
-
-    public void setMatchedNode(boolean matchedNode) {
-        this.matchedNode = matchedNode;
-    }
-
-    public boolean hasMatchedNode() {
-        return matchedNode;
-    }
-
-    public boolean isComplexValidator() {
-        return isComplexValidator;
-    }
-
-    public void setComplexValidator(boolean isComplexValidator) {
-        this.isComplexValidator = isComplexValidator;
-    }
-
-    public boolean isWalkEnabled() {
-        return isWalkEnabled;
-    }
-
-    public void setWalkEnabled(boolean isWalkEnabled) {
-        this.isWalkEnabled = isWalkEnabled;
-    }
-
-    public boolean isValidationEnabled() {
-        return isValidationEnabled;
-    }
-
-    public void setValidationEnabled(boolean isValidationEnabled) {
-        this.isValidationEnabled = isValidationEnabled;
-    }
-
-}


Test patch:
diff --git a/src/test/java/com/networknt/schema/CachedSupplierTest.java b/src/test/java/com/networknt/schema/CachedSupplierTest.java
new file mode 100644
index 0000000..f0b8cfa
--- /dev/null
+++ b/src/test/java/com/networknt/schema/CachedSupplierTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for CachedSupplier.
+ */
+class CachedSupplierTest {
+    @Test
+    void nullValue() {
+        CachedSupplier<String> supplier = new CachedSupplier<>(null);
+        assertNull(supplier.get());
+    }
+
+    @Test
+    void concurrency() throws Exception {
+        AtomicInteger value = new AtomicInteger(0);
+        
+        CachedSupplier<Integer> supplier = new CachedSupplier<>(() -> {
+            return value.addAndGet(1);
+        });
+        Exception[] instance = new Exception[1];
+        CountDownLatch latch = new CountDownLatch(1);
+        List<Thread> threads = new ArrayList<>();
+        for (int i = 0; i < 50; ++i) {
+            Runnable runner = new Runnable() {
+                public void run() {
+                    try {
+                        latch.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                    try {
+                        int result = supplier.get();
+                        assertEquals(1, result);
+                    } catch(Exception e) {
+                        synchronized(instance) {
+                            instance[0] = e;
+                        }
+                    }
+                }
+            };
+            Thread thread = new Thread(runner, "Thread" + i);
+            thread.start();
+            threads.add(thread);
+        }
+        latch.countDown(); // Release the latch for threads to run concurrently
+        threads.forEach(t -> {
+            try {
+                t.join();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        if (instance[0] != null) {
+            throw instance[0];
+        }
+        assertEquals(1, value.get());
+    }
+}
diff --git a/src/test/java/com/networknt/schema/OneOfValidatorTest.java b/src/test/java/com/networknt/schema/OneOfValidatorTest.java
index 151632d..70b4f13 100644
--- a/src/test/java/com/networknt/schema/OneOfValidatorTest.java
+++ b/src/test/java/com/networknt/schema/OneOfValidatorTest.java
@@ -17,8 +17,11 @@
 package com.networknt.schema;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -139,4 +142,146 @@ public class OneOfValidatorTest {
         JsonSchemaException ex = assertThrows(JsonSchemaException.class, () -> factory.getSchema(schemaData));
         assertEquals("type", ex.getValidationMessage().getMessageKey());
     }
+
+    /**
+     * This test checks that the oneOf example at
+     * https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
+     * behaves according to the specification instead of the example.
+     *
+     * https://github.com/swagger-api/swagger.io/issues/253
+     * https://github.com/OAI/OpenAPI-Specification/issues/3477
+     * https://github.com/networknt/json-schema-validator/issues/110
+     */
+    @Test
+    void invalidSwaggerIoExample() {
+        String document = "paths:\r\n"
+                + "  /pets:\r\n"
+                + "    patch:\r\n"
+                + "      requestBody:\r\n"
+                + "        content:\r\n"
+                + "          application/json:\r\n"
+                + "            schema:\r\n"
+                + "              oneOf:\r\n"
+                + "                - $ref: '#/components/schemas/Cat'\r\n"
+                + "                - $ref: '#/components/schemas/Dog'\r\n"
+                + "      responses:\r\n"
+                + "        '200':\r\n"
+                + "          description: Updated\r\n"
+                + "components:\r\n"
+                + "  schemas:\r\n"
+                + "    Dog:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        bark:\r\n"
+                + "          type: boolean\r\n"
+                + "        breed:\r\n"
+                + "          type: string\r\n"
+                + "          enum: [Dingo, Husky, Retriever, Shepherd]\r\n"
+                + "    Cat:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        hunts:\r\n"
+                + "          type: boolean\r\n"
+                + "        age:\r\n"
+                + "          type: integer";
+        
+        JsonSchema schema = JsonSchemaFactory
+                .getInstance(VersionFlag.V202012,
+                        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders
+                                .schemas(Collections.singletonMap("http://example.org/example.yaml", document))))
+                .getSchema(SchemaLocation.of(
+                        "http://example.org/example.yaml#/paths/~1pets/patch/requestBody/content/application~1json/schema"));
+        
+        String example1 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"breed\": \"Dingo\" \r\n"
+                + "}";
+        assertFalse(schema.validate(example1, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example2 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true\r\n"
+                + "}";
+        assertFalse(schema.validate(example2, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example3 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true,\r\n"
+                + "  \"breed\": \"Husky\",\r\n"
+                + "  \"age\": 3      \r\n"
+                + "}";
+        assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN));
+    }
+    
+    /**
+     * This test checks that the oneOf example at
+     * https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
+     * behaves according to the specification instead of the example.
+     *
+     * https://github.com/swagger-api/swagger.io/issues/253
+     * https://github.com/OAI/OpenAPI-Specification/issues/3477
+     * https://github.com/networknt/json-schema-validator/issues/110
+     */
+    @Test
+    void fixedSwaggerIoExample() {
+        String document = "paths:\r\n"
+                + "  /pets:\r\n"
+                + "    patch:\r\n"
+                + "      requestBody:\r\n"
+                + "        content:\r\n"
+                + "          application/json:\r\n"
+                + "            schema:\r\n"
+                + "              oneOf:\r\n"
+                + "                - $ref: '#/components/schemas/Cat'\r\n"
+                + "                - $ref: '#/components/schemas/Dog'\r\n"
+                + "      responses:\r\n"
+                + "        '200':\r\n"
+                + "          description: Updated\r\n"
+                + "components:\r\n"
+                + "  schemas:\r\n"
+                + "    Dog:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        bark:\r\n"
+                + "          type: boolean\r\n"
+                + "        breed:\r\n"
+                + "          type: string\r\n"
+                + "          enum: [Dingo, Husky, Retriever, Shepherd]\r\n"
+                + "      required:\r\n"
+                + "        - bark\r\n"
+                + "        - breed\r\n"
+                + "    Cat:\r\n"
+                + "      type: object\r\n"
+                + "      properties:\r\n"
+                + "        hunts:\r\n"
+                + "          type: boolean\r\n"
+                + "        age:\r\n"
+                + "          type: integer\r\n"
+                + "      required:\r\n"
+                + "        - hunts\r\n"
+                + "        - age";
+        
+        JsonSchema schema = JsonSchemaFactory
+                .getInstance(VersionFlag.V202012,
+                        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders
+                                .schemas(Collections.singletonMap("http://example.org/example.yaml", document))))
+                .getSchema(SchemaLocation.of(
+                        "http://example.org/example.yaml#/paths/~1pets/patch/requestBody/content/application~1json/schema"));
+        
+        String example1 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"breed\": \"Dingo\" \r\n"
+                + "}";
+        assertTrue(schema.validate(example1, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example2 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true\r\n"
+                + "}";
+        assertFalse(schema.validate(example2, InputFormat.JSON, OutputFormat.BOOLEAN));
+        String example3 = "{\r\n"
+                + "  \"bark\": true,\r\n"
+                + "  \"hunts\": true,\r\n"
+                + "  \"breed\": \"Husky\",\r\n"
+                + "  \"age\": 3      \r\n"
+                + "}";
+        assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN));
+    }
 }