Repository: networknt/json-schema-validator
Pull request #50 (2017-11-22 14:21:39)

Support custom meta schemas with custom keywords and formats
Thanks for this nice library!

I want to switch a project from fge where I used the extensibility of fge extensively. So I refactored your library a bit to allow for multiple meta schemas, custom keywords etc. 

I would be happy if you would accept my changes.

Refactored so that the library supports multiple meta schemas and users
of the library can add custom meta schemas as well as controlling
aspects like e.g. fetching via a URL.




Patch:
diff --git a/pom.xml b/pom.xml
index ec92daa..588e7ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,9 +13,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+    xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.networknt</groupId>
     <artifactId>json-schema-validator</artifactId>
@@ -23,7 +23,6 @@
     <description>A json schema validator that supports draft v4</description>
     <url>https://github.com/networknt/json-schema-validator</url>
     <name>JsonSchemaValidator</name>
-
     <developers>
         <developer>
             <id>stevehu</id>
@@ -31,12 +30,10 @@
             <email>stevehu@gmail.com</email>
         </developer>
     </developers>
-
     <issueManagement>
         <system>github</system>
         <url>https://github.com/networknt/json-schema-validator/issues</url>
     </issueManagement>
-
     <licenses>
         <license>
             <name>Apache License Version 2.0</name>
@@ -44,13 +41,11 @@
             <distribution>repo</distribution>
         </license>
     </licenses>
-
     <scm>
         <connection>scm:git://github.com:networknt/json-schema-validator.git</connection>
         <developerConnection>scm:git://github.com:networknt/json-schema-validator.git</developerConnection>
         <url>https://github.com:networknt/json-schema-validator.git</url>
     </scm>
-
     <distributionManagement>
         <snapshotRepository>
             <id>ossrh</id>
@@ -61,7 +56,6 @@
             <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
         </repository>
     </distributionManagement>
-
     <properties>
         <java.version>1.6</java.version>
         <java.testversion>1.7</java.testversion>
@@ -74,10 +68,7 @@
         <version.mockito>2.7.21</version.mockito>
         <version.hamcrest>1.3</version.hamcrest>
         <version.undertow>1.4.11.Final</version.undertow>
-
     </properties>
-
-
     <dependencies>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
@@ -99,7 +90,6 @@
             <artifactId>commons-lang3</artifactId>
             <version>${version.common-lang3}</version>
         </dependency>
-
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
@@ -152,7 +142,6 @@
                 </includes>
             </testResource>
         </testResources>
-
         <plugins>
             <plugin>
                 <groupId>org.sonatype.plugins</groupId>
@@ -166,13 +155,8 @@
                 </configuration>
             </plugin>
             <!--
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-release-plugin</artifactId>
-				<version>2.5.3</version>
-			</plugin>
+			<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-release-plugin</artifactId><version>2.5.3</version></plugin>
             -->
-
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-source-plugin</artifactId>
@@ -221,7 +205,6 @@
                     </execution>
                 </executions>
             </plugin>
-
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
@@ -234,8 +217,6 @@
                     </dependency>
                 </dependencies>
             </plugin>
-
-
             <!-- JACOCO added for code coverage -->
             <plugin>
                 <groupId>org.jacoco</groupId>
@@ -261,10 +242,8 @@
                 </executions>
             </plugin>
             <!-- end JACOCO -->
-
         </plugins>
     </build>
-
     <reporting>
         <plugins>
             <plugin>
@@ -274,7 +253,6 @@
             </plugin>
         </plugins>
     </reporting>
-
     <profiles>
         <profile>
             <id>release-sign-artifacts</id>
diff --git a/src/main/java/com/networknt/schema/AbstractFormat.java b/src/main/java/com/networknt/schema/AbstractFormat.java
new file mode 100644
index 0000000..5a6948d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AbstractFormat.java
@@ -0,0 +1,25 @@
+package com.networknt.schema;
+
+public abstract class AbstractFormat implements Format{
+    private final String name;
+    private final String errorMessageDescription;
+    
+    public AbstractFormat(String name) {
+        this(name, "");
+    }
+    
+    public AbstractFormat(String name, String errorMessageDescription) {
+        this.name = name;
+        this.errorMessageDescription = errorMessageDescription;
+    }
+    
+    @Override
+    public String getName() {
+        return name;
+    }
+    
+    @Override
+    public String getErrorMessageDescription() {
+        return errorMessageDescription;
+    }
+}
diff --git a/src/main/java/com/networknt/schema/AbstractJsonValidator.java b/src/main/java/com/networknt/schema/AbstractJsonValidator.java
new file mode 100644
index 0000000..da3ff49
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AbstractJsonValidator.java
@@ -0,0 +1,36 @@
+package com.networknt.schema;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public abstract class AbstractJsonValidator implements JsonValidator {
+    private final String keyword;
+    protected AbstractJsonValidator(String keyword) {
+        this.keyword = keyword;
+    }
+    public Set<ValidationMessage> validate(JsonNode node) {
+        return validate(node, node, AT_ROOT);
+    }
+    
+    protected ValidationMessage buildValidationMessage(ErrorMessageType errorMessageType, String at, String... arguments) {
+        return ValidationMessage.of(keyword, errorMessageType, at, arguments);
+    }
+    protected ValidationMessage buildValidationMessage(ErrorMessageType errorMessageType, String at, Map<String, Object> details) {
+        return ValidationMessage.of(keyword, errorMessageType, at, details);
+    }
+
+    protected Set<ValidationMessage> pass() {
+        return Collections.emptySet();
+    }
+    
+    protected Set<ValidationMessage> fail(ErrorMessageType errorMessageType, String at, Map<String, Object> details) {
+        return Collections.singleton(buildValidationMessage(errorMessageType, at, details));
+    }
+    
+    protected Set<ValidationMessage> fail(ErrorMessageType errorMessageType, String at, String...arguments) {
+        return Collections.singleton(buildValidationMessage(errorMessageType, at, arguments));
+    }
+}
diff --git a/src/main/java/com/networknt/schema/AbstractKeyword.java b/src/main/java/com/networknt/schema/AbstractKeyword.java
new file mode 100644
index 0000000..033140f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/AbstractKeyword.java
@@ -0,0 +1,15 @@
+package com.networknt.schema;
+
+
+public abstract class AbstractKeyword implements Keyword {
+    private final String value;
+    
+    public AbstractKeyword(String value) {
+        this.value = value;
+    }
+    
+    public String getValue() {
+        return value;
+    }
+    
+}
diff --git a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
index 9f8dfa0..a02b680 100644
--- a/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/AdditionalPropertiesValidator.java
@@ -16,14 +16,19 @@
 
 package com.networknt.schema;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import com.fasterxml.jackson.databind.JsonNode;
 
 public class AdditionalPropertiesValidator extends BaseJsonValidator implements JsonValidator {
     private static final Logger logger = LoggerFactory.getLogger(AdditionalPropertiesValidator.class);
@@ -34,15 +39,15 @@ public class AdditionalPropertiesValidator extends BaseJsonValidator implements
     private List<Pattern> patternProperties = new ArrayList<Pattern>();
 
     public AdditionalPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                                         ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ADDITIONAL_PROPERTIES);
+            ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ADDITIONAL_PROPERTIES, validationContext);
         allowAdditionalProperties = false;
         if (schemaNode.isBoolean()) {
             allowAdditionalProperties = schemaNode.booleanValue();
         }
         if (schemaNode.isObject()) {
             allowAdditionalProperties = true;
-            additionalPropertiesSchema = new JsonSchema(mapper, getValidatorType().getValue(), schemaNode, parentSchema);
+            additionalPropertiesSchema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
         }
 
         allowedProperties = new ArrayList<String>();
diff --git a/src/main/java/com/networknt/schema/AllOfValidator.java b/src/main/java/com/networknt/schema/AllOfValidator.java
index 790c762..da65d0f 100644
--- a/src/main/java/com/networknt/schema/AllOfValidator.java
+++ b/src/main/java/com/networknt/schema/AllOfValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,11 +31,11 @@ public class AllOfValidator extends BaseJsonValidator implements JsonValidator {
 
     private List<JsonSchema> schemas = new ArrayList<JsonSchema>();
 
-    public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF);
+    public AllOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ALL_OF, validationContext);
         int size = schemaNode.size();
         for (int i = 0; i < size; i++) {
-            schemas.add(new JsonSchema(mapper, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
+            schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
         }
     }
 
diff --git a/src/main/java/com/networknt/schema/AnyOfValidator.java b/src/main/java/com/networknt/schema/AnyOfValidator.java
index 611a216..9ddb671 100644
--- a/src/main/java/com/networknt/schema/AnyOfValidator.java
+++ b/src/main/java/com/networknt/schema/AnyOfValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,11 +31,11 @@ public class AnyOfValidator extends BaseJsonValidator implements JsonValidator {
 
     private List<JsonSchema> schemas = new ArrayList<JsonSchema>();
 
-    public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF);
+    public AnyOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ANY_OF, validationContext);
         int size = schemaNode.size();
         for (int i = 0; i < size; i++) {
-            schemas.add(new JsonSchema(mapper, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
+            schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
         }
     }
 
diff --git a/src/main/java/com/networknt/schema/BaseJsonValidator.java b/src/main/java/com/networknt/schema/BaseJsonValidator.java
index 11ec376..b863ecc 100644
--- a/src/main/java/com/networknt/schema/BaseJsonValidator.java
+++ b/src/main/java/com/networknt/schema/BaseJsonValidator.java
@@ -31,19 +31,16 @@ public abstract class BaseJsonValidator implements JsonValidator {
     private JsonSchema parentSchema;
     private JsonSchema subSchema;
     private ValidatorTypeCode validatorType;
-    private String errorCode;
+    private ErrorMessageType errorMessageType;
 
     public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                             ValidatorTypeCode validatorType) {
-        this.schemaPath = schemaPath;
-        this.schemaNode = schemaNode;
-        this.parentSchema = parentSchema;
-        this.validatorType = validatorType;
-        this.subSchema = obainSubSchemaNode(schemaNode);
+                             ValidatorTypeCode validatorType, ValidationContext validationContext) {
+    	this(schemaPath, schemaNode, parentSchema, validatorType, obainSubSchemaNode(schemaNode, validationContext) );
     }
 
     public BaseJsonValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
                              ValidatorTypeCode validatorType, JsonSchema subSchema) {
+        this.errorMessageType = validatorType;
         this.schemaPath = schemaPath;
         this.schemaNode = schemaNode;
         this.parentSchema = parentSchema;
@@ -71,20 +68,19 @@ public abstract class BaseJsonValidator implements JsonValidator {
         return subSchema != null;
     }
     
-    protected JsonSchema obainSubSchemaNode(JsonNode schemaNode){
+    protected static JsonSchema obainSubSchemaNode(JsonNode schemaNode, ValidationContext validationContext){
         JsonNode node = schemaNode.get("id");
         if(node == null) return null;
     	if(node.equals(schemaNode.get("$schema"))) return null;
 
         try {
-            JsonSchemaFactory factory = new JsonSchemaFactory();
             String text = node.textValue();
             if (text == null) {
                 return null;
             }
             else {
                 URL url = URLFactory.toURL(node.textValue());
-                return factory.getSchema(url);
+                return validationContext.getJsonSchemaFactory().getSchema(url);
             }
         } catch (MalformedURLException e) {
             return null;
@@ -110,27 +106,16 @@ public abstract class BaseJsonValidator implements JsonValidator {
     protected void parseErrorCode(String errorCodeKey) {
         JsonNode errorCodeNode = getParentSchema().getSchemaNode().get(errorCodeKey);
         if (errorCodeNode != null && errorCodeNode.isTextual()) {
-            errorCode = errorCodeNode.asText();
+            String errorCodeText = errorCodeNode.asText();
+            if (StringUtils.isNotBlank(errorCodeText)) {
+                errorMessageType = CustomErrorMessageType.of(errorCodeText);
+            }
         }
     }
 
-    private String getErrorCode() {
-        return errorCode;
-    }
-
-    private boolean isUsingCustomErrorCode() {
-        return StringUtils.isNotBlank(errorCode);
-    }
 
     protected ValidationMessage buildValidationMessage(String at, String... arguments) {
-        ValidationMessage.Builder builder = new ValidationMessage.Builder();
-        if (isUsingCustomErrorCode()) {
-            builder.code(getErrorCode()).path(at).arguments(arguments).type(validatorType.getValue());
-        } else {
-            builder.code(validatorType.getErrorCode()).path(at).arguments(arguments)
-                    .format(validatorType.getMessageFormat()).type(validatorType.getValue());
-        }
-        return builder.build();
+        return ValidationMessage.of(getValidatorType().getValue(), errorMessageType, at, arguments);
     }
 
     protected void debug(Logger logger, JsonNode node, JsonNode rootNode, String at) {
diff --git a/src/main/java/com/networknt/schema/CustomErrorMessageType.java b/src/main/java/com/networknt/schema/CustomErrorMessageType.java
new file mode 100644
index 0000000..4d8fe20
--- /dev/null
+++ b/src/main/java/com/networknt/schema/CustomErrorMessageType.java
@@ -0,0 +1,31 @@
+package com.networknt.schema;
+
+import java.text.MessageFormat;
+
+public class CustomErrorMessageType implements ErrorMessageType {
+    private final String errorCode;
+    private final MessageFormat messageFormat;
+    
+    private CustomErrorMessageType(String errorCode, MessageFormat messageFormat) {
+        this.errorCode = errorCode;
+        this.messageFormat = messageFormat;
+    }
+    
+    public static ErrorMessageType of(String errorCode) {
+        return new CustomErrorMessageType(errorCode, null);
+    }
+    public static ErrorMessageType of(String errorCode, MessageFormat messageFormat) {
+        return new CustomErrorMessageType(errorCode, null);
+    }
+
+    @Override
+    public String getErrorCode() {
+        return errorCode;
+    }
+
+    @Override
+    public MessageFormat getMessageFormat() {
+        return messageFormat;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/DependenciesValidator.java b/src/main/java/com/networknt/schema/DependenciesValidator.java
index 932e080..06a2de9 100644
--- a/src/main/java/com/networknt/schema/DependenciesValidator.java
+++ b/src/main/java/com/networknt/schema/DependenciesValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -28,9 +27,9 @@ public class DependenciesValidator extends BaseJsonValidator implements JsonVali
     private final Map<String, List<String>> propertyDeps = new HashMap<String, List<String>>();
     private Map<String, JsonSchema> schemaDeps = new HashMap<String, JsonSchema>();
 
-    public DependenciesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
+    public DependenciesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
 
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENCIES);
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.DEPENDENCIES, validationContext);
 
         for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
             String pname = it.next();
@@ -45,7 +44,7 @@ public class DependenciesValidator extends BaseJsonValidator implements JsonVali
                     depsProps.add(pvalue.get(i).asText());
                 }
             } else if (pvalue.isObject()) {
-                schemaDeps.put(pname, new JsonSchema(mapper, pname, pvalue, parentSchema));
+                schemaDeps.put(pname, new JsonSchema(validationContext, pname, pvalue, parentSchema));
             }
         }
 
diff --git a/src/main/java/com/networknt/schema/EnumValidator.java b/src/main/java/com/networknt/schema/EnumValidator.java
index 4733804..7382ce6 100644
--- a/src/main/java/com/networknt/schema/EnumValidator.java
+++ b/src/main/java/com/networknt/schema/EnumValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,8 +32,8 @@ public class EnumValidator extends BaseJsonValidator implements JsonValidator {
     private List<JsonNode> nodes;
     private String error;
 
-    public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ENUM);
+    public EnumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ENUM, validationContext);
         nodes = new ArrayList<JsonNode>();
         error = "[none]";
 
diff --git a/src/main/java/com/networknt/schema/ErrorMessageType.java b/src/main/java/com/networknt/schema/ErrorMessageType.java
new file mode 100644
index 0000000..7c9d330
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ErrorMessageType.java
@@ -0,0 +1,20 @@
+package com.networknt.schema;
+
+import java.text.MessageFormat;
+
+public interface ErrorMessageType {
+    /**
+     * Your error code. Please ensure global uniqueness. Builtin error codes are sequential numbers.
+     * 
+     * Customer error codes could have a prefix to denote the namespace of your custom keywords and errors.
+     * @return error code
+     */
+    String getErrorCode();
+
+    /**
+     * optional message format
+     * @return the message format or null if no message text shall be rendered. 
+     */
+    MessageFormat getMessageFormat();
+    
+}
diff --git a/src/main/java/com/networknt/schema/Format.java b/src/main/java/com/networknt/schema/Format.java
new file mode 100644
index 0000000..fa29fa4
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Format.java
@@ -0,0 +1,12 @@
+package com.networknt.schema;
+
+public interface Format {
+    /**
+     * @return the format name as referred to in a json schema format node.
+     */
+    String getName();
+    
+    boolean matches(String value);
+    
+    String getErrorMessageDescription();
+}
diff --git a/src/main/java/com/networknt/schema/FormatKeyword.java b/src/main/java/com/networknt/schema/FormatKeyword.java
new file mode 100644
index 0000000..8e1ffca
--- /dev/null
+++ b/src/main/java/com/networknt/schema/FormatKeyword.java
@@ -0,0 +1,40 @@
+package com.networknt.schema;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class FormatKeyword implements Keyword {
+    private final ValidatorTypeCode type;
+    private final Map<String, Format> formats;
+
+    public FormatKeyword(ValidatorTypeCode type, Map<String, Format> formats) {
+        this.type = type;
+        this.formats = formats;
+    }
+
+    Collection<Format> getFormats() {
+        return Collections.unmodifiableCollection(formats.values());
+    }
+    
+    @Override
+    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext)
+            throws Exception {
+        Format format = null;
+        if (schemaNode != null && schemaNode.isTextual()) {
+            String formatName = schemaNode.textValue();
+            format = formats.get(formatName);
+        }
+
+        return new FormatValidator(schemaPath, schemaNode, parentSchema, validationContext, format);
+    }
+    
+    @Override
+    public String getValue() {
+        return type.getValue();
+    }
+    
+    
+}
diff --git a/src/main/java/com/networknt/schema/FormatValidator.java b/src/main/java/com/networknt/schema/FormatValidator.java
index ca9e620..db35f4d 100644
--- a/src/main/java/com/networknt/schema/FormatValidator.java
+++ b/src/main/java/com/networknt/schema/FormatValidator.java
@@ -17,58 +17,22 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashSet;
-import java.util.Map;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
 
 public class FormatValidator extends BaseJsonValidator implements JsonValidator {
     private static final Logger logger = LoggerFactory.getLogger(FormatValidator.class);
-    private static final Map<String, Pattern> FORMATS = new HashMap<String, Pattern>();
 
-    static {
-        FORMATS.put("date-time", Pattern.compile(
-                "^\\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}[tT ]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([zZ]|[+-]\\d{2}:\\d{2})$"));
-        FORMATS.put("date", Pattern.compile("^\\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$"));
-        FORMATS.put("time", Pattern.compile("^\\d{2}:\\d{2}:\\d{2}$"));
-        FORMATS.put("email", Pattern.compile("^\\S+@\\S+$"));
-        FORMATS.put("ip-address", Pattern.compile(
-                "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"));
-        FORMATS.put("ipv4", Pattern.compile(
-                "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"));
-        FORMATS.put("ipv6", Pattern.compile(
-                "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$"));
-        FORMATS.put("uri", Pattern.compile("(^[a-zA-Z][a-zA-Z0-9+-.]*:[^\\s]*$)|(^//[^\\s]*$)"));
-        FORMATS.put("color", Pattern.compile(
-                "(#?([0-9A-Fa-f]{3,6})\\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\\(\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*\\))|(rgb\\(\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*\\))"));
-        FORMATS.put("hostname", Pattern.compile(
-                "^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$"));
-        FORMATS.put("alpha", Pattern.compile("^[a-zA-Z]+$"));
-        FORMATS.put("alphanumeric", Pattern.compile("^[a-zA-Z0-9]+$"));
-        FORMATS.put("phone", Pattern.compile("^\\+(?:[0-9] ?){6,14}[0-9]$"));
-        FORMATS.put("utc-millisec", Pattern.compile("^[0-9]+(\\.?[0-9]+)?$"));
-        FORMATS.put("style", Pattern.compile("\\s*(.+?):\\s*([^;]+);?"));
-    }
-
-    private String format;
-    private Pattern p;
-
-    public FormatValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.FORMAT);
-        format = "";
-        if (schemaNode != null && schemaNode.isTextual()) {
-            format = schemaNode.textValue();
-            p = FORMATS.get(format);
-        }
+    private final Format format;
 
+    public FormatValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext, Format format) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.FORMAT, validationContext);
+        this.format = format;
         parseErrorCode(getValidatorType().getErrorCodeKey());
     }
 
@@ -82,15 +46,14 @@ public class FormatValidator extends BaseJsonValidator implements JsonValidator
             return errors;
         }
 
-        if (p != null) {
+        if (format != null) {
             try {
-                Matcher m = p.matcher(node.textValue());
-                if (!m.matches()) {
-                    errors.add(buildValidationMessage(at, format, p.pattern()));
+                if (!format.matches(node.textValue())) {
+                    errors.add(buildValidationMessage(at, format.getName(), format.getErrorMessageDescription()));
                 }
             } catch (PatternSyntaxException pse) {
                 // String is considered valid if pattern is invalid
-                logger.error("Failed to apply pattern on " + at + ": Invalid RE syntax [" + format + "]", pse);
+                logger.error("Failed to apply pattern on " + at + ": Invalid RE syntax [" + format.getName() + "]", pse);
             }
         }
 
diff --git a/src/main/java/com/networknt/schema/ItemsValidator.java b/src/main/java/com/networknt/schema/ItemsValidator.java
index 38cbdcd..9818802 100644
--- a/src/main/java/com/networknt/schema/ItemsValidator.java
+++ b/src/main/java/com/networknt/schema/ItemsValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -36,14 +35,14 @@ public class ItemsValidator extends BaseJsonValidator implements JsonValidator {
     private boolean additionalItems = true;
     private JsonSchema additionalSchema;
 
-    public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS);
+    public ItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ITEMS, validationContext);
         if (schemaNode.isObject()) {
-            schema = new JsonSchema(mapper, getValidatorType().getValue(), schemaNode, parentSchema);
+            schema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
         } else {
             tupleSchema = new ArrayList<JsonSchema>();
             for (JsonNode s : schemaNode) {
-                tupleSchema.add(new JsonSchema(mapper, getValidatorType().getValue(), s, parentSchema));
+                tupleSchema.add(new JsonSchema(validationContext, getValidatorType().getValue(), s, parentSchema));
             }
 
             JsonNode addItemNode = getParentSchema().getSchemaNode().get(PROPERTY_ADDITIONAL_ITEMS);
@@ -51,7 +50,7 @@ public class ItemsValidator extends BaseJsonValidator implements JsonValidator {
                 if (addItemNode.isBoolean()) {
                     additionalItems = addItemNode.asBoolean();
                 } else if (addItemNode.isObject()) {
-                    additionalSchema = new JsonSchema(mapper, addItemNode);
+                    additionalSchema = new JsonSchema(validationContext, addItemNode);
                 }
             }
         }
diff --git a/src/main/java/com/networknt/schema/JsonMetaSchema.java b/src/main/java/com/networknt/schema/JsonMetaSchema.java
new file mode 100644
index 0000000..8fc64aa
--- /dev/null
+++ b/src/main/java/com/networknt/schema/JsonMetaSchema.java
@@ -0,0 +1,258 @@
+/*
+ * 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.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class JsonMetaSchema {
+
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(JsonMetaSchema.class);
+    private static Map<String, String> UNKNOWN_KEYWORDS = new ConcurrentHashMap<String, String>();
+    
+    private static class DraftV4 {
+        private static String URI = "http://json-schema.org/draft-04/schema#";
+        // Draft 6 uses "$id"
+        private static final String DRAFT_4_ID = "id";
+        public static final List<Format> BUILTIN_FORMATS = new ArrayList<Format>();
+        static {
+            BUILTIN_FORMATS.add(pattern("date-time", 
+                    "^\\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}[tT ]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([zZ]|[+-]\\d{2}:\\d{2})$"));
+            BUILTIN_FORMATS.add(pattern("date", "^\\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}$"));
+            BUILTIN_FORMATS.add(pattern("time", "^\\d{2}:\\d{2}:\\d{2}$"));
+            BUILTIN_FORMATS.add(pattern("email", "^\\S+@\\S+$"));
+            BUILTIN_FORMATS.add(pattern("ip-address", 
+                    "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"));
+            BUILTIN_FORMATS.add(pattern("ipv4", 
+                    "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"));
+            BUILTIN_FORMATS.add(pattern("ipv6", 
+                    "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$"));
+            BUILTIN_FORMATS.add(pattern("uri", "(^[a-zA-Z][a-zA-Z0-9+-.]*:[^\\s]*$)|(^//[^\\s]*$)"));
+            BUILTIN_FORMATS.add(pattern("color", 
+                    "(#?([0-9A-Fa-f]{3,6})\\b)|(aqua)|(black)|(blue)|(fuchsia)|(gray)|(green)|(lime)|(maroon)|(navy)|(olive)|(orange)|(purple)|(red)|(silver)|(teal)|(white)|(yellow)|(rgb\\(\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*,\\s*\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b\\s*\\))|(rgb\\(\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*,\\s*(\\d?\\d%|100%)+\\s*\\))"));
+            BUILTIN_FORMATS.add(pattern("hostname", 
+                    "^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])(\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]))*$"));
+            BUILTIN_FORMATS.add(pattern("alpha", "^[a-zA-Z]+$"));
+            BUILTIN_FORMATS.add(pattern("alphanumeric", "^[a-zA-Z0-9]+$"));
+            BUILTIN_FORMATS.add(pattern("phone", "^\\+(?:[0-9] ?){6,14}[0-9]$"));
+            BUILTIN_FORMATS.add(pattern("utc-millisec", "^[0-9]+(\\.?[0-9]+)?$"));
+            BUILTIN_FORMATS.add(pattern("style", "\\s*(.+?):\\s*([^;]+);?"));
+        }
+
+        static PatternFormat pattern(String name, String regex) {
+            return new PatternFormat(name, regex);
+        }
+        
+        public static JsonMetaSchema getInstance() {
+            return new Builder(URI)
+                    .idKeyword(DRAFT_4_ID)
+                    .addFormats(BUILTIN_FORMATS)
+                    .addKeywords(ValidatorTypeCode.getNonFormatKeywords())
+                    // keywords that may validly exist, but have no validation aspect to them
+                    .addKeywords(Arrays.asList(
+                            new NonValidationKeyword("$schema"),
+                            new NonValidationKeyword("id"),
+                            new NonValidationKeyword("title"), 
+                            new NonValidationKeyword("description"), 
+                            new NonValidationKeyword("default"),
+                            new NonValidationKeyword("definitions")
+                    ))
+                    .build();
+        }
+    }
+    
+    public static class Builder {
+        private Map<String, Keyword> keywords = new HashMap<String, Keyword>();
+        private Map<String, Format> formats = new HashMap<String, Format>();
+        private String uri;
+        private String idKeyword = "id";
+        
+        public Builder(String uri) {
+            this.uri = uri;
+        }
+        
+        private static Map<String, Keyword> createKeywordsMap(Map<String, Keyword> kwords, Map<String, Format> formats) {
+            final Map<String, Keyword> map = new HashMap<String, Keyword>();
+            for (Map.Entry<String, Keyword> type: kwords.entrySet()) {
+                String keywordName = type.getKey();
+                Keyword keyword = type.getValue();
+                if (ValidatorTypeCode.FORMAT.getValue().equals(keywordName)) {
+                    if (!(keyword instanceof FormatKeyword)) {
+                        throw new IllegalArgumentException("Overriding the keyword 'format' is not supported");
+                    }
+                    // ignore - format keyword will be created again below.
+                } else {
+                    map.put(keyword.getValue(), keyword);
+                }
+            }
+            final FormatKeyword formatKeyword = new FormatKeyword(ValidatorTypeCode.FORMAT, formats);
+            map.put(formatKeyword.getValue(), formatKeyword);
+            return Collections.unmodifiableMap(map);
+        }
+        
+        public Builder addKeyword(Keyword keyword) {
+            this.keywords.put(keyword.getValue(), keyword);
+            return this;
+        }
+        
+        public Builder addKeywords(Collection<? extends Keyword> keywords) {
+            for (Keyword keyword: keywords) {
+                this.keywords.put(keyword.getValue(), keyword);
+            }
+            return this;
+        }
+        
+        public Builder addFormat(Format format) {
+            this.formats.put(format.getName(), format);
+            return this;
+        }
+        
+        public Builder addFormats(Collection<? extends Format> formats) {
+            for (Format format : formats) {
+                addFormat(format);
+            }
+            return this;
+        }
+        
+        
+        public Builder idKeyword(String idKeyword) {
+            this.idKeyword = idKeyword;
+            return this;
+        }
+        
+        public JsonMetaSchema build() {
+            // create builtin keywords with (custom) formats.
+            final Map<String, Keyword> kwords = createKeywordsMap(keywords, formats);
+            return new JsonMetaSchema(uri, idKeyword, kwords);
+        }
+    }
+    
+    private final String uri;
+    private final String idKeyword;
+    private final Map<String, Keyword> keywords;
+
+    private JsonMetaSchema(String uri, String idKeyword, Map<String, Keyword> keywords) {
+        if (StringUtils.isBlank(uri)) {
+            throw new IllegalArgumentException("uri must not be null or blank");
+        }
+        if (StringUtils.isBlank(idKeyword)) {
+            throw new IllegalArgumentException("idKeyword must not be null or blank");
+        }
+        if (keywords == null) {
+            throw new IllegalArgumentException("keywords must not be null ");
+        }
+
+        this.uri = uri;
+        this.idKeyword = idKeyword;
+        this.keywords = keywords;
+    }
+
+    public static JsonMetaSchema getDraftV4() {
+        return DraftV4.getInstance();
+    }
+    
+    /**
+     * Builder without keywords or formats.
+     * 
+     * Use {@link #getDraftV4()} for the Draft 4 Metaschema, or if you need a builder based on Draft4, use
+     * 
+     * <code>
+     * JsonMetaSchema.builder("http://your-metaschema-uri", JsonSchemaFactory.getDraftV4()).build();
+     * </code>
+     * 
+     * @param uri the URI of the metaschema that will be defined via this builder.
+     * @return a builder instance without any keywords or formats - usually not what one needs.
+     */
+    public static Builder builder(String uri) {
+        return new Builder(uri);
+    }
+    
+    /**
+     * 
+     * @param uri the URI of your new JsonMetaSchema that will be defined via this builder.
+     * @param blueprint the JsonMetaSchema to base your custom JsonMetaSchema on.
+     * @return a builder instance preconfigured to be the same as blueprint, but with a different uri.
+     */
+    public static Builder builder(String uri, JsonMetaSchema blueprint) {
+        FormatKeyword formatKeyword = (FormatKeyword)blueprint.keywords.get(ValidatorTypeCode.FORMAT.getValue());
+        if (formatKeyword == null) {
+            throw new IllegalArgumentException("The formatKeyword did not exist - blueprint is invalid.");
+        }
+        return builder(uri)
+                .idKeyword(blueprint.idKeyword)
+                .addKeywords(blueprint.keywords.values())
+                .addFormats(formatKeyword.getFormats());
+    }
+
+    public String readId(JsonNode schemaNode) {
+        JsonNode idNode = schemaNode.get(idKeyword);
+        if (idNode == null || !idNode.isTextual()) {
+            return null;
+        }
+        return idNode.textValue();
+    }
+    
+    public String getUri() {
+        return uri;
+    }
+    
+    public Optional<JsonValidator> newValidator(ValidationContext validationContext, String schemaPath, String keyword /* keyword */, JsonNode schemaNode,
+            JsonSchema parentSchema) {
+        
+        try {
+            Keyword kw = keywords.get(keyword);
+            if (kw == null) {
+                if (!UNKNOWN_KEYWORDS.containsKey(keyword)) {
+                    UNKNOWN_KEYWORDS.put(keyword, keyword);
+                    logger.warn("Unknown keyword " + keyword + " - you should define your own Meta Schema. If the keyword is irrelevant for validation, just use a NonValidationKeyword");
+                }
+                return Optional.empty();
+            }
+            return Optional.of(kw.newValidator(schemaPath, schemaNode, parentSchema, validationContext));
+        } catch (InvocationTargetException e) {
+            if (e.getTargetException() instanceof JsonSchemaException) {
+                throw (JsonSchemaException) e.getTargetException();
+            } else {
+                logger.warn("Could not load validator " + keyword);
+                throw new JsonSchemaException(e.getTargetException());
+            }
+        } catch(JsonSchemaException e) {
+            throw e;
+        } catch (Exception e) {
+            logger.warn("Could not load validator " + keyword);
+            throw new JsonSchemaException(e);
+        }
+    }
+    
+
+}
diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java
index 9447b0c..4d68768 100644
--- a/src/main/java/com/networknt/schema/JsonSchema.java
+++ b/src/main/java/com/networknt/schema/JsonSchema.java
@@ -16,54 +16,48 @@
 
 package com.networknt.schema;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
 import java.net.URLDecoder;
-import java.util.*;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Optional;
+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 Logger logger = LoggerFactory.getLogger(JsonSchema.class);
     private static final Pattern intPattern = Pattern.compile("^[0-9]+$");
-    protected Map<String, JsonValidator> validators;
-    private ObjectMapper mapper;
+    protected final Map<String, JsonValidator> validators;
+    private final ValidationContext validationContext;
 
-    JsonSchema(ObjectMapper mapper, JsonNode schemaNode) {
-        this(mapper, "#", schemaNode, null);
+    JsonSchema(ValidationContext validationContext,  JsonNode schemaNode) {
+        this(validationContext,  "#", schemaNode, null);
     }
 
-    JsonSchema(ObjectMapper mapper, String schemaPath, JsonNode schemaNode,
+    JsonSchema(ValidationContext validationContext,  String schemaPath, JsonNode schemaNode,
                JsonSchema parent) {
-        super(schemaPath, schemaNode, parent, null);
-        this.init(mapper, schemaNode);
+        this(validationContext,  schemaPath, schemaNode, parent, obainSubSchemaNode(schemaNode, validationContext));
     }
 
-    JsonSchema(ObjectMapper mapper, String schemaPath, JsonNode schemaNode,
+    JsonSchema(ValidationContext validatorFactory,  String schemaPath, JsonNode schemaNode,
                JsonSchema parent, JsonSchema subSchema) {
         super(schemaPath, schemaNode, parent, null, subSchema);
-        this.init(mapper, schemaNode);
+        this.validationContext = validatorFactory;
+        this.validators = Collections.unmodifiableMap(this.read(schemaNode));
     }
 
-    public JsonSchema(ObjectMapper mapper, JsonNode schemaNode, JsonSchema subSchema) {
-        this(mapper, "#", schemaNode, null, subSchema);
-    }
-
-    private void init(ObjectMapper mapper, JsonNode schemaNode) {
-        this.mapper = mapper;
-        this.validators = new LinkedHashMap<String, JsonValidator>();
-        this.read(schemaNode);
+    public JsonSchema(ValidationContext validationContext,  JsonNode schemaNode, JsonSchema subSchema) {
+        this(validationContext, "#", schemaNode, null, subSchema);
     }
 
     /**
@@ -109,50 +103,26 @@ public class JsonSchema extends BaseJsonValidator {
         return ancestor;
     }
 
-    @SuppressWarnings("unchecked")
-    private void read(JsonNode schemaNode) {
+    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);
 
-            String shortClassName = pname;
-            if (shortClassName.startsWith("$")) {
-                // remove "$" from class name for $ref schema
-                shortClassName = shortClassName.substring(1);
+            Optional<JsonValidator> validator = validationContext.newValidator(getSchemaPath(), pname, n, this);
+            if (validator.isPresent()) {
+                validators.put(getSchemaPath() + "/" + pname, validator.get());
             }
 
-            try {
-                ValidatorTypeCode.fromValue(shortClassName);
-
-                String className = Character.toUpperCase(shortClassName.charAt(0))
-                        + shortClassName.substring(1) + "Validator";
-                Class<JsonValidator> clazz = (Class<JsonValidator>) Class
-                        .forName("com.networknt.schema." + className);
-                Constructor<JsonValidator> c = null;
-                c = clazz.getConstructor(new Class[]{String.class,
-                        JsonNode.class, JsonSchema.class, ObjectMapper.class});
-                validators.put(getSchemaPath() + "/" + pname, c.newInstance(
-                        getSchemaPath() + "/" + pname, n, this, mapper));
-            } catch (IllegalArgumentException e) {
-                // ignore unsupported schema node
-            } catch (InvocationTargetException e) {
-                if (e.getTargetException() instanceof JsonSchemaException) {
-                    throw (JsonSchemaException) e.getTargetException();
-                } else {
-                    logger.info("Could not load validator " + pname);
-                }
-            } catch (Exception e) {
-                logger.info("Could not load validator " + pname);
-            }
         }
+        return validators;
     }
 
-    public Set<ValidationMessage> validate(JsonNode JsonNode,
-                                           JsonNode rootNode, String at) {
-        Set<ValidationMessage> errors = new HashSet<ValidationMessage>();
+    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));
+            errors.addAll(v.validate(jsonNode, rootNode, at));
         }
         return errors;
     }
diff --git a/src/main/java/com/networknt/schema/JsonSchemaException.java b/src/main/java/com/networknt/schema/JsonSchemaException.java
index 7a48a1f..5c1d744 100644
--- a/src/main/java/com/networknt/schema/JsonSchemaException.java
+++ b/src/main/java/com/networknt/schema/JsonSchemaException.java
@@ -19,6 +19,10 @@ package com.networknt.schema;
 public class JsonSchemaException extends RuntimeException {
     private static final long serialVersionUID = -7805792737596582110L;
 
+    public JsonSchemaException(ValidationMessage validationMessage) {
+        super(validationMessage.getMessage());
+    }
+    
     public JsonSchemaException(String message) {
         super(message);
     }
diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
index 3e53e36..fa67982 100644
--- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java
+++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java
@@ -16,36 +16,153 @@
 
 package com.networknt.schema;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
-public class JsonSchemaFactory {
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.url.StandardURLFetcher;
+import com.networknt.schema.url.URLFetcher;
 
-    // Draft 6 uses "$id"
-    private static final String DRAFT_4_ID = "id";
+public class JsonSchemaFactory {
 
     private static final Logger logger = LoggerFactory
             .getLogger(JsonSchemaFactory.class);
-    private ObjectMapper mapper;
+    
+    
+    public static class Builder {
+        private ObjectMapper objectMapper = new ObjectMapper();
+        private URLFetcher urlFetcher;
+        private String defaultMetaSchemaURI;
+        private Map<String, JsonMetaSchema> jsonMetaSchemas = new HashMap<String, JsonMetaSchema>();
+        
+        public Builder objectMapper(ObjectMapper objectMapper) {
+            this.objectMapper = objectMapper;
+            return this;
+        }
+        
+        public Builder urlFetcher(URLFetcher urlFetcher) {
+            this.urlFetcher = urlFetcher;
+            return this;
+        }
+        
+        public Builder defaultMetaSchemaURI(String defaultMetaSchemaURI) {
+            this.defaultMetaSchemaURI = defaultMetaSchemaURI;
+            return this;
+        }
+        
+        public Builder addMetaSchema(JsonMetaSchema jsonMetaSchema) {
+            this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema);
+            return this;
+        }
+        
+        public Builder addMetaSchemas(Collection<? extends JsonMetaSchema> jsonMetaSchemas) {
+            for (JsonMetaSchema jsonMetaSchema: jsonMetaSchemas) {
+                this.jsonMetaSchemas.put(jsonMetaSchema.getUri(), jsonMetaSchema);
+            }
+            return this;
+        }
+        
+        public JsonSchemaFactory build() {
+            // create builtin keywords with (custom) formats.
+            return new JsonSchemaFactory(
+                    objectMapper == null ? new ObjectMapper() : objectMapper, 
+                    urlFetcher == null ? new StandardURLFetcher(): urlFetcher, 
+                    defaultMetaSchemaURI, 
+                    jsonMetaSchemas
+            );
+        }
+    }
+    
+    private final ObjectMapper mapper;
+    private final URLFetcher urlFetcher;
+    private final String defaultMetaSchemaURI;
+    private final Map<String, JsonMetaSchema> jsonMetaSchemas;
+
+    private JsonSchemaFactory(ObjectMapper mapper, URLFetcher urlFetcher, String defaultMetaSchemaURI, Map<String, JsonMetaSchema> jsonMetaSchemas) {
+        if (mapper == null) {
+            throw new IllegalArgumentException("ObjectMapper must not be null");
+        }
+        if (urlFetcher == null) {
+            throw new IllegalArgumentException("URLFetcher must not be null");
+        }
+        if (defaultMetaSchemaURI == null || defaultMetaSchemaURI.trim().isEmpty()) {
+            throw new IllegalArgumentException("defaultMetaSchemaURI must not be null or empty");
+        }
+        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");
+        }
+        this.mapper = mapper;
+        this.defaultMetaSchemaURI = defaultMetaSchemaURI;
+        this.urlFetcher = urlFetcher;
+        this.jsonMetaSchemas = jsonMetaSchemas;
+    }
 
-    public JsonSchemaFactory() {
-        this(new ObjectMapper());
+    /**
+     * Builder without keywords or formats.
+     * 
+     * Use {@link #getDraftV4()} instead, or if you need a builder based on Draft4, use
+     * 
+     * <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(JsonSchemaFactory blueprint) {
+        return builder()
+                .addMetaSchemas(blueprint.jsonMetaSchemas.values())
+                .urlFetcher(blueprint.urlFetcher)
+                .defaultMetaSchemaURI(blueprint.defaultMetaSchemaURI)
+                .objectMapper(blueprint.mapper);
+    }
+    
+    private JsonSchema newJsonSchema(JsonNode schemaNode) {
+        final ValidationContext validationContext = createValidationContext(schemaNode);
+        return new JsonSchema(validationContext, schemaNode);
     }
 
-    public JsonSchemaFactory(ObjectMapper mapper) {
-        this.mapper = mapper;
+    protected ValidationContext createValidationContext(JsonNode schemaNode) {
+        final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
+        return new ValidationContext(jsonMetaSchema, this);
     }
 
+    private JsonMetaSchema findMetaSchemaForSchema(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;
+    }
+    
     public JsonSchema getSchema(String schema) {
         try {
-            JsonNode schemaNode = mapper.readTree(schema);
-            return new JsonSchema(mapper, schemaNode);
+            final JsonNode schemaNode = mapper.readTree(schema);
+            return newJsonSchema(schemaNode);
         } catch (IOException ioe) {
             logger.error("Failed to load json schema!", ioe);
             throw new JsonSchemaException(ioe);
@@ -54,8 +171,8 @@ public class JsonSchemaFactory {
 
     public JsonSchema getSchema(InputStream schemaStream) {
         try {
-            JsonNode schemaNode = mapper.readTree(schemaStream);
-            return new JsonSchema(mapper, schemaNode);
+            final JsonNode schemaNode = mapper.readTree(schemaStream);
+            return newJsonSchema(schemaNode);
         } catch (IOException ioe) {
             logger.error("Failed to load json schema!", ioe);
             throw new JsonSchemaException(ioe);
@@ -64,15 +181,23 @@ public class JsonSchemaFactory {
 
     public JsonSchema getSchema(URL schemaURL) {
         try {
-
-            JsonNode schemaNode = mapper.readTree(schemaURL.openStream());
-
-            if (this.idMatchesSourceUrl(schemaNode, schemaURL)) {
-                return new JsonSchema(mapper, schemaNode, null);
+            InputStream inputStream = null;
+            try {
+                inputStream = urlFetcher.fetch(schemaURL);
+                JsonNode schemaNode = mapper.readTree(inputStream);
+                final JsonMetaSchema jsonMetaSchema = findMetaSchemaForSchema(schemaNode);
+
+                if (idMatchesSourceUrl(jsonMetaSchema, schemaNode, schemaURL)) {
+                    
+                    return new JsonSchema(new ValidationContext(jsonMetaSchema, this), schemaNode, null);
+                }
+
+                return newJsonSchema(schemaNode);
+            } finally {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
             }
-
-            return new JsonSchema(mapper, schemaNode);
-
         } catch (IOException ioe) {
             logger.error("Failed to load json schema!", ioe);
             throw new JsonSchemaException(ioe);
@@ -80,20 +205,20 @@ public class JsonSchemaFactory {
     }
 
     public JsonSchema getSchema(JsonNode jsonNode) {
-        return new JsonSchema(mapper, jsonNode);
+        return newJsonSchema(jsonNode);
     }
 
-    private boolean idMatchesSourceUrl(JsonNode schema, URL schemaUrl) {
-
-        JsonNode idNode = schema.get(DRAFT_4_ID);
+    private boolean idMatchesSourceUrl(JsonMetaSchema metaSchema, JsonNode schema, URL schemaUrl) {
 
-        if (idNode == null) {
+        String id = metaSchema.readId(schema);
+        if (id == null || id.isEmpty()) {
             return false;
         }
-
-        String id = idNode.asText();
-        logger.info("Matching " + id + " to " + schemaUrl.toString());
-        return id.equals(schemaUrl.toString());
+        boolean result = id.equals(schemaUrl.toString());
+        if (logger.isDebugEnabled()) {
+            logger.debug("Matching " + id + " to " + schemaUrl.toString() + ": " + result);
+        }
+        return result;
 
     }
 
diff --git a/src/main/java/com/networknt/schema/JsonValidator.java b/src/main/java/com/networknt/schema/JsonValidator.java
index 0360bfe..5667bbe 100644
--- a/src/main/java/com/networknt/schema/JsonValidator.java
+++ b/src/main/java/com/networknt/schema/JsonValidator.java
@@ -25,6 +25,7 @@ import java.util.Set;
  */
 public interface JsonValidator {
     public static final String AT_ROOT = "$";
+    
 
     /**
      * Validate the given root JsonNode, starting at the root of the data path.
diff --git a/src/main/java/com/networknt/schema/Keyword.java b/src/main/java/com/networknt/schema/Keyword.java
new file mode 100644
index 0000000..7f925fa
--- /dev/null
+++ b/src/main/java/com/networknt/schema/Keyword.java
@@ -0,0 +1,10 @@
+package com.networknt.schema;
+
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public interface Keyword {
+    String getValue();
+    
+    JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws JsonSchemaException, Exception;
+}
diff --git a/src/main/java/com/networknt/schema/MaxItemsValidator.java b/src/main/java/com/networknt/schema/MaxItemsValidator.java
index b484f62..bd2461c 100644
--- a/src/main/java/com/networknt/schema/MaxItemsValidator.java
+++ b/src/main/java/com/networknt/schema/MaxItemsValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,8 +28,8 @@ public class MaxItemsValidator extends BaseJsonValidator implements JsonValidato
 
     private int max = 0;
 
-    public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_ITEMS);
+    public MaxItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_ITEMS, validationContext);
         if (schemaNode.isIntegralNumber()) {
             max = schemaNode.intValue();
         }
diff --git a/src/main/java/com/networknt/schema/MaxLengthValidator.java b/src/main/java/com/networknt/schema/MaxLengthValidator.java
index 858a4a9..06bce9b 100644
--- a/src/main/java/com/networknt/schema/MaxLengthValidator.java
+++ b/src/main/java/com/networknt/schema/MaxLengthValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,8 +28,8 @@ public class MaxLengthValidator extends BaseJsonValidator implements JsonValidat
 
     private int maxLength;
 
-    public MaxLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_LENGTH);
+    public MaxLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_LENGTH, validationContext);
         maxLength = Integer.MAX_VALUE;
         if (schemaNode != null && schemaNode.isIntegralNumber()) {
             maxLength = schemaNode.intValue();
diff --git a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
index 89b2ad7..ef7a2d5 100644
--- a/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/MaxPropertiesValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,8 +29,8 @@ public class MaxPropertiesValidator extends BaseJsonValidator implements JsonVal
     private int max;
 
     public MaxPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                                  ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_PROPERTIES);
+            ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAX_PROPERTIES, validationContext);
         if (schemaNode.isIntegralNumber()) {
             max = schemaNode.intValue();
         }
diff --git a/src/main/java/com/networknt/schema/MaximumValidator.java b/src/main/java/com/networknt/schema/MaximumValidator.java
index 02c15df..10f67a6 100644
--- a/src/main/java/com/networknt/schema/MaximumValidator.java
+++ b/src/main/java/com/networknt/schema/MaximumValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,9 +30,9 @@ public class MaximumValidator extends BaseJsonValidator implements JsonValidator
     private double maximum;
     private boolean excludeEqual = false;
 
-    public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
+    public MaximumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
 
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM);
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MAXIMUM, validationContext);
         if (schemaNode.isNumber()) {
             maximum = schemaNode.doubleValue();
         } else {
diff --git a/src/main/java/com/networknt/schema/MinItemsValidator.java b/src/main/java/com/networknt/schema/MinItemsValidator.java
index 8c81dc8..050794e 100644
--- a/src/main/java/com/networknt/schema/MinItemsValidator.java
+++ b/src/main/java/com/networknt/schema/MinItemsValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,8 +28,8 @@ public class MinItemsValidator extends BaseJsonValidator implements JsonValidato
 
     private int min = 0;
 
-    public MinItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_ITEMS);
+    public MinItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_ITEMS, validationContext);
         if (schemaNode.isIntegralNumber()) {
             min = schemaNode.intValue();
         }
diff --git a/src/main/java/com/networknt/schema/MinLengthValidator.java b/src/main/java/com/networknt/schema/MinLengthValidator.java
index c9ec3e7..56f9dbc 100644
--- a/src/main/java/com/networknt/schema/MinLengthValidator.java
+++ b/src/main/java/com/networknt/schema/MinLengthValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,8 +28,8 @@ public class MinLengthValidator extends BaseJsonValidator implements JsonValidat
 
     private int minLength;
 
-    public MinLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_LENGTH);
+    public MinLengthValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_LENGTH, validationContext);
         minLength = Integer.MIN_VALUE;
         if (schemaNode != null && schemaNode.isIntegralNumber()) {
             minLength = schemaNode.intValue();
diff --git a/src/main/java/com/networknt/schema/MinPropertiesValidator.java b/src/main/java/com/networknt/schema/MinPropertiesValidator.java
index ce4b400..076bf1f 100644
--- a/src/main/java/com/networknt/schema/MinPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/MinPropertiesValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,8 +29,8 @@ public class MinPropertiesValidator extends BaseJsonValidator implements JsonVal
     protected int min;
 
     public MinPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                                  ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_PROPERTIES);
+            ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MIN_PROPERTIES, validationContext);
         if (schemaNode.isIntegralNumber()) {
             min = schemaNode.intValue();
         }
diff --git a/src/main/java/com/networknt/schema/MinimumValidator.java b/src/main/java/com/networknt/schema/MinimumValidator.java
index 3cb360b..3e5107b 100644
--- a/src/main/java/com/networknt/schema/MinimumValidator.java
+++ b/src/main/java/com/networknt/schema/MinimumValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,8 +30,8 @@ public class MinimumValidator extends BaseJsonValidator implements JsonValidator
     private double minimum;
     private boolean excluded = false;
 
-    public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM);
+    public MinimumValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MINIMUM, validationContext);
         if (schemaNode.isNumber()) {
             minimum = schemaNode.doubleValue();
         } else {
diff --git a/src/main/java/com/networknt/schema/MultipleOfValidator.java b/src/main/java/com/networknt/schema/MultipleOfValidator.java
index f214be1..b91859a 100644
--- a/src/main/java/com/networknt/schema/MultipleOfValidator.java
+++ b/src/main/java/com/networknt/schema/MultipleOfValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,9 +28,9 @@ public class MultipleOfValidator extends BaseJsonValidator implements JsonValida
 
     private double divisor = 0;
 
-    public MultipleOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
+    public MultipleOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
 
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MULTIPLE_OF);
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.MULTIPLE_OF, validationContext);
         if (schemaNode.isNumber()) {
             divisor = schemaNode.doubleValue();
         }
diff --git a/src/main/java/com/networknt/schema/NonValidationKeyword.java b/src/main/java/com/networknt/schema/NonValidationKeyword.java
new file mode 100644
index 0000000..0a0e065
--- /dev/null
+++ b/src/main/java/com/networknt/schema/NonValidationKeyword.java
@@ -0,0 +1,33 @@
+package com.networknt.schema;
+
+import java.util.Collections;
+import java.util.Set;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+/**
+ * Used for Keywords that have no validation aspect, but are part of the metaschema.
+ */
+public class NonValidationKeyword extends AbstractKeyword {
+
+    private static final class Validator extends AbstractJsonValidator {
+        private Validator(String keyword) {
+            super(keyword);
+        }
+
+        @Override
+        public Set<ValidationMessage> validate(JsonNode node, JsonNode rootNode, String at) {
+            return Collections.emptySet();
+        }
+    }
+
+    public NonValidationKeyword(String keyword) {
+        super(keyword);
+    }
+    
+    @Override
+    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
+            ValidationContext validationContext) throws JsonSchemaException, Exception {
+        return new Validator(getValue());
+    }
+}
diff --git a/src/main/java/com/networknt/schema/NotAllowedValidator.java b/src/main/java/com/networknt/schema/NotAllowedValidator.java
index ee9f7f4..1bfa7f4 100644
--- a/src/main/java/com/networknt/schema/NotAllowedValidator.java
+++ b/src/main/java/com/networknt/schema/NotAllowedValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,9 +31,9 @@ public class NotAllowedValidator extends BaseJsonValidator implements JsonValida
 
     private List<String> fieldNames = new ArrayList<String>();
 
-    public NotAllowedValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
+    public NotAllowedValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
 
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT_ALLOWED);
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT_ALLOWED, validationContext);
         if (schemaNode.isArray()) {
             int size = schemaNode.size();
             for (int i = 0; i < size; i++) {
diff --git a/src/main/java/com/networknt/schema/NotValidator.java b/src/main/java/com/networknt/schema/NotValidator.java
index b0ee57e..87aaf21 100644
--- a/src/main/java/com/networknt/schema/NotValidator.java
+++ b/src/main/java/com/networknt/schema/NotValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,9 +28,9 @@ public class NotValidator extends BaseJsonValidator implements JsonValidator {
 
     private JsonSchema schema;
 
-    public NotValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT);
-        schema = new JsonSchema(mapper, getValidatorType().getValue(), schemaNode, parentSchema);
+    public NotValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.NOT, validationContext);
+        schema = new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode, parentSchema);
 
         parseErrorCode(getValidatorType().getErrorCodeKey());
     }
diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java
index b8f39cf..65f5308 100644
--- a/src/main/java/com/networknt/schema/OneOfValidator.java
+++ b/src/main/java/com/networknt/schema/OneOfValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -34,11 +33,11 @@ public class OneOfValidator extends BaseJsonValidator implements JsonValidator {
 
     private List<JsonSchema> schemas = new ArrayList<JsonSchema>();
 
-    public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ONE_OF);
+    public OneOfValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.ONE_OF, validationContext);
         int size = schemaNode.size();
         for (int i = 0; i < size; i++) {
-            schemas.add(new JsonSchema(mapper, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
+            schemas.add(new JsonSchema(validationContext, getValidatorType().getValue(), schemaNode.get(i), parentSchema));
         }
 
         parseErrorCode(getValidatorType().getErrorCodeKey());
@@ -68,8 +67,7 @@ public class OneOfValidator extends BaseJsonValidator implements JsonValidator {
             for (Iterator<ValidationMessage> it = errors.iterator(); it.hasNext();) {
                 ValidationMessage msg = it.next();
                 
-                if (ValidatorTypeCode.ADDITIONAL_PROPERTIES.equals(ValidatorTypeCode.fromValue(msg
-                        .getType()))) {
+                if (ValidatorTypeCode.ADDITIONAL_PROPERTIES.getValue().equals(msg.getType())) {
                     it.remove();
                 }
             }
diff --git a/src/main/java/com/networknt/schema/PatternFormat.java b/src/main/java/com/networknt/schema/PatternFormat.java
new file mode 100644
index 0000000..bc5df8d
--- /dev/null
+++ b/src/main/java/com/networknt/schema/PatternFormat.java
@@ -0,0 +1,27 @@
+package com.networknt.schema;
+
+import java.util.regex.Pattern;
+
+public class PatternFormat implements Format {
+    private final String name; 
+    private final Pattern pattern;
+    
+    public PatternFormat(String name, String regex) {
+        this.name = name;
+        this.pattern = Pattern.compile(regex);
+    }
+    
+    @Override
+    public boolean matches(String value) {
+        return pattern.matcher(value).matches();
+    }
+    
+    @Override
+    public String getName() {
+        return name;
+    }
+    @Override
+    public String getErrorMessageDescription() {
+        return pattern.pattern();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
index b169f7e..905a2a5 100644
--- a/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/PatternPropertiesValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,15 +30,15 @@ public class PatternPropertiesValidator extends BaseJsonValidator implements Jso
     private Map<Pattern, JsonSchema> schemas = new IdentityHashMap<Pattern, JsonSchema>();
 
     public PatternPropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema,
-                                      ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN_PROPERTIES);
+            ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN_PROPERTIES, validationContext);
         if (!schemaNode.isObject()) {
             throw new JsonSchemaException("patternProperties must be an object node");
         }
         Iterator<String> names = schemaNode.fieldNames();
         while (names.hasNext()) {
             String name = names.next();
-            schemas.put(Pattern.compile(name), new JsonSchema(mapper, name, schemaNode.get(name), parentSchema));
+            schemas.put(Pattern.compile(name), new JsonSchema(validationContext, name, schemaNode.get(name), parentSchema));
         }
     }
 
diff --git a/src/main/java/com/networknt/schema/PatternValidator.java b/src/main/java/com/networknt/schema/PatternValidator.java
index d2a9f98..096b43f 100644
--- a/src/main/java/com/networknt/schema/PatternValidator.java
+++ b/src/main/java/com/networknt/schema/PatternValidator.java
@@ -17,12 +17,10 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -34,9 +32,9 @@ public class PatternValidator extends BaseJsonValidator implements JsonValidator
     private String pattern;
     private Pattern p;
 
-    public PatternValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
+    public PatternValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
 
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN);
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PATTERN, validationContext);
         pattern = "";
         if (schemaNode != null && schemaNode.isTextual()) {
             pattern = schemaNode.textValue();
diff --git a/src/main/java/com/networknt/schema/PropertiesValidator.java b/src/main/java/com/networknt/schema/PropertiesValidator.java
index b7e1173..4c68641 100644
--- a/src/main/java/com/networknt/schema/PropertiesValidator.java
+++ b/src/main/java/com/networknt/schema/PropertiesValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -28,12 +27,12 @@ public class PropertiesValidator extends BaseJsonValidator implements JsonValida
     private static final Logger logger = LoggerFactory.getLogger(PropertiesValidator.class);
     private Map<String, JsonSchema> schemas;
 
-    public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTIES);
+    public PropertiesValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.PROPERTIES, validationContext);
         schemas = new HashMap<String, JsonSchema>();
         for (Iterator<String> it = schemaNode.fieldNames(); it.hasNext(); ) {
             String pname = it.next();
-            schemas.put(pname, new JsonSchema(mapper, schemaPath + "/" + pname, schemaNode.get(pname), parentSchema));
+            schemas.put(pname, new JsonSchema(validationContext, schemaPath + "/" + pname, schemaNode.get(pname), parentSchema));
         }
     }
 
diff --git a/src/main/java/com/networknt/schema/ReadOnlyValidator.java b/src/main/java/com/networknt/schema/ReadOnlyValidator.java
index def91c3..3d8ea2c 100644
--- a/src/main/java/com/networknt/schema/ReadOnlyValidator.java
+++ b/src/main/java/com/networknt/schema/ReadOnlyValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,8 +31,8 @@ public class ReadOnlyValidator extends BaseJsonValidator implements JsonValidato
 
     private List<String> fieldNames = new ArrayList<String>();
 
-    public ReadOnlyValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.READ_ONLY);
+    public ReadOnlyValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.READ_ONLY, validationContext);
         if (schemaNode.isArray()) {
             int size = schemaNode.size();
             for (int i = 0; i < size; i++) {
diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java
index c6bb7a5..6fae589 100644
--- a/src/main/java/com/networknt/schema/RefValidator.java
+++ b/src/main/java/com/networknt/schema/RefValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.networknt.schema.url.URLFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,28 +36,27 @@ public class RefValidator extends BaseJsonValidator implements JsonValidator {
     private final String REF_CURRENT = "#";
     private final String REF_RELATIVE = "../";
 
-    public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
+    public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
 
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF);
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext);
         String refValue = schemaNode.asText();
         if (!refValue.startsWith(REF_CURRENT)) {
             // handle remote ref
-        	String schemaUrl = refValue;
-        	int index = refValue.indexOf(REF_CURRENT);
+            String schemaUrl = refValue;
+        	    int index = refValue.indexOf(REF_CURRENT);
             if (index > 0) {
                 schemaUrl = schemaUrl.substring(0, index);
             }
-        	if(isRelativePath(schemaUrl)){
-        		schemaUrl = obtainAbsolutePath(parentSchema, schemaUrl);
-        	}
+            if(isRelativePath(schemaUrl)){
+                schemaUrl = obtainAbsolutePath(parentSchema, schemaUrl);
+            }
             
-            JsonSchemaFactory factory = new JsonSchemaFactory(mapper);
             try {
                 URL url = URLFactory.toURL(schemaUrl);
-                parentSchema = factory.getSchema(url);
+                parentSchema = validationContext.getJsonSchemaFactory().getSchema(url);
             } catch (MalformedURLException e) {
                 InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(schemaUrl);
-                parentSchema = factory.getSchema(is);
+                parentSchema = validationContext.getJsonSchemaFactory().getSchema(is);
             }
             if (index < 0) {
                 schema = parentSchema.findAncestor();
@@ -71,7 +69,7 @@ public class RefValidator extends BaseJsonValidator implements JsonValidator {
         } else {
             JsonNode node = parentSchema.getRefSchemaNode(refValue);
             if (node != null) {
-                schema = new JsonSchema(mapper, refValue, node, parentSchema);
+                schema = new JsonSchema(validationContext, refValue, node, parentSchema);
             }
         }
     }
diff --git a/src/main/java/com/networknt/schema/RequiredValidator.java b/src/main/java/com/networknt/schema/RequiredValidator.java
index 96185ca..e77e7e5 100644
--- a/src/main/java/com/networknt/schema/RequiredValidator.java
+++ b/src/main/java/com/networknt/schema/RequiredValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,9 +31,9 @@ public class RequiredValidator extends BaseJsonValidator implements JsonValidato
 
     private List<String> fieldNames = new ArrayList<String>();
 
-    public RequiredValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
+    public RequiredValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
 
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REQUIRED);
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REQUIRED, validationContext);
         if (schemaNode.isArray()) {
             int size = schemaNode.size();
             for (int i = 0; i < size; i++) {
diff --git a/src/main/java/com/networknt/schema/TypeValidator.java b/src/main/java/com/networknt/schema/TypeValidator.java
index a8ecb4b..f370be9 100644
--- a/src/main/java/com/networknt/schema/TypeValidator.java
+++ b/src/main/java/com/networknt/schema/TypeValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,12 +29,12 @@ public class TypeValidator extends BaseJsonValidator implements JsonValidator {
     private JsonType schemaType;
     private UnionTypeValidator unionTypeValidator;
 
-    public TypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.TYPE);
+    public TypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.TYPE, validationContext);
         schemaType = TypeFactory.getSchemaNodeType(schemaNode);
 
         if (schemaType == JsonType.UNION) {
-            unionTypeValidator = new UnionTypeValidator(schemaPath, schemaNode, parentSchema, mapper);
+            unionTypeValidator = new UnionTypeValidator(schemaPath, schemaNode, parentSchema, validationContext);
         }
 
         parseErrorCode(getValidatorType().getErrorCodeKey());
diff --git a/src/main/java/com/networknt/schema/UnionTypeValidator.java b/src/main/java/com/networknt/schema/UnionTypeValidator.java
index a355ad3..52534f4 100644
--- a/src/main/java/com/networknt/schema/UnionTypeValidator.java
+++ b/src/main/java/com/networknt/schema/UnionTypeValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,8 +31,8 @@ public class UnionTypeValidator extends BaseJsonValidator implements JsonValidat
     private List<JsonValidator> schemas;
     private String error;
 
-    public UnionTypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UNION_TYPE);
+    public UnionTypeValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UNION_TYPE, validationContext);
         schemas = new ArrayList<JsonValidator>();
 
         StringBuilder errorBuilder = new StringBuilder();
@@ -51,9 +50,9 @@ public class UnionTypeValidator extends BaseJsonValidator implements JsonValidat
             sep = ", ";
 
             if (n.isObject())
-                schemas.add(new JsonSchema(mapper, ValidatorTypeCode.TYPE.getValue(), n, parentSchema));
+                schemas.add(new JsonSchema(validationContext, ValidatorTypeCode.TYPE.getValue(), n, parentSchema));
             else
-                schemas.add(new TypeValidator(schemaPath + "/" + i, n, parentSchema, mapper));
+                schemas.add(new TypeValidator(schemaPath + "/" + i, n, parentSchema, validationContext));
 
             i++;
         }
diff --git a/src/main/java/com/networknt/schema/UniqueItemsValidator.java b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
index ccac9d3..05eabc0 100644
--- a/src/main/java/com/networknt/schema/UniqueItemsValidator.java
+++ b/src/main/java/com/networknt/schema/UniqueItemsValidator.java
@@ -17,7 +17,6 @@
 package com.networknt.schema;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -30,8 +29,8 @@ public class UniqueItemsValidator extends BaseJsonValidator implements JsonValid
 
     private boolean unique = false;
 
-    public UniqueItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ObjectMapper mapper) {
-        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UNIQUE_ITEMS);
+    public UniqueItemsValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) {
+        super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.UNIQUE_ITEMS, validationContext);
         if (schemaNode.isBoolean()) {
             unique = schemaNode.booleanValue();
         }
diff --git a/src/main/java/com/networknt/schema/ValidationContext.java b/src/main/java/com/networknt/schema/ValidationContext.java
new file mode 100644
index 0000000..3166839
--- /dev/null
+++ b/src/main/java/com/networknt/schema/ValidationContext.java
@@ -0,0 +1,30 @@
+package com.networknt.schema;
+
+import java.util.Optional;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class ValidationContext {
+    private final JsonMetaSchema metaSchema;
+    private final JsonSchemaFactory jsonSchemaFactory;
+    
+    public ValidationContext(JsonMetaSchema metaSchema, JsonSchemaFactory jsonSchemaFactory) {
+        if (metaSchema == null) {
+            throw new IllegalArgumentException("JsonMetaSchema must not be null");
+        }
+        if (jsonSchemaFactory == null) {
+            throw new IllegalArgumentException("JsonSchemaFactory must not be null");
+        }
+        this.metaSchema = metaSchema;
+        this.jsonSchemaFactory = jsonSchemaFactory;
+    }
+    
+    public Optional<JsonValidator> newValidator(String schemaPath, String keyword /* keyword */, JsonNode schemaNode,
+            JsonSchema parentSchema) {
+        return metaSchema.newValidator(this, schemaPath, keyword, schemaNode, parentSchema);
+    }
+    
+    public JsonSchemaFactory getJsonSchemaFactory() {
+        return jsonSchemaFactory;
+    }
+}
diff --git a/src/main/java/com/networknt/schema/ValidationMessage.java b/src/main/java/com/networknt/schema/ValidationMessage.java
index b1d4044..fe218be 100644
--- a/src/main/java/com/networknt/schema/ValidationMessage.java
+++ b/src/main/java/com/networknt/schema/ValidationMessage.java
@@ -18,12 +18,14 @@ package com.networknt.schema;
 
 import java.text.MessageFormat;
 import java.util.Arrays;
+import java.util.Map;
 
 public class ValidationMessage {
     private String type;
     private String code;
     private String path;
     private String[] arguments;
+    private Map<String, Object> details;
     private String message;
 
     ValidationMessage() {
@@ -52,6 +54,14 @@ public class ValidationMessage {
     void setArguments(String[] arguments) {
         this.arguments = arguments;
     }
+    
+    void setDetails(Map<String, Object> details) {
+        this.details = details;
+    }
+    
+    public Map<String, Object> getDetails() {
+        return details;
+    }
 
     public String getMessage() {
         return message;
@@ -76,6 +86,7 @@ public class ValidationMessage {
         if (type != null ? !type.equals(that.type) : that.type != null) return false;
         if (code != null ? !code.equals(that.code) : that.code != null) return false;
         if (path != null ? !path.equals(that.path) : that.path != null) return false;
+        if (details != null ? !details.equals(that.details) : that.details != null) return false;
         if (!Arrays.equals(arguments, that.arguments)) return false;
         return !(message != null ? !message.equals(that.message) : that.message != null);
 
@@ -86,6 +97,7 @@ public class ValidationMessage {
         int result = type != null ? type.hashCode() : 0;
         result = 31 * result + (code != null ? code.hashCode() : 0);
         result = 31 * result + (path != null ? path.hashCode() : 0);
+        result = 31 * result + (details != null ? details.hashCode() : 0);
         result = 31 * result + (arguments != null ? Arrays.hashCode(arguments) : 0);
         result = 31 * result + (message != null ? message.hashCode() : 0);
         return result;
@@ -99,11 +111,26 @@ public class ValidationMessage {
         this.type = type;
     }
 
+    public static ValidationMessage of(String type, ErrorMessageType errorMessageType, String at, String... arguments) {
+        ValidationMessage.Builder builder = new ValidationMessage.Builder();
+        builder.code(errorMessageType.getErrorCode()).path(at).arguments(arguments)
+                    .format(errorMessageType.getMessageFormat()).type(type);
+        return builder.build();
+    }
+    
+    public static ValidationMessage of(String type, ErrorMessageType errorMessageType, String at, Map<String, Object> details) {
+        ValidationMessage.Builder builder = new ValidationMessage.Builder();
+        builder.code(errorMessageType.getErrorCode()).path(at).details(details)
+        .format(errorMessageType.getMessageFormat()).type(type);
+        return builder.build();
+    }
+
     public static class Builder {
         private String type;
         private String code;
         private String path;
         private String[] arguments;
+        private Map<String, Object> details;
         private MessageFormat format;
 
         public Builder type(String type) {
@@ -125,6 +152,11 @@ public class ValidationMessage {
             this.arguments = arguments;
             return this;
         }
+        
+        public Builder details(Map<String, Object> details) {
+            this.details = details;
+            return this;
+        }
 
         public Builder format(MessageFormat format) {
             this.format = format;
@@ -137,6 +169,7 @@ public class ValidationMessage {
             msg.setCode(code);
             msg.setPath(path);
             msg.setArguments(arguments);
+            msg.setDetails(details);
 
             if (format != null) {
                 String[] objs = new String[(arguments == null ? 0 : arguments.length) + 1];
diff --git a/src/main/java/com/networknt/schema/ValidatorTypeCode.java b/src/main/java/com/networknt/schema/ValidatorTypeCode.java
index 5241af6..d6a1121 100644
--- a/src/main/java/com/networknt/schema/ValidatorTypeCode.java
+++ b/src/main/java/com/networknt/schema/ValidatorTypeCode.java
@@ -16,11 +16,14 @@
 
 package com.networknt.schema;
 
+import java.lang.reflect.Constructor;
 import java.text.MessageFormat;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
 
-public enum ValidatorTypeCode {
+import com.fasterxml.jackson.databind.JsonNode;
+
+public enum ValidatorTypeCode implements Keyword, ErrorMessageType {
     ADDITIONAL_PROPERTIES("additionalProperties", "1001", new MessageFormat(
             "{0}.{1}: is not defined in the schema and the schema does not allow additional properties")),
     ALL_OF("allOf", "1002", new MessageFormat("{0}: should be valid to all the schemas {1}")),
@@ -29,7 +32,13 @@ public enum ValidatorTypeCode {
     DEPENDENCIES("dependencies", "1007", new MessageFormat("{0}: has an error with dependencies {1}")),
     EDITS("edits", "1005", new MessageFormat("{0}: has an error with 'edits'")),
     ENUM("enum", "1008", new MessageFormat("{0}: does not have a value in the enumeration {1}")),
-    FORMAT("format", "1009", new MessageFormat("{0}: does not match the {1} pattern {2}")),
+    FORMAT("format", "1009", new MessageFormat("{0}: does not match the {1} pattern {2}")){
+        @Override
+        public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext)
+                throws Exception {
+            throw new UnsupportedOperationException("Use FormatKeyword instead");
+        }
+    },
     ITEMS("items", "1010", new MessageFormat("{0}[{1}]: no validator found at this index")),
     MAXIMUM("maximum", "1011", new MessageFormat("{0}: must have a maximum value of {1}")),
     MAX_ITEMS("maxItems", "1012", new MessageFormat("{0}: there must be a maximum of {1} items in the array")),
@@ -47,20 +56,12 @@ public enum ValidatorTypeCode {
     PATTERN("pattern", "1023", new MessageFormat("{0}: does not match the regex pattern {1}")),
     PROPERTIES("properties", "1025", new MessageFormat("{0}: has an error with 'properties'")),
     READ_ONLY("readOnly", "1032", new MessageFormat("{0}: is a readonly field, it cannot be changed")),
-    REF("ref", "1026", new MessageFormat("{0}: has an error with 'refs'")),
+    REF("$ref", "1026", new MessageFormat("{0}: has an error with 'refs'")),
     REQUIRED("required", "1028", new MessageFormat("{0}.{1}: is missing but it is required")),
     TYPE("type", "1029", new MessageFormat("{0}: {1} found, {2} expected")),
     UNION_TYPE("unionType", "1030", new MessageFormat("{0}: {1} found, but {2} is required")),
     UNIQUE_ITEMS("uniqueItems", "1031", new MessageFormat("{0}: the items in the array must be unique"));
    
-	private static Map<String, ValidatorTypeCode> constants = new HashMap<String, ValidatorTypeCode>();
-
-    static {
-        for (ValidatorTypeCode c : values()) {
-            constants.put(c.value, c);
-        }
-    }
-
     private final String value;
     private final String errorCode;
     private final MessageFormat messageFormat;
@@ -73,15 +74,34 @@ public enum ValidatorTypeCode {
         this.errorCodeKey = value + "ErrorCode";
     }
 
-    public static ValidatorTypeCode fromValue(String value) {
-        ValidatorTypeCode constant = constants.get(value);
-        if (constant == null) {
-            throw new IllegalArgumentException(value);
-        } else {
-            return constant;
+    public static List<ValidatorTypeCode> getNonFormatKeywords() {
+        final List<ValidatorTypeCode> result = new ArrayList<ValidatorTypeCode>();
+        for (ValidatorTypeCode keyword: values()) {
+            if (!FORMAT.equals(keyword)) {
+                result.add(keyword);
+            }
         }
+        return result;
     }
+    
+    public JsonValidator newValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) throws Exception {
+            String shortClassName = getValue();
+            if (shortClassName.startsWith("$")) {
+                // remove "$" from class name for $ref schema
+                shortClassName = shortClassName.substring(1);
+            }
 
+            final String className = Character.toUpperCase(shortClassName.charAt(0)) + shortClassName.substring(1)
+                    + "Validator";
+            @SuppressWarnings("unchecked")
+            final Class<JsonValidator> clazz = (Class<JsonValidator>) Class
+                    .forName("com.networknt.schema." + className);
+            Constructor<JsonValidator> c = null;
+            c = clazz.getConstructor(
+                    new Class[] { String.class, JsonNode.class, JsonSchema.class, ValidationContext.class });
+            return c.newInstance(schemaPath + "/" + getValue(), schemaNode, parentSchema, validationContext);
+    }
+    
     @Override
     public String toString() {
         return this.value;
diff --git a/src/main/java/com/networknt/schema/url/StandardURLFetcher.java b/src/main/java/com/networknt/schema/url/StandardURLFetcher.java
new file mode 100644
index 0000000..3d99be4
--- /dev/null
+++ b/src/main/java/com/networknt/schema/url/StandardURLFetcher.java
@@ -0,0 +1,16 @@
+package com.networknt.schema.url;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Standard URL fetcher that uses {@link URL#openStream()} for fetching.
+ */
+public class StandardURLFetcher implements URLFetcher {
+
+    @Override
+    public InputStream fetch(URL url) throws IOException {
+        return url.openStream();
+    }
+}
diff --git a/src/main/java/com/networknt/schema/url/URLFetcher.java b/src/main/java/com/networknt/schema/url/URLFetcher.java
new file mode 100644
index 0000000..7c0308f
--- /dev/null
+++ b/src/main/java/com/networknt/schema/url/URLFetcher.java
@@ -0,0 +1,9 @@
+package com.networknt.schema.url;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+public interface URLFetcher {
+    InputStream fetch(URL url) throws IOException;
+}


Test patch:
diff --git a/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java b/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java
index 9bd0fa6..9ee0785 100644
--- a/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java
+++ b/src/test/java/com/networknt/schema/BaseJsonSchemaValidatorTest.java
@@ -48,27 +48,28 @@ public class BaseJsonSchemaValidatorTest {
     }
 
     protected JsonSchema getJsonSchemaFromClasspath(String name) throws Exception {
-        JsonSchemaFactory factory = new JsonSchemaFactory();
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
         InputStream is = Thread.currentThread().getContextClassLoader()
                 .getResourceAsStream(name);
         JsonSchema schema = factory.getSchema(is);
         return schema;
     }
 
+
     protected JsonSchema getJsonSchemaFromStringContent(String schemaContent) throws Exception {
-        JsonSchemaFactory factory = new JsonSchemaFactory();
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
         JsonSchema schema = factory.getSchema(schemaContent);
         return schema;
     }
 
     protected JsonSchema getJsonSchemaFromUrl(String url) throws Exception {
-        JsonSchemaFactory factory = new JsonSchemaFactory();
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
         JsonSchema schema = factory.getSchema(new URL(url));
         return schema;
     }
 
     protected JsonSchema getJsonSchemaFromJsonNode(JsonNode jsonNode) throws Exception {
-        JsonSchemaFactory factory = new JsonSchemaFactory();
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
         JsonSchema schema = factory.getSchema(jsonNode);
         return schema;
     }
diff --git a/src/test/java/com/networknt/schema/JsonSchemaTest.java b/src/test/java/com/networknt/schema/JsonSchemaTest.java
index 9c3b71e..33ba586 100644
--- a/src/test/java/com/networknt/schema/JsonSchemaTest.java
+++ b/src/test/java/com/networknt/schema/JsonSchemaTest.java
@@ -20,8 +20,6 @@ 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.HttpHandler;
-import io.undertow.server.handlers.resource.FileResource;
 import io.undertow.server.handlers.resource.FileResourceManager;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -38,6 +36,7 @@ import static io.undertow.Handlers.resource;
 
 public class JsonSchemaTest {
     protected ObjectMapper mapper = new ObjectMapper();
+    protected JsonSchemaFactory validatorFactory = JsonSchemaFactory.builder(JsonSchemaFactory.getInstance()).objectMapper(mapper).build();
     protected static Undertow server = null;
 
     public JsonSchemaTest() {
@@ -75,7 +74,7 @@ public class JsonSchemaTest {
         for (int j = 0; j < testCases.size(); j++) {
             try {
                 JsonNode testCase = testCases.get(j);
-                JsonSchema schema = new JsonSchema(mapper, testCase.get("schema"));
+                JsonSchema schema = validatorFactory.getSchema(testCase.get("schema"));
                 ArrayNode testNodes = (ArrayNode) testCase.get("tests");
                 for (int i = 0; i < testNodes.size(); i++) {
                     JsonNode test = testNodes.get(i);
@@ -110,7 +109,8 @@ public class JsonSchemaTest {
     public void testLoadingWithId() throws Exception {
         URL url = new URL("http://localhost:1234/self_ref/selfRef.json");
         JsonNode schemaJson = mapper.readTree(url);
-        JsonSchemaFactory factory = new JsonSchemaFactory();
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
+        @SuppressWarnings("unused")
         JsonSchema schema = factory.getSchema(schemaJson);
     }
 
diff --git a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
index 43e716f..4c6d6c7 100644
--- a/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
+++ b/src/test/java/com/networknt/schema/PatternPropertiesValidatorTest.java
@@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode;
 import org.junit.Assert;
 import org.junit.Test;
 
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -30,7 +29,7 @@ public class PatternPropertiesValidatorTest extends BaseJsonSchemaValidatorTest
 
     @Test(expected=JsonSchemaException.class)
     public void testInvalidPatternPropertiesValidator() throws Exception {
-        JsonSchemaFactory factory = new JsonSchemaFactory();
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance();
         JsonSchema schema = factory.getSchema("{\"patternProperties\":6}");
 
         JsonNode node = getJsonNodeFromStringContent("");