Repository: networknt/json-schema-validator
Pull request #202 (2019-10-24 20:24:02)

Feat/#199 fail fast
Adding the feature to fail-fast once the very first validation error is discovered.

stevehu: @khiftikhar Sorry to review the PR late, I am working on the release of the light platform during this weekend. For the fail-fast flag, I see you have added to the ValidationContext which is an object that carries the runtime info from one validator to another. The best place for the fail-fast flag would be the SchemaValidatorsConfig class which is designed to add more configuration flags in the future. In this way, we can keep the signature of JsonSchema and JsonSchemaFactory as config object already passed in. This will make the API static without impact current users. What do you think? Thanks a lot for your help. 
khiftikhar: @stevehu I have moved the fail-fast flat to the SchemaValidatorsConfig.java now. Please take a look when you do have time.
stevehu: @khiftikhar It looks very good to put the fail-fast flag into the config. This also gives us an example of how to add another flag in the future. I see that you are using an exception to throw the message when fail-fast is true. Is there any other way to return the error message when it happens in any validator? For some users, the exception might be treated as an alarm. I will look into it when I complete the release. Thanks.
khiftikhar: @stevehu I had been looking into how we can stop the processing otherwise, but it did required a bit more changes into the code. 

I also thought to introduce a checked exception to `builder().failFast`method but then I thought it might make the usability a bit uglier.

Now, setting up this flag is a conscious decision of the user, and the documentation clearly specifies that no error message is returned, rather and exception is thrown. So it's the user's responsibility to take of it. 

Patch:
diff --git a/pom.xml b/pom.xml
index a348e26..a03ce75 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.networknt</groupId>
     <artifactId>json-schema-validator</artifactId>
-    <version>1.0.22</version>
+    <version>1.0.23</version>
     <packaging>bundle</packaging>
     <description>A json schema validator that supports draft v4</description>
     <url>https://github.com/networknt/json-schema-validator</url>
@@ -142,6 +142,7 @@
             <plugin>
                 <groupId>org.apache.felix</groupId>
                 <artifactId>maven-bundle-plugin</artifactId>
+                <version>4.2.1</version>
                 <extensions>true</extensions>
             </plugin>
             <plugin>
diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java
index 847cd1c..06c2f58 100644
--- a/src/main/java/com/networknt/schema/BaseJsonValidator.java
+++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java
@@ -1,145 +1,153 @@
-/*
- * 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 java.net.URI;
-import java.util.Set;
-
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-
-import com.fasterxml.jackson.databind.JsonNode;
-
-public abstract class BaseJsonValidator implements JsonValidator {
-    private String schemaPath;
-    private JsonNode schemaNode;
-    private JsonSchema parentSchema;
-    private boolean suppressSubSchemaRetrieval;
-    private ValidatorTypeCode validatorType;
-    private ErrorMessageType errorMessageType;
-    /**
-     * SchemaValidatorsConfig can only get and set in validationContext
-     */
-    protected SchemaValidatorsConfig config;
-
-    /**
-     * ThreadLocal to allow to pass state in recursive validator calls
-     */
-    protected final static ThreadLocal<ValidatorState> validatorState = new ThreadLocal<ValidatorState>();
-    
-    public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                             ValidatorTypeCode validatorType, ValidationContext validationContext) {
-    	this(schemaPath, schemaNode, parentSchema, validatorType, false );
-    	this.config = validationContext.getConfig() == null ? new SchemaValidatorsConfig() : validationContext.getConfig();
-    }
-
-    public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                             ValidatorTypeCode validatorType, boolean suppressSubSchemaRetrieval) {
-        this.errorMessageType = validatorType;
-        this.schemaPath = schemaPath;
-        this.schemaNode = schemaNode;
-        this.parentSchema = parentSchema;
-        this.validatorType = validatorType;
-        this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval;
-    }
-
-    protected String getSchemaPath() {
-        return schemaPath;
-    }
-
-    public JsonNode getSchemaNode() {
-        return schemaNode;
-    }
-
-    protected JsonSchema getParentSchema() {
-        return parentSchema;
-    }
-    
-    protected JsonSchema fetchSubSchemaNode(ValidationContext validationContext) {
-        return suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(schemaNode, validationContext);
-    }
-
-    
-    private static JsonSchema obtainSubSchemaNode(final JsonNode schemaNode, final ValidationContext validationContext){
-        final JsonNode node = schemaNode.get("id");
-        if(node == null) return null;
-    	if(node.equals(schemaNode.get("$schema"))) return null;
-
-        final String text = node.textValue();
-        if (text == null) {
-            return null;
-        }
-        else {
-        	final URI uri;
-            try {
-                uri = validationContext.getURIFactory().create(node.textValue());
-            } catch (IllegalArgumentException e) {
-                return null;
-            }
-            return validationContext.getJsonSchemaFactory().getSchema(uri, validationContext.getConfig());
-        }
-    }
-
-    public Set<ValidationMessage> validate(JsonNode node) {
-        return validate(node, node, AT_ROOT);
-    }
-
-    protected boolean equals(double n1, double n2) {
-        return Math.abs(n1 - n2) < 1e-12;
-    }
-
-    protected boolean greaterThan(double n1, double n2) {
-        return n1 - n2 > 1e-12;
-    }
-
-    protected boolean lessThan(double n1, double n2) {
-        return n1 - n2 < -1e-12;
-    }
-
-    protected void parseErrorCode(String errorCodeKey) {
-        JsonNode errorCodeNode = getParentSchema().getSchemaNode().get(errorCodeKey);
-        if (errorCodeNode != null && errorCodeNode.isTextual()) {
-            String errorCodeText = errorCodeNode.asText();
-            if (StringUtils.isNotBlank(errorCodeText)) {
-                errorMessageType = CustomErrorMessageType.of(errorCodeText);
-            }
-        }
-    }
-
-
-    protected ValidationMessage buildValidationMessage(String at, String... arguments) {
-        return ValidationMessage.of(getValidatorType().getValue(), errorMessageType, at, arguments);
-    }
-
-    protected void debug(Logger logger, JsonNode node, JsonNode rootNode, String at) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("validate( " + node + ", " + rootNode + ", " + at + ")");
-        }
-    }
-
-    protected ValidatorTypeCode getValidatorType() {
-        return validatorType;
-    }
-
-    protected String getNodeFieldType() {
-        JsonNode typeField = this.getParentSchema().getSchemaNode().get("type");
-        if (typeField != null) {
-            return typeField.asText();
-        }
-        return null;
-    }
-}
+/*
+ * 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 java.net.URI;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public abstract class BaseJsonValidator implements JsonValidator {
+    private String schemaPath;
+    private JsonNode schemaNode;
+    private JsonSchema parentSchema;
+    private boolean suppressSubSchemaRetrieval;
+    private ValidatorTypeCode validatorType;
+    private ErrorMessageType errorMessageType;
+    /**
+     * SchemaValidatorsConfig can only get and set in validationContext
+     */
+    protected SchemaValidatorsConfig config;
+    protected final boolean failFast;
+
+    /**
+     * ThreadLocal to allow to pass state in recursive validator calls
+     */
+    protected final static ThreadLocal<ValidatorState> validatorState = new ThreadLocal<ValidatorState>();
+
+    public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
+                             ValidatorTypeCode validatorType, ValidationContext validationContext) {
+    	this(schemaPath, schemaNode, parentSchema, validatorType, false ,
+          validationContext.getConfig() != null && validationContext.getConfig().isFailFast());
+    	this.config = validationContext.getConfig() == null ? new SchemaValidatorsConfig() : validationContext.getConfig();
+    }
+
+    public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
+                             ValidatorTypeCode validatorType, boolean suppressSubSchemaRetrieval, boolean failFast) {
+
+        this.errorMessageType = validatorType;
+        this.schemaPath = schemaPath;
+        this.schemaNode = schemaNode;
+        this.parentSchema = parentSchema;
+        this.validatorType = validatorType;
+        this.suppressSubSchemaRetrieval = suppressSubSchemaRetrieval;
+        this.failFast = failFast;
+    }
+
+    protected String getSchemaPath() {
+        return schemaPath;
+    }
+
+    public JsonNode getSchemaNode() {
+        return schemaNode;
+    }
+
+    protected JsonSchema getParentSchema() {
+        return parentSchema;
+    }
+
+    protected JsonSchema fetchSubSchemaNode(ValidationContext validationContext) {
+        return suppressSubSchemaRetrieval ? null : obtainSubSchemaNode(schemaNode, validationContext);
+    }
+
+
+    private static JsonSchema obtainSubSchemaNode(final JsonNode schemaNode, final ValidationContext validationContext){
+        final JsonNode node = schemaNode.get("id");
+        if(node == null) return null;
+    	if(node.equals(schemaNode.get("$schema"))) return null;
+
+        final String text = node.textValue();
+        if (text == null) {
+            return null;
+        }
+        else {
+        	final URI uri;
+            try {
+                uri = validationContext.getURIFactory().create(node.textValue());
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+            return validationContext.getJsonSchemaFactory().getSchema(uri, validationContext.getConfig());
+        }
+    }
+
+    public Set<ValidationMessage> validate(JsonNode node) {
+        return validate(node, node, AT_ROOT);
+    }
+
+    protected boolean equals(double n1, double n2) {
+        return Math.abs(n1 - n2) < 1e-12;
+    }
+
+    protected boolean greaterThan(double n1, double n2) {
+        return n1 - n2 > 1e-12;
+    }
+
+    protected boolean lessThan(double n1, double n2) {
+        return n1 - n2 < -1e-12;
+    }
+
+    protected void parseErrorCode(String errorCodeKey) {
+        JsonNode errorCodeNode = getParentSchema().getSchemaNode().get(errorCodeKey);
+        if (errorCodeNode != null && errorCodeNode.isTextual()) {
+            String errorCodeText = errorCodeNode.asText();
+            if (StringUtils.isNotBlank(errorCodeText)) {
+                errorMessageType = CustomErrorMessageType.of(errorCodeText);
+            }
+        }
+    }
+
+
+    protected ValidationMessage buildValidationMessage(String at, String... arguments) {
+        final ValidationMessage message = ValidationMessage.of(getValidatorType().getValue(), errorMessageType, at, arguments);
+        if (failFast) {
+            throw new JsonSchemaException(message);
+        }
+        return message;
+    }
+
+    protected void debug(Logger logger, JsonNode node, JsonNode rootNode, String at) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("validate( " + node + ", " + rootNode + ", " + at + ")");
+        }
+    }
+
+    protected ValidatorTypeCode getValidatorType() {
+        return validatorType;
+    }
+
+    protected String getNodeFieldType() {
+        JsonNode typeField = this.getParentSchema().getSchemaNode().get("type");
+        if (typeField != null) {
+            return typeField.asText();
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
index 642ebdd..569a108 100644
--- a/src/main/java/com/networknt/schema/JsonSchema.java
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -1,181 +1,182 @@
-/*
- * 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 java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URLDecoder;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import com.fasterxml.jackson.databind.JsonNode;
-
-/**
- * This is the core of json constraint implementation. It parses json constraint
- * file and generates JsonValidators. The class is thread safe, once it is
- * constructed, it can be used to validate multiple json data concurrently.
- */
-public class JsonSchema extends BaseJsonValidator {
-    private static final Pattern intPattern = Pattern.compile("^[0-9]+$");
-    protected final Map<String, JsonValidator> validators;
-    private final ValidationContext validationContext;
-    
-    /**
-     * This is the current uri of this schema. This uri could refer to the uri of this schema's file
-     * or it could potentially be a uri that has been altered by an id. An 'id' is able to completely overwrite
-     * the current uri or add onto it. This is necessary so that '$ref's are able to be relative to a
-     * combination of the current schema file's uri and 'id' uris visible to this schema.
-     * 
-     * This can be null. If it is null, then the creation of relative uris will fail. However, an absolute
-     * 'id' would still be able to specify an absolute uri.
-     */
-    private final URI currentUri;
-    
-    private JsonValidator requiredValidator = null;
-
-    public JsonSchema(ValidationContext validationContext, URI baseUri, JsonNode schemaNode) {
-        this(validationContext, "#", baseUri, schemaNode, null);
-    }
-
-    public JsonSchema(ValidationContext validationContext, String schemaPath, URI currentUri, JsonNode schemaNode,
-               JsonSchema parent) {
-        this(validationContext,  schemaPath, currentUri, schemaNode, parent, false);
-    }
-
-    public JsonSchema(ValidationContext validationContext, URI baseUri, JsonNode schemaNode, boolean suppressSubSchemaRetrieval) {
-        this(validationContext, "#", baseUri, schemaNode, null, suppressSubSchemaRetrieval);
-    }
-
-    private JsonSchema(ValidationContext validationContext,  String schemaPath, URI currentUri, JsonNode schemaNode,
-               JsonSchema parent, boolean suppressSubSchemaRetrieval) {
-        super(schemaPath, schemaNode, parent, null, suppressSubSchemaRetrieval);
-        this.validationContext = validationContext;
-        this.config = validationContext.getConfig();
-        this.currentUri = this.combineCurrentUriWithIds(currentUri, schemaNode);
-        this.validators = Collections.unmodifiableMap(this.read(schemaNode));
-    }
-    
-    private URI combineCurrentUriWithIds(URI currentUri, JsonNode schemaNode) {
-      final JsonNode idNode = schemaNode.get("id");
-      if (idNode == null) {
-        return currentUri;
-      } else {
-        try {
-          return this.validationContext.getURIFactory().create(currentUri, idNode.asText());
-        } catch (IllegalArgumentException e) {
-          throw new JsonSchemaException(ValidationMessage.of(ValidatorTypeCode.ID.getValue(), ValidatorTypeCode.ID, idNode.asText(), currentUri.toString()));
-        }
-      }
-    }
-    
-    public URI getCurrentUri()
-    {
-      return this.currentUri;
-    }
-
-    /**
-     * Find the schema node for $ref attribute.
-     *
-     * @param ref String
-     * @return JsonNode
-     */
-    public JsonNode getRefSchemaNode(String ref) {
-        JsonSchema schema = findAncestor();
-        JsonNode node = schema.getSchemaNode();
-
-        if (ref.startsWith("#/")) {
-            // handle local ref
-            String[] keys = ref.substring(2).split("/");
-            for (String key : keys) {
-                try {
-                    key = URLDecoder.decode(key, "utf-8");
-                } catch (UnsupportedEncodingException e) {
-                }
-                Matcher matcher = intPattern.matcher(key);
-                if (matcher.matches()) {
-                    node = node.get(Integer.parseInt(key));
-                } else {
-                    node = node.get(key);
-                }
-                if (node == null){
-                    JsonSchema subSchema = schema.fetchSubSchemaNode(validationContext);
-                    if (subSchema != null) {
-                        node = subSchema.getRefSchemaNode(ref);
-                    }
-                }
-                if (node == null){
-                    break;
-                }
-            }
-        }
-        return node;
-    }
-
-    public JsonSchema findAncestor() {
-        JsonSchema ancestor = this;
-        if (this.getParentSchema() != null) {
-            ancestor = this.getParentSchema().findAncestor();
-        }
-        return ancestor;
-    }
-
-    private Map<String, JsonValidator> read(JsonNode schemaNode) {
-        Map<String, JsonValidator> validators = new HashMap<String, JsonValidator>();
-        Iterator<String> pnames = schemaNode.fieldNames();
-        while (pnames.hasNext()) {
-            String pname = pnames.next();
-            JsonNode n = schemaNode.get(pname);
-
-            JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, n, this);
-            if (validator != null) {
-                validators.put(getSchemaPath() + "/" + pname, validator);
-                
-                if(pname.equals("required"))
-                	requiredValidator = validator;
-            }
-
-        }
-        return validators;
-    }
-
-    public Set<ValidationMessage> validate(JsonNode jsonNode, JsonNode rootNode, String at) {
-        Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
-        for (JsonValidator v : validators.values()) {
-            errors.addAll(v.validate(jsonNode, rootNode, at));
-        }
-        return errors;
-    }
-
-    @Override
-    public String toString() {
-        return "\"" + getSchemaPath() + "\" : " + getSchemaNode().toString();
-    }
-
-    public boolean hasRequiredValidator() {
-    	return requiredValidator != null ? true : false;
-    }
-    
-	public JsonValidator getRequiredValidator() {
-		return requiredValidator;
-	}
-}
+/*
+ * 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 java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * This is the core of json constraint implementation. It parses json constraint
+ * file and generates JsonValidators. The class is thread safe, once it is
+ * constructed, it can be used to validate multiple json data concurrently.
+ */
+public class JsonSchema extends BaseJsonValidator {
+    private static final Pattern intPattern = Pattern.compile("^[0-9]+$");
+    protected final Map<String, JsonValidator> validators;
+    private final ValidationContext validationContext;
+    
+    /**
+     * This is the current uri of this schema. This uri could refer to the uri of this schema's file
+     * or it could potentially be a uri that has been altered by an id. An 'id' is able to completely overwrite
+     * the current uri or add onto it. This is necessary so that '$ref's are able to be relative to a
+     * combination of the current schema file's uri and 'id' uris visible to this schema.
+     * 
+     * This can be null. If it is null, then the creation of relative uris will fail. However, an absolute
+     * 'id' would still be able to specify an absolute uri.
+     */
+    private final URI currentUri;
+    
+    private JsonValidator requiredValidator = null;
+
+    public JsonSchema(ValidationContext validationContext, URI baseUri, JsonNode schemaNode) {
+        this(validationContext, "#", baseUri, schemaNode, null);
+    }
+
+    public JsonSchema(ValidationContext validationContext, String schemaPath, URI currentUri, JsonNode schemaNode,
+               JsonSchema parent) {
+        this(validationContext,  schemaPath, currentUri, schemaNode, parent, false);
+    }
+
+    public JsonSchema(ValidationContext validationContext, URI baseUri, JsonNode schemaNode, boolean suppressSubSchemaRetrieval) {
+        this(validationContext, "#", baseUri, schemaNode, null, suppressSubSchemaRetrieval);
+    }
+
+    private JsonSchema(ValidationContext validationContext,  String schemaPath, URI currentUri, JsonNode schemaNode,
+               JsonSchema parent, boolean suppressSubSchemaRetrieval) {
+        super(schemaPath, schemaNode, parent, null, suppressSubSchemaRetrieval,
+            validationContext.getConfig() != null && validationContext.getConfig().isFailFast());
+        this.validationContext = validationContext;
+        this.config = validationContext.getConfig();
+        this.currentUri = this.combineCurrentUriWithIds(currentUri, schemaNode);
+        this.validators = Collections.unmodifiableMap(this.read(schemaNode));
+    }
+    
+    private URI combineCurrentUriWithIds(URI currentUri, JsonNode schemaNode) {
+      final JsonNode idNode = schemaNode.get("id");
+      if (idNode == null) {
+        return currentUri;
+      } else {
+        try {
+          return this.validationContext.getURIFactory().create(currentUri, idNode.asText());
+        } catch (IllegalArgumentException e) {
+          throw new JsonSchemaException(ValidationMessage.of(ValidatorTypeCode.ID.getValue(), ValidatorTypeCode.ID, idNode.asText(), currentUri.toString()));
+        }
+      }
+    }
+    
+    public URI getCurrentUri()
+    {
+      return this.currentUri;
+    }
+
+    /**
+     * Find the schema node for $ref attribute.
+     *
+     * @param ref String
+     * @return JsonNode
+     */
+    public JsonNode getRefSchemaNode(String ref) {
+        JsonSchema schema = findAncestor();
+        JsonNode node = schema.getSchemaNode();
+
+        if (ref.startsWith("#/")) {
+            // handle local ref
+            String[] keys = ref.substring(2).split("/");
+            for (String key : keys) {
+                try {
+                    key = URLDecoder.decode(key, "utf-8");
+                } catch (UnsupportedEncodingException e) {
+                }
+                Matcher matcher = intPattern.matcher(key);
+                if (matcher.matches()) {
+                    node = node.get(Integer.parseInt(key));
+                } else {
+                    node = node.get(key);
+                }
+                if (node == null){
+                    JsonSchema subSchema = schema.fetchSubSchemaNode(validationContext);
+                    if (subSchema != null) {
+                        node = subSchema.getRefSchemaNode(ref);
+                    }
+                }
+                if (node == null){
+                    break;
+                }
+            }
+        }
+        return node;
+    }
+
+    public JsonSchema findAncestor() {
+        JsonSchema ancestor = this;
+        if (this.getParentSchema() != null) {
+            ancestor = this.getParentSchema().findAncestor();
+        }
+        return ancestor;
+    }
+
+    private Map<String, JsonValidator> read(JsonNode schemaNode) {
+        Map<String, JsonValidator> validators = new HashMap<String, JsonValidator>();
+        Iterator<String> pnames = schemaNode.fieldNames();
+        while (pnames.hasNext()) {
+            String pname = pnames.next();
+            JsonNode n = schemaNode.get(pname);
+
+            JsonValidator validator = validationContext.newValidator(getSchemaPath(), pname, n, this);
+            if (validator != null) {
+                validators.put(getSchemaPath() + "/" + pname, validator);
+                
+                if(pname.equals("required"))
+                	requiredValidator = validator;
+            }
+
+        }
+        return validators;
+    }
+
+    public Set<ValidationMessage> validate(JsonNode jsonNode, JsonNode rootNode, String at) {
+        Set<ValidationMessage> errors = new LinkedHashSet<ValidationMessage>();
+        for (JsonValidator v : validators.values()) {
+            errors.addAll(v.validate(jsonNode, rootNode, at));
+        }
+        return errors;
+    }
+
+    @Override
+    public String toString() {
+        return "\"" + getSchemaPath() + "\" : " + getSchemaNode().toString();
+    }
+
+    public boolean hasRequiredValidator() {
+    	return requiredValidator != null ? true : false;
+    }
+    
+	public JsonValidator getRequiredValidator() {
+		return requiredValidator;
+	}
+}
diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
index 2e84b0a..bc06fc6 100644
--- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java
+++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
@@ -1,350 +1,350 @@
-/*
- * 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 java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.networknt.schema.uri.ClasspathURLFactory;
-import com.networknt.schema.uri.ClasspathURLFetcher;
-import com.networknt.schema.uri.URIFactory;
-import com.networknt.schema.uri.URIFetcher;
-import com.networknt.schema.uri.URISchemeFactory;
-import com.networknt.schema.uri.URISchemeFetcher;
-import com.networknt.schema.uri.URLFactory;
-import com.networknt.schema.uri.URLFetcher;
-
-public class JsonSchemaFactory {
-    private static final Logger logger = LoggerFactory
-            .getLogger(JsonSchemaFactory.class);
-    
-    
-    public static class Builder {
-        private ObjectMapper objectMapper = new ObjectMapper();
-        private String defaultMetaSchemaURI;
-        private final Map<String, URIFactory> uriFactoryMap = new HashMap<String, URIFactory>();
-        private final Map<String, URIFetcher> uriFetcherMap = new HashMap<String, URIFetcher>();
-        private final Map<String, JsonMetaSchema> jsonMetaSchemas = new HashMap<String, JsonMetaSchema>();
-        private final Map<String, String> uriMap = new HashMap<String, String>();
-        
-        public Builder() {
-            // Adds support for creating {@link URL}s.
-            final URIFactory urlFactory = new URLFactory();
-            for (final String scheme : URLFactory.SUPPORTED_SCHEMES) {
-                this.uriFactoryMap.put(scheme, urlFactory);
-            }
-
-            // Adds support for fetching with {@link URL}s.
-            final URIFetcher urlFetcher = new URLFetcher();
-            for (final String scheme : URLFetcher.SUPPORTED_SCHEMES) {
-                this.uriFetcherMap.put(scheme, urlFetcher);
-            }
-
-            // Adds support for creating and fetching with classpath {@link URL}s.
-            final URIFactory classpathURLFactory = new ClasspathURLFactory();
-            final URIFetcher classpathURLFetcher = new ClasspathURLFetcher();
-            for (final String scheme : ClasspathURLFactory.SUPPORTED_SCHEMES) {
-                this.uriFactoryMap.put(scheme, classpathURLFactory);
-                this.uriFetcherMap.put(scheme, classpathURLFetcher);
-            }
-        }
-        
-        public Builder objectMapper(final ObjectMapper objectMapper) {
-            this.objectMapper = objectMapper;
-            return this;
-        }
-        
-        public Builder defaultMetaSchemaURI(final String defaultMetaSchemaURI) {
-            this.defaultMetaSchemaURI = defaultMetaSchemaURI;
-            return this;
-        }
-        
-        /**
-         * Maps a number of schemes to a {@link URIFactory}.
-         * @param uriFactory the uri factory that will be used for the given schemes.
-         * @param schemes the scheme that the uri factory will be assocaited with.
-         * @return this builder.
-         */
-        public Builder uriFactory(final URIFactory uriFactory, final String... schemes) {
-            for (final String scheme : schemes)
-            {
-                this.uriFactoryMap.put(scheme, uriFactory);
-            }
-            return this;
-        }
-		
-        /**
-         * Maps a number of schemes to a {@link URIFetcher}.
-         * @param uriFetcher the uri fetcher that will be used for the given schemes.
-         * @param schemes the scheme that the uri fetcher will be assocaited with.
-         * @return this builder.
-         */
-        public Builder uriFetcher(final URIFetcher uriFetcher, final String... schemes) {
-            for (final String scheme : schemes)
-            {
-                this.uriFetcherMap.put(scheme, uriFetcher);
-            }
-            return this;
-        }
-        
-        public Builder addMetaSchema(final JsonMetaSchema jsonMetaSchema) {
-            this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema);
-            return this;
-        }
-        
-        public Builder addMetaSchemas(final Collection<? extends JsonMetaSchema> jsonMetaSchemas) {
-            for (JsonMetaSchema jsonMetaSchema: jsonMetaSchemas) {
-                this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema);
-            }
-            return this;
-        }
-        
-        public Builder addUriMappings(final Map<String, String> map) {
-            this.uriMap.putAll(map);
-            return this;
-        }
-        
-        public JsonSchemaFactory build() {
-            // create builtin keywords with (custom) formats.
-            return new JsonSchemaFactory(
-                    objectMapper == null ? new ObjectMapper() : objectMapper, 
-                    defaultMetaSchemaURI, 
-                    new URISchemeFactory(uriFactoryMap),
-                    new URISchemeFetcher(uriFetcherMap), 
-                    jsonMetaSchemas,
-                    uriMap
-            );
-        }
-    }
-    
-    private final ObjectMapper mapper;
-    private final String defaultMetaSchemaURI;
-    private final URISchemeFactory uriFactory;
-    private final URISchemeFetcher uriFetcher;
-    private final Map<String, JsonMetaSchema> jsonMetaSchemas;
-    private final Map<String, String> uriMap;
-
-    private JsonSchemaFactory(
-            final ObjectMapper mapper, 
-            final String defaultMetaSchemaURI, 
-            final URISchemeFactory uriFactory, 
-            final URISchemeFetcher uriFetcher, 
-            final Map<String, JsonMetaSchema> jsonMetaSchemas, 
-            final Map<String, String> uriMap) {
-        if (mapper == null) {
-            throw new IllegalArgumentException("ObjectMapper must not be null");
-        }
-        if (defaultMetaSchemaURI == null || defaultMetaSchemaURI.trim().isEmpty()) {
-            throw new IllegalArgumentException("defaultMetaSchemaURI must not be null or empty");
-        }
-        if (uriFactory == null) {
-            throw new IllegalArgumentException("URIFactory must not be null");
-        }
-        if (uriFetcher == null) {
-            throw new IllegalArgumentException("URIFetcher must not be null");
-        }
-        if (jsonMetaSchemas == null || jsonMetaSchemas.isEmpty()) {
-            throw new IllegalArgumentException("Json Meta Schemas must not be null or empty");
-        }
-        if (jsonMetaSchemas.get(defaultMetaSchemaURI) == null) {
-            throw new IllegalArgumentException("Meta Schema for default Meta Schema URI must be provided");
-        }
-        if (uriMap == null) {
-            throw new IllegalArgumentException("URL Mappings must not be null");
-        }
-        this.mapper = mapper;
-        this.defaultMetaSchemaURI = defaultMetaSchemaURI;
-        this.uriFactory = uriFactory;
-        this.uriFetcher = uriFetcher;
-        this.jsonMetaSchemas = jsonMetaSchemas;
-        this.uriMap = uriMap;
-    }
-
-    /**
-     * Builder without keywords or formats.
-     *
-     * <code>
-     * JsonSchemaFactory.builder(JsonSchemaFactory.getDraftV4()).build();
-     * </code>
-     * 
-     * @return a builder instance without any keywords or formats - usually not what one needs.
-     */
-    static Builder builder() {
-        return new Builder();
-    }
-    
-    public static JsonSchemaFactory getInstance() {
-        JsonMetaSchema draftV4 = JsonMetaSchema.getDraftV4();
-        return builder()
-                .defaultMetaSchemaURI(draftV4.getUri())
-                .addMetaSchema(draftV4)
-                .build();
-    }
-    
-    public static Builder builder(final JsonSchemaFactory blueprint) {
-        Builder builder = builder()
-                .addMetaSchemas(blueprint.jsonMetaSchemas.values())
-                .defaultMetaSchemaURI(blueprint.defaultMetaSchemaURI)
-                .objectMapper(blueprint.mapper)
-                .addUriMappings(blueprint.uriMap);
-        
-        for (Map.Entry<String, URIFactory> entry : blueprint.uriFactory.getURIFactories().entrySet())
-        {
-            builder = builder.uriFactory(entry.getValue(), entry.getKey());
-        }
-        for (Map.Entry<String, URIFetcher> entry : blueprint.uriFetcher.getURIFetchers().entrySet())
-        {
-            builder = builder.uriFetcher(entry.getValue(), entry.getKey());
-        }
-        return builder;
-    }
-    
-    private JsonSchema newJsonSchema(final URI schemaUri, final JsonNode schemaNode, final SchemaValidatorsConfig config) {
-        final ValidationContext validationContext = createValidationContext(schemaNode);
-        validationContext.setConfig(config);
-        JsonSchema jsonSchema = new JsonSchema(validationContext, schemaUri, schemaNode);
-        return jsonSchema;
-    }
-
-    protected ValidationContext createValidationContext(final JsonNode schemaNode) {
-        final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
-        return new ValidationContext(this.uriFactory, jsonMetaSchema, this, null);
-    }
-
-    private JsonMetaSchema findMetaSchemaForSchema(final JsonNode schemaNode) {
-        final JsonNode uriNode = schemaNode.get("$schema");
-        final String uri = uriNode == null || uriNode.isNull() ? defaultMetaSchemaURI: uriNode.textValue();
-        final JsonMetaSchema jsonMetaSchema = jsonMetaSchemas.get(uri);
-        if (jsonMetaSchema == null) {
-            throw new JsonSchemaException("Unknown Metaschema: " + uri);
-        }
-        return jsonMetaSchema;
-    }
-    
-    /**
-     * @return A shared {@link URI} factory that is used for creating the URI references in schemas.
-     */
-    public URIFactory getUriFactory()
-    {
-    	return this.uriFactory;
-    }
-	
-    public JsonSchema getSchema(final String schema, final SchemaValidatorsConfig config) {
-        try {
-            final JsonNode schemaNode = mapper.readTree(schema);
-            return newJsonSchema(null, schemaNode, config);
-        } catch (IOException ioe) {
-            logger.error("Failed to load json schema!", ioe);
-            throw new JsonSchemaException(ioe);
-        }
-    }
-
-    public JsonSchema getSchema(final String schema) {
-        return getSchema(schema, null);
-    }
-    
-    public JsonSchema getSchema(final InputStream schemaStream, final SchemaValidatorsConfig config) {
-        try {
-            final JsonNode schemaNode = mapper.readTree(schemaStream);
-            return newJsonSchema(null, schemaNode, config);
-        } catch (IOException ioe) {
-            logger.error("Failed to load json schema!", ioe);
-            throw new JsonSchemaException(ioe);
-        }
-    }
-
-    public JsonSchema getSchema(final InputStream schemaStream) {
-        return getSchema(schemaStream, null);
-    }
-    
-    public JsonSchema getSchema(final URI schemaUri, final SchemaValidatorsConfig config) {
-        try {
-            InputStream inputStream = null;
-            final Map<String, String> map = (config != null) ? config.getUriMappings() : new HashMap<String, String>(uriMap);
-            map.putAll(uriMap);
-            
-            final URI mappedUri;
-            try {
-                mappedUri = this.uriFactory.create(map.getOrDefault(schemaUri.toString(), schemaUri.toString()));
-            } catch (IllegalArgumentException e) {
-                logger.error("Failed to create URI.", e);
-                throw new JsonSchemaException(e);
-            }
-            try {
-                inputStream = this.uriFetcher.fetch(mappedUri);
-                final JsonNode schemaNode = mapper.readTree(inputStream);
-                final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
-
-                if (idMatchesSourceUri(jsonMetaSchema, schemaNode, schemaUri)) {
-                    return new JsonSchema(new ValidationContext(this.uriFactory, jsonMetaSchema, this, config), mappedUri, schemaNode, true /*retrieved via id, resolving will not change anything*/);
-                }
-
-                return newJsonSchema(mappedUri, schemaNode, config);
-            } finally {
-                if (inputStream != null) {
-                    inputStream.close();
-                }
-            }
-        } catch (IOException ioe) {
-            logger.error("Failed to load json schema!", ioe);
-            throw new JsonSchemaException(ioe);
-        }
-    }
-
-    public JsonSchema getSchema(final URI schemaUri) {
-        return getSchema(schemaUri, new SchemaValidatorsConfig());
-    }
-
-    public JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode, final SchemaValidatorsConfig config) {
-        return newJsonSchema(schemaUri, jsonNode, config);
-    }
-    
-
-    public JsonSchema getSchema(final JsonNode jsonNode, final SchemaValidatorsConfig config) {
-        return newJsonSchema(null, jsonNode, config);
-    }
-
-    public JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode) {
-        return newJsonSchema(schemaUri, jsonNode, null);
-    }
-    
-    public JsonSchema getSchema(final JsonNode jsonNode) {
-        return newJsonSchema(null, jsonNode, null);
-    }
-
-    private boolean idMatchesSourceUri(final JsonMetaSchema metaSchema, final JsonNode schema, final URI schemaUri) {
-
-        String id = metaSchema.readId(schema);
-        if (id == null || id.isEmpty()) {
-            return false;
-        }
-        boolean result = id.equals(schemaUri.toString());
-        if (logger.isDebugEnabled()) {
-            logger.debug("Matching " + id + " to " + schemaUri.toString() + ": " + result);
-        }
-        return result;
-    }
-}
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.uri.ClasspathURLFactory;
+import com.networknt.schema.uri.ClasspathURLFetcher;
+import com.networknt.schema.uri.URIFactory;
+import com.networknt.schema.uri.URIFetcher;
+import com.networknt.schema.uri.URISchemeFactory;
+import com.networknt.schema.uri.URISchemeFetcher;
+import com.networknt.schema.uri.URLFactory;
+import com.networknt.schema.uri.URLFetcher;
+
+public class JsonSchemaFactory {
+    private static final Logger logger = LoggerFactory
+            .getLogger(JsonSchemaFactory.class);
+
+
+    public static class Builder {
+        private ObjectMapper objectMapper = new ObjectMapper();
+        private String defaultMetaSchemaURI;
+        private final Map<String, URIFactory> uriFactoryMap = new HashMap<String, URIFactory>();
+        private final Map<String, URIFetcher> uriFetcherMap = new HashMap<String, URIFetcher>();
+        private final Map<String, JsonMetaSchema> jsonMetaSchemas = new HashMap<String, JsonMetaSchema>();
+        private final Map<String, String> uriMap = new HashMap<String, String>();
+
+        public Builder() {
+            // Adds support for creating {@link URL}s.
+            final URIFactory urlFactory = new URLFactory();
+            for (final String scheme : URLFactory.SUPPORTED_SCHEMES) {
+                this.uriFactoryMap.put(scheme, urlFactory);
+            }
+
+            // Adds support for fetching with {@link URL}s.
+            final URIFetcher urlFetcher = new URLFetcher();
+            for (final String scheme : URLFetcher.SUPPORTED_SCHEMES) {
+                this.uriFetcherMap.put(scheme, urlFetcher);
+            }
+
+            // Adds support for creating and fetching with classpath {@link URL}s.
+            final URIFactory classpathURLFactory = new ClasspathURLFactory();
+            final URIFetcher classpathURLFetcher = new ClasspathURLFetcher();
+            for (final String scheme : ClasspathURLFactory.SUPPORTED_SCHEMES) {
+                this.uriFactoryMap.put(scheme, classpathURLFactory);
+                this.uriFetcherMap.put(scheme, classpathURLFetcher);
+            }
+        }
+        
+        public Builder objectMapper(final ObjectMapper objectMapper) {
+            this.objectMapper = objectMapper;
+            return this;
+        }
+        
+        public Builder defaultMetaSchemaURI(final String defaultMetaSchemaURI) {
+            this.defaultMetaSchemaURI = defaultMetaSchemaURI;
+            return this;
+        }
+        
+        /**
+         * Maps a number of schemes to a {@link URIFactory}.
+         * @param uriFactory the uri factory that will be used for the given schemes.
+         * @param schemes the scheme that the uri factory will be assocaited with.
+         * @return this builder.
+         */
+        public Builder uriFactory(final URIFactory uriFactory, final String... schemes) {
+            for (final String scheme : schemes)
+            {
+                this.uriFactoryMap.put(scheme, uriFactory);
+            }
+            return this;
+        }
+		
+        /**
+         * Maps a number of schemes to a {@link URIFetcher}.
+         * @param uriFetcher the uri fetcher that will be used for the given schemes.
+         * @param schemes the scheme that the uri fetcher will be assocaited with.
+         * @return this builder.
+         */
+        public Builder uriFetcher(final URIFetcher uriFetcher, final String... schemes) {
+            for (final String scheme : schemes)
+            {
+                this.uriFetcherMap.put(scheme, uriFetcher);
+            }
+            return this;
+        }
+        
+        public Builder addMetaSchema(final JsonMetaSchema jsonMetaSchema) {
+            this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema);
+            return this;
+        }
+        
+        public Builder addMetaSchemas(final Collection<? extends JsonMetaSchema> jsonMetaSchemas) {
+            for (JsonMetaSchema jsonMetaSchema: jsonMetaSchemas) {
+                this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema);
+            }
+            return this;
+        }
+        
+        public Builder addUriMappings(final Map<String, String> map) {
+            this.uriMap.putAll(map);
+            return this;
+        }
+        
+        public JsonSchemaFactory build() {
+            // create builtin keywords with (custom) formats.
+            return new JsonSchemaFactory(
+                    objectMapper == null ? new ObjectMapper() : objectMapper,
+                    defaultMetaSchemaURI, 
+                    new URISchemeFactory(uriFactoryMap),
+                    new URISchemeFetcher(uriFetcherMap), 
+                    jsonMetaSchemas,
+                    uriMap
+            );
+        }
+    }
+    
+    private final ObjectMapper mapper;
+    private final String defaultMetaSchemaURI;
+    private final URISchemeFactory uriFactory;
+    private final URISchemeFetcher uriFetcher;
+    private final Map<String, JsonMetaSchema> jsonMetaSchemas;
+    private final Map<String, String> uriMap;
+
+    private JsonSchemaFactory(
+            final ObjectMapper mapper,
+            final String defaultMetaSchemaURI,
+            final URISchemeFactory uriFactory,
+            final URISchemeFetcher uriFetcher,
+            final Map<String, JsonMetaSchema> jsonMetaSchemas,
+            final Map<String, String> uriMap) {
+        if (mapper == null) {
+            throw new IllegalArgumentException("ObjectMapper must not be null");
+        }
+        if (defaultMetaSchemaURI == null || defaultMetaSchemaURI.trim().isEmpty()) {
+            throw new IllegalArgumentException("defaultMetaSchemaURI must not be null or empty");
+        }
+        if (uriFactory == null) {
+            throw new IllegalArgumentException("URIFactory must not be null");
+        }
+        if (uriFetcher == null) {
+            throw new IllegalArgumentException("URIFetcher must not be null");
+        }
+        if (jsonMetaSchemas == null || jsonMetaSchemas.isEmpty()) {
+            throw new IllegalArgumentException("Json Meta Schemas must not be null or empty");
+        }
+        if (jsonMetaSchemas.get(defaultMetaSchemaURI) == null) {
+            throw new IllegalArgumentException("Meta Schema for default Meta Schema URI must be provided");
+        }
+        if (uriMap == null) {
+            throw new IllegalArgumentException("URL Mappings must not be null");
+        }
+        this.mapper = mapper;
+        this.defaultMetaSchemaURI = defaultMetaSchemaURI;
+        this.uriFactory = uriFactory;
+        this.uriFetcher = uriFetcher;
+        this.jsonMetaSchemas = jsonMetaSchemas;
+        this.uriMap = uriMap;
+    }
+
+    /**
+     * Builder without keywords or formats.
+     *
+     * <code>
+     * JsonSchemaFactory.builder(JsonSchemaFactory.getDraftV4()).build();
+     * </code>
+     * 
+     * @return a builder instance without any keywords or formats - usually not what one needs.
+     */
+    static Builder builder() {
+        return new Builder();
+    }
+    
+    public static JsonSchemaFactory getInstance() {
+        JsonMetaSchema draftV4 = JsonMetaSchema.getDraftV4();
+        return builder()
+                .defaultMetaSchemaURI(draftV4.getUri())
+                .addMetaSchema(draftV4)
+                .build();
+    }
+    
+    public static Builder builder(final JsonSchemaFactory blueprint) {
+        Builder builder = builder()
+                .addMetaSchemas(blueprint.jsonMetaSchemas.values())
+                .defaultMetaSchemaURI(blueprint.defaultMetaSchemaURI)
+                .objectMapper(blueprint.mapper)
+                .addUriMappings(blueprint.uriMap);
+        
+        for (Map.Entry<String, URIFactory> entry : blueprint.uriFactory.getURIFactories().entrySet())
+        {
+            builder = builder.uriFactory(entry.getValue(), entry.getKey());
+        }
+        for (Map.Entry<String, URIFetcher> entry : blueprint.uriFetcher.getURIFetchers().entrySet())
+        {
+            builder = builder.uriFetcher(entry.getValue(), entry.getKey());
+        }
+        return builder;
+    }
+    
+    private JsonSchema newJsonSchema(final URI schemaUri, final JsonNode schemaNode, final SchemaValidatorsConfig config) {
+        final ValidationContext validationContext = createValidationContext(schemaNode);
+        validationContext.setConfig(config);
+        JsonSchema jsonSchema = new JsonSchema(validationContext, schemaUri, schemaNode);
+        return jsonSchema;
+    }
+
+    protected ValidationContext createValidationContext(final JsonNode schemaNode) {
+        final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
+        return new ValidationContext(this.uriFactory, jsonMetaSchema, this, null);
+    }
+
+    private JsonMetaSchema findMetaSchemaForSchema(final JsonNode schemaNode) {
+        final JsonNode uriNode = schemaNode.get("$schema");
+        final String uri = uriNode == null || uriNode.isNull() ? defaultMetaSchemaURI: uriNode.textValue();
+        final JsonMetaSchema jsonMetaSchema = jsonMetaSchemas.get(uri);
+        if (jsonMetaSchema == null) {
+            throw new JsonSchemaException("Unknown Metaschema: " + uri);
+        }
+        return jsonMetaSchema;
+    }
+    
+    /**
+     * @return A shared {@link URI} factory that is used for creating the URI references in schemas.
+     */
+    public URIFactory getUriFactory()
+    {
+    	return this.uriFactory;
+    }
+	
+    public JsonSchema getSchema(final String schema, final SchemaValidatorsConfig config) {
+        try {
+            final JsonNode schemaNode = mapper.readTree(schema);
+            return newJsonSchema(null, schemaNode, config);
+        } catch (IOException ioe) {
+            logger.error("Failed to load json schema!", ioe);
+            throw new JsonSchemaException(ioe);
+        }
+    }
+
+    public JsonSchema getSchema(final String schema) {
+        return getSchema(schema, null);
+    }
+    
+    public JsonSchema getSchema(final InputStream schemaStream, final SchemaValidatorsConfig config) {
+        try {
+            final JsonNode schemaNode = mapper.readTree(schemaStream);
+            return newJsonSchema(null, schemaNode, config);
+        } catch (IOException ioe) {
+            logger.error("Failed to load json schema!", ioe);
+            throw new JsonSchemaException(ioe);
+        }
+    }
+
+    public JsonSchema getSchema(final InputStream schemaStream) {
+        return getSchema(schemaStream, null);
+    }
+    
+    public JsonSchema getSchema(final URI schemaUri, final SchemaValidatorsConfig config) {
+        try {
+            InputStream inputStream = null;
+            final Map<String, String> map = (config != null) ? config.getUriMappings() : new HashMap<String, String>(uriMap);
+            map.putAll(uriMap);
+            
+            final URI mappedUri;
+            try {
+                mappedUri = this.uriFactory.create(map.getOrDefault(schemaUri.toString(), schemaUri.toString()));
+            } catch (IllegalArgumentException e) {
+                logger.error("Failed to create URI.", e);
+                throw new JsonSchemaException(e);
+            }
+            try {
+                inputStream = this.uriFetcher.fetch(mappedUri);
+                final JsonNode schemaNode = mapper.readTree(inputStream);
+                final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
+
+                if (idMatchesSourceUri(jsonMetaSchema, schemaNode, schemaUri)) {
+                    return new JsonSchema(new ValidationContext(this.uriFactory, jsonMetaSchema, this, config), mappedUri, schemaNode, true /*retrieved via id, resolving will not change anything*/);
+                }
+
+                return newJsonSchema(mappedUri, schemaNode, config);
+            } finally {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+            }
+        } catch (IOException ioe) {
+            logger.error("Failed to load json schema!", ioe);
+            throw new JsonSchemaException(ioe);
+        }
+    }
+
+    public JsonSchema getSchema(final URI schemaUri) {
+        return getSchema(schemaUri, new SchemaValidatorsConfig());
+    }
+
+    public JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode, final SchemaValidatorsConfig config) {
+        return newJsonSchema(schemaUri, jsonNode, config);
+    }
+    
+
+    public JsonSchema getSchema(final JsonNode jsonNode, final SchemaValidatorsConfig config) {
+        return newJsonSchema(null, jsonNode, config);
+    }
+
+    public JsonSchema getSchema(final URI schemaUri, final JsonNode jsonNode) {
+        return newJsonSchema(schemaUri, jsonNode, null);
+    }
+    
+    public JsonSchema getSchema(final JsonNode jsonNode) {
+        return newJsonSchema(null, jsonNode, null);
+    }
+
+    private boolean idMatchesSourceUri(final JsonMetaSchema metaSchema, final JsonNode schema, final URI schemaUri) {
+
+        String id = metaSchema.readId(schema);
+        if (id == null || id.isEmpty()) {
+            return false;
+        }
+        boolean result = id.equals(schemaUri.toString());
+        if (logger.isDebugEnabled()) {
+            logger.debug("Matching " + id + " to " + schemaUri.toString() + ": " + result);
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
index 4fe0be3..d9684cf 100644
--- a/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
+++ b/src/main/java/com/networknt/schema/SchemaValidatorsConfig.java
@@ -16,14 +16,21 @@
 
 package com.networknt.schema;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 public class SchemaValidatorsConfig {
     /**
      * when validate type, if TYPE_LOOSE = true, will try to convert string to different types to match the type defined in schema.
      */
     private boolean typeLoose;
+
+    /**
+     * When set to true, validator process is stop immediately when a very first validation error is discovered.
+     */
+    private boolean failFast;
     
     /**
      * Map of public, normally internet accessible schema URLs to alternate locations; this allows for offline
@@ -50,6 +57,19 @@ public class SchemaValidatorsConfig {
         this.typeLoose = typeLoose;
     }
 
+    /**
+     * When enabled, {@link JsonValidator#validate(JsonNode, JsonNode, String)}
+     * or {@link JsonValidator#validate(JsonNode)} doesn't return any {@link Set}&lt;{@link ValidationMessage}&gt;,
+     * instead a {@link JsonSchemaException} is thrown as soon as a validation errors is discovered.
+     * */
+    public void setFailFast(final boolean failFast) {
+        this.failFast = failFast;
+    }
+
+    public boolean isFailFast(){
+        return this.failFast;
+    }
+
     public Map<String, String> getUriMappings() {
         // return a copy of the mappings
         return new HashMap<String, String>(uriMappings);


Test patch:
diff --git a/src/test/java/com/networknt/schema/JsonSchemaTest.java b/src/test/java/com/networknt/schema/JsonSchemaTest.java
index fc70567..cf17f52 100644
--- a/src/test/java/com/networknt/schema/JsonSchemaTest.java
+++ b/src/test/java/com/networknt/schema/JsonSchemaTest.java
@@ -17,26 +17,27 @@
 package com.networknt.schema;
 
 import static io.undertow.Handlers.resource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import io.undertow.Undertow;
+import io.undertow.server.handlers.resource.FileResourceManager;
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
-
+import java.util.Set;
 import org.junit.AfterClass;
-import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-
-import io.undertow.Undertow;
-import io.undertow.server.handlers.resource.FileResourceManager;
-
 public class JsonSchemaTest {
     protected ObjectMapper mapper = new ObjectMapper();
     protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance()).objectMapper(mapper).build();
@@ -73,7 +74,7 @@ public class JsonSchemaTest {
         final URI testCaseFileUri = URI.create("classpath:" + testCaseFile);
         InputStream in = Thread.currentThread().getContextClassLoader()
                 .getResourceAsStream(testCaseFile);
-        ArrayNode testCases = (ArrayNode) mapper.readTree(in);
+        ArrayNode testCases = mapper.readValue(in, ArrayNode.class);
 
         for (int j = 0; j < testCases.size(); j++) {
             try {
@@ -99,7 +100,7 @@ public class JsonSchemaTest {
                             System.out.println("schema: " + schema.toString());
                             System.out.println("data: " + test.get("data"));
                         }
-                        Assert.assertEquals(0, errors.size());
+                        assertEquals(0, errors.size());
                     } else {
                         if (errors.isEmpty()) {
                             System.out.println("---- test case failed ----");
@@ -112,10 +113,10 @@ public class JsonSchemaTest {
                                 System.out.println("schema: " + schema);
                                 System.out.println("data: " + test.get("data"));
                                 System.out.println("errors: " + errors);
-                                Assert.assertEquals("expected error count", errorCount.asInt(), errors.size());
+                                assertEquals("expected error count", errorCount.asInt(), errors.size());
                             }
                         }
-                        Assert.assertEquals(false, errors.isEmpty());
+                        assertEquals(false, errors.isEmpty());
                     }
                 }
             } catch (JsonSchemaException e) {
@@ -325,4 +326,80 @@ public class JsonSchemaTest {
     public void testUUIDValidator() throws Exception {
         runTestFile("tests/uuid.json");
     }
+
+    /**
+     * Although, the data file has three errors, but only on is reported
+     */
+    @Test
+    public void testFailFast_AllErrors() throws IOException {
+        try {
+            validateFailingFastSchemaFor("product.schema.json", "product-all-errors-data.json");
+            fail("Exception must be thrown");
+        } catch (JsonSchemaException e) {
+            final Set<ValidationMessage> messages = e.getValidationMessages();
+            assertEquals(1, messages.size());
+        }
+    }
+
+    /**
+     * File contains only one error and that is reported.
+     */
+    @Test
+    public void testFailFast_OneErrors() throws IOException {
+        try {
+            validateFailingFastSchemaFor("product.schema.json", "product-one-error-data.json");
+            fail("Exception must be thrown");
+        } catch (JsonSchemaException e) {
+            final Set<ValidationMessage> messages = e.getValidationMessages();
+            assertEquals(1, messages.size());
+        }
+    }
+
+    /**
+     * Although, the file contains two errors, but only one is reported
+     */
+    @Test
+    public void testFailFast_TwoErrors() throws IOException {
+        try {
+            validateFailingFastSchemaFor("product.schema.json", "product-two-errors-data.json");
+            fail("Exception must be thrown");
+        } catch (JsonSchemaException e) {
+            final Set<ValidationMessage> messages = e.getValidationMessages();
+            assertEquals(1, messages.size());
+        }
+    }
+
+    /**
+     * The file contains no errors, in ths case {@link Set}&lt;{@link ValidationMessage}&gt; must be empty
+     */
+    @Test
+    public void testFailFast_NoErrors() throws IOException {
+        try {
+            final Set<ValidationMessage> messages = validateFailingFastSchemaFor("product.schema.json", "product-no-errors-data.json");
+            assertTrue(messages.isEmpty());
+        } catch (JsonSchemaException e) {
+            fail("Must not get an errors");
+        }
+    }
+
+    private Set<ValidationMessage> validateFailingFastSchemaFor(final String schemaFileName, final String dataFileName) throws IOException {
+        final ObjectMapper objectMapper = new ObjectMapper();
+        final JsonNode schema = getJsonNodeFromResource(objectMapper, schemaFileName);
+        final JsonNode dataFile = getJsonNodeFromResource(objectMapper, dataFileName);
+        final SchemaValidatorsConfig config = new SchemaValidatorsConfig();
+        config.setFailFast(true);
+        return JsonSchemaFactory
+            .builder(JsonSchemaFactory.getInstance())
+            .objectMapper(objectMapper)
+            .build()
+            .getSchema(schema, config)
+            .validate(dataFile);
+    }
+
+    private JsonNode getJsonNodeFromResource(final ObjectMapper mapper, final String locationInTestResources) throws IOException {
+        return mapper.readTree(
+            Thread.currentThread().getContextClassLoader()
+                .getResourceAsStream("tests" + System.getProperty("file.separator") + locationInTestResources));
+
+    }
 }
diff --git a/src/test/resources/tests/product-all-errors-data.json b/src/test/resources/tests/product-all-errors-data.json
new file mode 100644
index 0000000..da95a64
--- /dev/null
+++ b/src/test/resources/tests/product-all-errors-data.json
@@ -0,0 +1,5 @@
+{
+    "id": "it must be integer",
+    "name": "too short",
+    "price": "it should be number"
+}
\ No newline at end of file
diff --git a/src/test/resources/tests/product-no-errors-data.json b/src/test/resources/tests/product-no-errors-data.json
new file mode 100644
index 0000000..478f468
--- /dev/null
+++ b/src/test/resources/tests/product-no-errors-data.json
@@ -0,0 +1,5 @@
+{
+    "id": 1,
+    "name": "A valid product name",
+    "price": 99.99
+}
\ No newline at end of file
diff --git a/src/test/resources/tests/product-one-error-data.json b/src/test/resources/tests/product-one-error-data.json
new file mode 100644
index 0000000..d187ea8
--- /dev/null
+++ b/src/test/resources/tests/product-one-error-data.json
@@ -0,0 +1,5 @@
+{
+    "id": 786,
+    "name": "too short",
+    "price": 99.99
+}
\ No newline at end of file
diff --git a/src/test/resources/tests/product-two-errors-data.json b/src/test/resources/tests/product-two-errors-data.json
new file mode 100644
index 0000000..5e8f621
--- /dev/null
+++ b/src/test/resources/tests/product-two-errors-data.json
@@ -0,0 +1,5 @@
+{
+    "id": 99.99,
+    "name": "A valid product name",
+    "price": 99.99
+}
\ No newline at end of file
diff --git a/src/test/resources/tests/product.schema.json b/src/test/resources/tests/product.schema.json
new file mode 100644
index 0000000..07058e6
--- /dev/null
+++ b/src/test/resources/tests/product.schema.json
@@ -0,0 +1,28 @@
+{
+  "type": "object",
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "description": "A product from Acme's catalog",
+  "title": "Product",
+  "properties": {
+    "id": {
+      "type": "integer",
+      "description": "The unique identifier for a product"
+    },
+    "name": {
+      "type": "string",
+      "maxLength": 200,
+      "minLength": 10,
+      "description": "Name of the product"
+    },
+    "price": {
+      "type": "number",
+      "minimum": 0,
+      "exclusiveMinimum": true
+    }
+  },
+  "required": [
+    "id",
+    "name",
+    "price"
+  ]
+}
\ No newline at end of file