This is an automated email from the ASF dual-hosted git repository.

slawrence pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-daffodil.git


The following commit(s) were added to refs/heads/master by this push:
     new 81792fa  Custom Infoset Validator API with SPI support
81792fa is described below

commit 81792fa3a45b1b5f3cd92a4b3a77ee7e569646e1
Author: John Wass <[email protected]>
AuthorDate: Thu Nov 5 09:09:48 2020 -0500

    Custom Infoset Validator API with SPI support
    
    Abstracted Validator implementation to support alternative validation 
backends.  The original Xerces validation was refactored to become the 
XercesValidator and implements this new API.
    
    Support for loading implementations via SPI was added and is used to 
support the Daffodil CLI. The CLI is refactored to extend the --validate flag 
arguments to accept names used for SPI lookup.  The CLI arguments refactoring 
keeps backwards comapibility with previous argument handling.
    
    DAFFODIL-2409
---
 .../src/main/scala/org/apache/daffodil/Main.scala  |  24 +++-
 .../apache/daffodil/TestValidatorPatterns.scala    |  50 ++++++++
 .../scala/org/apache/daffodil/japi/Daffodil.scala  |   8 ++
 .../daffodil/japi/packageprivate/Utils.scala       |   9 --
 .../daffodil/example/ValidatorApiExample.java      |  74 +++++++++++
 .../daffodil/example/ValidatorSpiExample.java      |  82 ++++++++++++
 .../example/validation/FailingValidator.java       |  22 ++--
 .../validation/FailingValidatorFactory.java        |  23 ++--
 .../example/validation/PassingValidator.java       |  22 ++--
 .../validation/PassingValidatorFactory.java        |  23 ++--
 .../org.apache.daffodil.api.ValidatorFactory       |  17 +++
 .../org.apache.daffodil.api.ValidatorFactory       |  16 +++
 .../org/apache/daffodil/api/ValidationMode.scala   |   2 +
 .../scala/org/apache/daffodil/api/Validator.scala  |  99 +++++++++++++++
 .../scala/org/apache/daffodil/util/Validator.scala | 102 ---------------
 .../apache/daffodil/validation/Validators.scala    |  69 ++++++++++
 .../daffodil/validation/XercesValidator.scala      | 141 +++++++++++++++++++++
 daffodil-lib/src/test/resources/.keep              |   0
 .../org.apache.daffodil.api.ValidatorFactory       |  17 +++
 .../resources/test/validation/testData1Infoset.xml |  23 ++++
 .../resources/test/validation/testSchema1.dfdl.xsd |  46 +++++++
 .../daffodil/validation/TestValidatorsSPI.scala    |  70 ++++++++++
 .../daffodil/validation/TestXercesValidator.scala} |  22 ++--
 .../daffodil/validation/ValidatorSPISupport.scala  |  49 +++++++
 .../apache/daffodil/processors/DataProcessor.scala |  82 ++++++------
 .../apache/daffodil/processors/RuntimeData.scala   |   2 +-
 .../scala/org/apache/daffodil/sapi/Daffodil.scala  |   7 +
 .../daffodil/sapi/packageprivate/Utils.scala       |   2 +
 .../org.apache.daffodil.api.ValidatorFactory       |  17 +++
 .../daffodil/example/ValidatorApiExample.scala     |  29 +++--
 .../example/ValidatorExamplesSupport.scala         |  95 ++++++++++++++
 .../daffodil/example/ValidatorSpiExample.scala     |  48 +++++++
 project/Dependencies.scala                         |   1 +
 33 files changed, 1085 insertions(+), 208 deletions(-)

diff --git a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala 
b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
index fb582d6..5b6f18e 100644
--- a/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
+++ b/daffodil-cli/src/main/scala/org/apache/daffodil/Main.scala
@@ -29,6 +29,8 @@ import java.nio.file.Paths
 import java.util.Scanner
 import java.util.concurrent.Executors
 
+import com.typesafe.config.ConfigFactory
+
 import scala.concurrent.Await
 import scala.concurrent.ExecutionContext
 import scala.concurrent.Future
@@ -98,6 +100,7 @@ import org.apache.daffodil.util.Logging
 import org.apache.daffodil.util.LoggingDefaults
 import org.apache.daffodil.util.Misc
 import org.apache.daffodil.util.Timer
+import org.apache.daffodil.validation.Validators
 import org.apache.daffodil.xml.QName
 import org.apache.daffodil.xml.RefQName
 import org.apache.daffodil.xml.DaffodilXMLLoader
@@ -107,6 +110,8 @@ import org.rogach.scallop.ArgType
 import org.rogach.scallop.ScallopOption
 import org.rogach.scallop.ValueConverter
 
+import scala.util.matching.Regex
+
 class CommandLineSAXErrorHandler() extends org.xml.sax.ErrorHandler with 
Logging {
 
   def warning(exception: SAXParseException) = {
@@ -218,11 +223,17 @@ class CLIConf(arguments: Array[String]) extends 
scallop.ScallopConf(arguments)
   }
 
   implicit def validateConverter = singleArgConverter[ValidationMode.Type]((s: 
String) => {
+    import ValidatorPatterns._
     s.toLowerCase match {
       case "on" => ValidationMode.Full
       case "limited" => ValidationMode.Limited
       case "off" => ValidationMode.Off
-      case _ => throw new Exception("Unrecognized ValidationMode %s.  Must be 
'on', 'limited' or 'off'.".format(s))
+      case DefaultArgPattern(name, arg) if Validators.isRegistered(name) =>
+        val config = if(arg.endsWith(".conf")) ConfigFactory.load(arg) else 
ConfigFactory.parseString(s"$name=$arg")
+        ValidationMode.Custom(Validators.get(name).make(config))
+      case NoArgsPattern(name) if Validators.isRegistered(name) =>
+        ValidationMode.Custom(Validators.get(name).make(ConfigFactory.empty))
+      case _ => throw new Exception("Unrecognized ValidationMode %s.  Must be 
'on', 'limited', 'off', or name of spi validator.".format(s))
     }
   })
 
@@ -326,7 +337,7 @@ class CLIConf(arguments: Array[String]) extends 
scallop.ScallopConf(arguments)
     val path = opt[String](argName = "path", descr = "path to the node to 
create parser.")
     val parser = opt[File](short = 'P', argName = "file", descr = "use a 
previously saved parser.")
     val output = opt[String](argName = "file", descr = "write output to a 
given file. If not given or is -, output is written to stdout.")
-    val validate: ScallopOption[ValidationMode.Type] = 
opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), 
argName = "mode", descr = "the validation mode. 'on', 'limited' or 'off'.")
+    val validate: ScallopOption[ValidationMode.Type] = 
opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), 
argName = "mode", descr = "the validation mode. 'on', 'limited', 'off', or spi 
name.")
     val vars = props[String]('D', keyName = "variable", valueName = "value", 
descr = "variables to be used when parsing. An optional namespace may be 
provided.")
     val tunables = props[String]('T', keyName = "tunable", valueName = 
"value", descr = "daffodil tunable to be used when parsing.")
     val config = opt[String](short = 'c', argName = "file", descr = "path to 
file containing configuration items.")
@@ -391,7 +402,7 @@ class CLIConf(arguments: Array[String]) extends 
scallop.ScallopConf(arguments)
     val threads = opt[Int](short = 't', argName = "threads", default = 
Some(1), descr = "The number of threads to use.")
     val path = opt[String](argName = "path", descr = "path to the node to 
create parser.")
     val parser = opt[File](short = 'P', argName = "file", descr = "use a 
previously saved parser.")
-    val validate: ScallopOption[ValidationMode.Type] = 
opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), 
argName = "mode", descr = "the validation mode. 'on', 'limited' or 'off'.")
+    val validate: ScallopOption[ValidationMode.Type] = 
opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), 
argName = "mode", descr = "the validation mode. 'on', 'limited', 'off', or spi 
name.")
     val vars = props[String]('D', keyName = "variable", valueName = "value", 
descr = "variables to be used when processing. An optional namespace may be 
provided.")
     val tunables = props[String]('T', keyName = "tunable", valueName = 
"value", descr = "daffodil tunable to be used when processing.")
     val config = opt[String](short = 'c', argName = "file", descr = "path to 
file containing configuration items.")
@@ -441,7 +452,7 @@ class CLIConf(arguments: Array[String]) extends 
scallop.ScallopConf(arguments)
     val path = opt[String](argName = "path", descr = "path to the node to 
create parser.")
     val parser = opt[File](short = 'P', argName = "file", descr = "use a 
previously saved parser.")
     val output = opt[String](argName = "file", descr = "write output to file. 
If not given or is -, output is written to standard output.")
-    val validate: ScallopOption[ValidationMode.Type] = 
opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), 
argName = "mode", descr = "the validation mode. 'on', 'limited' or 'off'.")
+    val validate: ScallopOption[ValidationMode.Type] = 
opt[ValidationMode.Type](short = 'V', default = Some(ValidationMode.Off), 
argName = "mode", descr = "the validation mode. 'on', 'limited', 'off', or spi 
name.")
     val vars = props[String]('D', keyName = "variable", valueName = "value", 
descr = "variables to be used when unparsing. An optional namespace may be 
provided.")
     val tunables = props[String]('T', keyName = "tunable", valueName = 
"value", descr = "daffodil tunable to be used when parsing.")
     val config = opt[String](short = 'c', argName = "file", descr = "path to 
file containing configuration items.")
@@ -549,6 +560,11 @@ class CLIConf(arguments: Array[String]) extends 
scallop.ScallopConf(arguments)
   verify()
 }
 
+object ValidatorPatterns {
+  val NoArgsPattern: Regex = "(.+?)".r.anchored
+  val DefaultArgPattern: Regex = "(.+?)=(.+)".r.anchored
+}
+
 object Main extends Logging {
 
   val traceCommands = Seq(
diff --git 
a/daffodil-cli/src/test/scala/org/apache/daffodil/TestValidatorPatterns.scala 
b/daffodil-cli/src/test/scala/org/apache/daffodil/TestValidatorPatterns.scala
new file mode 100644
index 0000000..e4d202f
--- /dev/null
+++ 
b/daffodil-cli/src/test/scala/org/apache/daffodil/TestValidatorPatterns.scala
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil
+
+import org.junit.Test
+import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
+
+class TestValidatorPatterns {
+  @Test def testNoArgsPattern(): Unit = {
+    val vname = "vvv"
+    s"$vname" match {
+      case ValidatorPatterns.DefaultArgPattern(_, _) =>
+        fail("should not have matched")
+      case ValidatorPatterns.NoArgsPattern(v) =>
+        assertEquals(vname, v)
+      case _ =>
+        fail("did not match")
+    }
+  }
+
+  @Test def testDefaultArgsPattern(): Unit = {
+    val vname = "vvv"
+    val varg = "some_default_argument_string"
+    s"$vname=$varg" match {
+      case ValidatorPatterns.DefaultArgPattern(v, arg) =>
+        assertEquals(vname, v)
+        assertEquals(varg, arg)
+      case ValidatorPatterns.NoArgsPattern(_) =>
+        fail("should not have matched")
+      case _ =>
+        fail("did not match")
+    }
+  }
+}
diff --git 
a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala 
b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala
index ae20614..a57f4e6 100644
--- a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala
+++ b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/Daffodil.scala
@@ -57,6 +57,7 @@ import org.apache.daffodil.processors.{ InvalidUsageException 
=> SInvalidUsageEx
 import java.net.URI
 
 import org.apache.daffodil.api.URISchemaSource
+import org.apache.daffodil.api.Validator
 import org.apache.daffodil.util.Maybe
 import org.apache.daffodil.util.Maybe._
 import org.apache.daffodil.util.MaybeULong
@@ -603,6 +604,13 @@ class DataProcessor private[japi] (private var dp: 
SDataProcessor)
   }
 
   /**
+   * Obtain a new [[DataProcessor]] having a specific validator
+   * @param validator validator instance
+   */
+  def withValidator(validator: Validator): DataProcessor =
+    copy(dp = 
dp.withValidationMode(org.apache.daffodil.api.ValidationMode.Custom(validator)))
+
+  /**
    * Read external variables from a Daffodil configuration file
    *
    * @see <a target="_blank" 
href='https://daffodil.apache.org/configuration/'>Daffodil Configuration 
File</a> - Daffodil configuration file format
diff --git 
a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/packageprivate/Utils.scala
 
b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/packageprivate/Utils.scala
index 5090396..4833e4d 100644
--- 
a/daffodil-japi/src/main/scala/org/apache/daffodil/japi/packageprivate/Utils.scala
+++ 
b/daffodil-japi/src/main/scala/org/apache/daffodil/japi/packageprivate/Utils.scala
@@ -76,15 +76,6 @@ private[japi] object ValidationConversions {
     }
     smode
   }
-
-  def modeFromScala(smode: SValidationMode.Type): ValidationMode = {
-    val mode: ValidationMode = smode match {
-      case SValidationMode.Off => ValidationMode.Off
-      case SValidationMode.Limited => ValidationMode.Limited
-      case SValidationMode.Full => ValidationMode.Full
-    }
-    mode
-  }
 }
 
 /* A wrapper log writer that scala logging can talk to, which is then forwarded
diff --git 
a/daffodil-japi/src/test/java/org/apache/daffodil/example/ValidatorApiExample.java
 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/ValidatorApiExample.java
new file mode 100644
index 0000000..2564c05
--- /dev/null
+++ 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/ValidatorApiExample.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.example;
+
+import org.apache.daffodil.example.validation.FailingValidator;
+import org.apache.daffodil.example.validation.PassingValidator;
+import org.apache.daffodil.japi.Daffodil;
+import org.apache.daffodil.japi.DataProcessor;
+import org.apache.daffodil.japi.ParseResult;
+import org.apache.daffodil.japi.ProcessorFactory;
+import org.apache.daffodil.japi.infoset.JDOMInfosetOutputter;
+import org.apache.daffodil.japi.io.InputSourceDataInputStream;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+public class ValidatorApiExample {
+    private java.io.File getResource(String resPath) {
+        try {
+            return new 
java.io.File(this.getClass().getResource(resPath).toURI());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Test
+    public void testPassing() throws IOException, ClassNotFoundException {
+        org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+        java.io.File schemaFile = getResource("/test/japi/mySchema5.dfdl.xsd");
+        ProcessorFactory pf = c.compileFile(schemaFile);
+        DataProcessor dp = pf.onPath("/").withValidator(new 
PassingValidator());
+
+        java.io.File file = getResource("/test/japi/myData5.dat");
+        java.io.FileInputStream fis = new java.io.FileInputStream(file);
+        InputSourceDataInputStream dis = new InputSourceDataInputStream(fis);
+        JDOMInfosetOutputter outputter = new JDOMInfosetOutputter();
+        ParseResult res = dp.parse(dis, outputter);
+
+        assertFalse(res.isValidationError());
+    }
+
+    @Test
+    public void testFailing() throws IOException, ClassNotFoundException {
+        org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+        java.io.File schemaFile = getResource("/test/japi/mySchema1.dfdl.xsd");
+        ProcessorFactory pf = c.compileFile(schemaFile);
+        DataProcessor dp = pf.onPath("/").withValidator(new 
FailingValidator());
+
+        java.io.File file = getResource("/test/japi/myData.dat");
+        java.io.FileInputStream fis = new java.io.FileInputStream(file);
+        InputSourceDataInputStream dis = new InputSourceDataInputStream(fis);
+        JDOMInfosetOutputter outputter = new JDOMInfosetOutputter();
+        ParseResult res = dp.parse(dis, outputter);
+
+        assertTrue(res.isValidationError());
+    }
+}
diff --git 
a/daffodil-japi/src/test/java/org/apache/daffodil/example/ValidatorSpiExample.java
 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/ValidatorSpiExample.java
new file mode 100644
index 0000000..83838a4
--- /dev/null
+++ 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/ValidatorSpiExample.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.example;
+
+import com.typesafe.config.ConfigFactory;
+import org.apache.daffodil.api.ValidatorFactory;
+import org.apache.daffodil.example.validation.FailingValidator;
+import org.apache.daffodil.example.validation.PassingValidator;
+import org.apache.daffodil.japi.Daffodil;
+import org.apache.daffodil.japi.DataProcessor;
+import org.apache.daffodil.japi.ParseResult;
+import org.apache.daffodil.japi.ProcessorFactory;
+import org.apache.daffodil.japi.infoset.JDOMInfosetOutputter;
+import org.apache.daffodil.japi.io.InputSourceDataInputStream;
+import org.apache.daffodil.validation.Validators;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class ValidatorSpiExample {
+    private java.io.File getResource(String resPath) {
+        try {
+            return new 
java.io.File(this.getClass().getResource(resPath).toURI());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Test
+    public void testPassing() throws Exception {
+        org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+        java.io.File schemaFile = getResource("/test/japi/mySchema5.dfdl.xsd");
+        ProcessorFactory pf = c.compileFile(schemaFile);
+
+        assertTrue(Validators.isRegistered(PassingValidator.name));
+        ValidatorFactory vf = Validators.get(PassingValidator.name);
+        DataProcessor dp = 
pf.onPath("/").withValidator(vf.make(ConfigFactory.empty()));
+
+        java.io.File file = getResource("/test/japi/myData5.dat");
+        java.io.FileInputStream fis = new java.io.FileInputStream(file);
+        InputSourceDataInputStream dis = new InputSourceDataInputStream(fis);
+        JDOMInfosetOutputter outputter = new JDOMInfosetOutputter();
+        ParseResult res = dp.parse(dis, outputter);
+
+        assertFalse(res.isValidationError());
+    }
+
+    @Test
+    public void testFailing() throws Exception {
+        org.apache.daffodil.japi.Compiler c = Daffodil.compiler();
+        java.io.File schemaFile = getResource("/test/japi/mySchema1.dfdl.xsd");
+        ProcessorFactory pf = c.compileFile(schemaFile);
+
+        assertTrue(Validators.isRegistered(FailingValidator.name));
+        ValidatorFactory vf = Validators.get(FailingValidator.name);
+        DataProcessor dp = 
pf.onPath("/").withValidator(vf.make(ConfigFactory.empty()));
+
+        java.io.File file = getResource("/test/japi/myData.dat");
+        java.io.FileInputStream fis = new java.io.FileInputStream(file);
+        InputSourceDataInputStream dis = new InputSourceDataInputStream(fis);
+        JDOMInfosetOutputter outputter = new JDOMInfosetOutputter();
+        ParseResult res = dp.parse(dis, outputter);
+
+        assertTrue(res.isValidationError());
+    }
+}
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/FailingValidator.java
similarity index 61%
copy from 
daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
copy to 
daffodil-japi/src/test/java/org/apache/daffodil/example/validation/FailingValidator.java
index 3dc7a65..335c40d 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
+++ 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/FailingValidator.java
@@ -15,15 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.daffodil.api
+package org.apache.daffodil.example.validation;
 
-import org.apache.daffodil.util.Enum
+import org.apache.daffodil.api.ValidationResult;
+import org.apache.daffodil.api.Validator;
 
-object ValidationMode extends Enum {
-  sealed abstract class Type protected (val mode: Int) extends EnumValueType 
with Ordered[Type] with Serializable {
-    def compare(that: ValidationMode.Type) = this.mode - that.mode
-  }
-  case object Off extends Type(10)
-  case object Limited extends Type(20)
-  case object Full extends Type(30)
+import java.io.InputStream;
+import java.util.Collections;
+
+public class FailingValidator implements Validator {
+    public static final String name = "failing-japi-validator";
+
+    @Override
+    public ValidationResult validateXML(InputStream document) {
+        return new ValidationResult(Collections.emptyList(), 
Collections.singletonList(() -> "boom"));
+    }
 }
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/FailingValidatorFactory.java
similarity index 65%
copy from 
daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
copy to 
daffodil-japi/src/test/java/org/apache/daffodil/example/validation/FailingValidatorFactory.java
index 3dc7a65..41da8b6 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
+++ 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/FailingValidatorFactory.java
@@ -15,15 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.daffodil.api
+package org.apache.daffodil.example.validation;
 
-import org.apache.daffodil.util.Enum
+import com.typesafe.config.Config;
+import org.apache.daffodil.api.Validator;
+import org.apache.daffodil.api.ValidatorFactory;
 
-object ValidationMode extends Enum {
-  sealed abstract class Type protected (val mode: Int) extends EnumValueType 
with Ordered[Type] with Serializable {
-    def compare(that: ValidationMode.Type) = this.mode - that.mode
-  }
-  case object Off extends Type(10)
-  case object Limited extends Type(20)
-  case object Full extends Type(30)
+public class FailingValidatorFactory implements ValidatorFactory {
+    @Override
+    public String name() {
+        return FailingValidator.name;
+    }
+
+    @Override
+    public Validator make(Config config) {
+        return new FailingValidator();
+    }
 }
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/PassingValidator.java
similarity index 62%
copy from 
daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
copy to 
daffodil-japi/src/test/java/org/apache/daffodil/example/validation/PassingValidator.java
index 3dc7a65..a6fe260 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
+++ 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/PassingValidator.java
@@ -15,15 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.daffodil.api
+package org.apache.daffodil.example.validation;
 
-import org.apache.daffodil.util.Enum
+import org.apache.daffodil.api.ValidationResult;
+import org.apache.daffodil.api.Validator;
 
-object ValidationMode extends Enum {
-  sealed abstract class Type protected (val mode: Int) extends EnumValueType 
with Ordered[Type] with Serializable {
-    def compare(that: ValidationMode.Type) = this.mode - that.mode
-  }
-  case object Off extends Type(10)
-  case object Limited extends Type(20)
-  case object Full extends Type(30)
+import java.io.InputStream;
+import java.util.Collections;
+
+public class PassingValidator implements Validator {
+    public static final String name = "passing-japi-validator";
+
+    @Override
+    public ValidationResult validateXML(InputStream document) {
+        return new ValidationResult(Collections.emptyList(), 
Collections.emptyList());
+    }
 }
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/PassingValidatorFactory.java
similarity index 65%
copy from 
daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
copy to 
daffodil-japi/src/test/java/org/apache/daffodil/example/validation/PassingValidatorFactory.java
index 3dc7a65..848345b 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
+++ 
b/daffodil-japi/src/test/java/org/apache/daffodil/example/validation/PassingValidatorFactory.java
@@ -15,15 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.daffodil.api
+package org.apache.daffodil.example.validation;
 
-import org.apache.daffodil.util.Enum
+import com.typesafe.config.Config;
+import org.apache.daffodil.api.Validator;
+import org.apache.daffodil.api.ValidatorFactory;
 
-object ValidationMode extends Enum {
-  sealed abstract class Type protected (val mode: Int) extends EnumValueType 
with Ordered[Type] with Serializable {
-    def compare(that: ValidationMode.Type) = this.mode - that.mode
-  }
-  case object Off extends Type(10)
-  case object Limited extends Type(20)
-  case object Full extends Type(30)
+public class PassingValidatorFactory implements ValidatorFactory {
+    @Override
+    public String name() {
+        return PassingValidator.name;
+    }
+
+    @Override
+    public Validator make(Config config) {
+        return new PassingValidator();
+    }
 }
diff --git 
a/daffodil-japi/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
 
b/daffodil-japi/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
new file mode 100644
index 0000000..df7a270
--- /dev/null
+++ 
b/daffodil-japi/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You 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.
+#
+org.apache.daffodil.example.validation.PassingValidatorFactory
+org.apache.daffodil.example.validation.FailingValidatorFactory
diff --git 
a/daffodil-lib/src/main/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
 
b/daffodil-lib/src/main/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
new file mode 100644
index 0000000..ac7b5d7
--- /dev/null
+++ 
b/daffodil-lib/src/main/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
@@ -0,0 +1,16 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You 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.
+#
+org.apache.daffodil.validation.XercesValidatorFactory
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala 
b/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
index 3dc7a65..d982a42 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
@@ -26,4 +26,6 @@ object ValidationMode extends Enum {
   case object Off extends Type(10)
   case object Limited extends Type(20)
   case object Full extends Type(30)
+
+  case class Custom(v: Validator) extends Type( 100)
 }
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/Validator.scala 
b/daffodil-lib/src/main/scala/org/apache/daffodil/api/Validator.scala
new file mode 100644
index 0000000..1a1c385
--- /dev/null
+++ b/daffodil-lib/src/main/scala/org/apache/daffodil/api/Validator.scala
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.api
+
+import com.typesafe.config.Config
+
+/**
+ * Implement this trait to provide custom validation logic
+ *
+ * The Validator implementations are expected to be thread safe
+ *
+ * @see [[org.apache.daffodil.validation.XercesValidator]] for example of 
using ThreadLocal for thread safety
+ */
+trait Validator {
+  def validateXML(document: java.io.InputStream): ValidationResult
+}
+
+/**
+ * Implement this trait and register with SPI to provide runtime discovery of 
custom Validator implementations
+ *
+ * The factory implementations are expected to be thread safe
+ */
+trait ValidatorFactory {
+  /**
+   * Unique name of this Validator service
+   * @return registered name of the validator factory
+   */
+  def name(): String
+
+  /**
+   * The factory method to generate the Validator instance
+   * @param config com.typesafe.config.Config to pass to validator instance
+   * @return [[org.apache.daffodil.api.Validator]] instance ready to execute
+   * @throws org.apache.daffodil.api.ValidatorInitializationException when 
initialization fails
+   */
+  @throws(classOf[ValidatorInitializationException])
+  def make(config: Config): Validator
+}
+
+/**
+ * Results of a validation execution
+ * @param warnings [[org.apache.daffodil.api.ValidationWarning]] objects
+ * @param errors [[org.apache.daffodil.api.ValidationFailure]] objects
+ */
+final case class ValidationResult(warnings: 
java.util.Collection[ValidationWarning], errors: 
java.util.Collection[ValidationFailure])
+
+object ValidationResult {
+  /**
+   * an empty [[org.apache.daffodil.api.ValidationResult]]
+   */
+  val empty: ValidationResult = ValidationResult(Seq.empty, Seq.empty)
+
+  def apply(warnings: Seq[ValidationWarning], errors: Seq[ValidationFailure]): 
ValidationResult = {
+    import scala.collection.JavaConverters.asJavaCollectionConverter
+    ValidationResult(warnings.asJavaCollection, errors.asJavaCollection)
+  }
+}
+
+/**
+ * Represents a non-fatal validation notification
+ */
+trait ValidationWarning {
+  def getMessage: String
+}
+
+/**
+ * Represents a fatal validation notification
+ */
+trait ValidationFailure {
+  def getMessage: String
+}
+
+/**
+ * Represents a fatal validation notification backed by a Throwable
+ */
+trait ValidationException {
+  def getCause: Throwable
+}
+
+/**
+ * Thrown when a validator fails to initialize
+ * @param message used in the underlying Exception
+ */
+final case class ValidatorInitializationException(message: String) extends 
Exception(message)
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Validator.scala 
b/daffodil-lib/src/main/scala/org/apache/daffodil/util/Validator.scala
deleted file mode 100644
index 1e5684a..0000000
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/util/Validator.scala
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.daffodil.util
-
-import java.net.URI
-
-import scala.collection.mutable
-import scala.xml.parsing.NoBindingFactoryAdapter
-
-import javax.xml.XMLConstants
-import javax.xml.transform.stream.StreamSource
-import org.apache.daffodil.xml.DFDLCatalogResolver
-import org.xml.sax.ErrorHandler
-
-/**
- * Use this for extra validation passes in the TDML Runner
- * to do a validation pass on the TDML expected Infoset w.r.t. the model and to
- * do a validation pass on the actual result w.r.t. the model as an XML 
document.
- */
-object Validator extends NoBindingFactoryAdapter {
-
-  private type CacheType = mutable.HashMap[Seq[String], 
javax.xml.validation.Validator]
-
-  private val validationSchemaCache =
-    new ThreadLocal[CacheType] {
-      override def initialValue =
-        new CacheType
-    }
-
-  def validateXMLSources(schemaFileNames: Seq[String], document: 
java.io.InputStream, errHandler: ErrorHandler): Unit = {
-    val cache = validationSchemaCache.get()
-    val validator = {
-      val optCachedValidator = cache.get(schemaFileNames)
-      optCachedValidator match {
-        case Some(cachedValidator) => {
-          cachedValidator.reset()
-          // reset takes it back to the original state at the point we call
-          // newValidator. So we need to re-set the features, resolvers,
-          // and handlers
-          val resolver = DFDLCatalogResolver.get
-          val validator : javax.xml.validation.Validator =
-            initializeValidator(cachedValidator, errHandler, resolver)
-          validator
-        }
-        case None => {
-          val schemaSources: Seq[javax.xml.transform.Source] = 
schemaFileNames.map { fn =>
-            {
-              val uri = new URI(fn)
-              val is = uri.toURL.openStream()
-              val stream = new StreamSource(is)
-              stream.setSystemId(uri.toString) // must set this so that 
relative URIs will be created for import/include files.
-              stream
-            }
-          }
-
-          val factory = new 
org.apache.xerces.jaxp.validation.XMLSchemaFactory()
-          factory.setErrorHandler(errHandler)
-          val resolver = DFDLCatalogResolver.get
-          factory.setResourceResolver(resolver)
-          val schema = factory.newSchema(schemaSources.toArray)
-          val validator = initializeValidator(schema.newValidator(), 
errHandler, resolver)
-          cache.put(schemaFileNames, validator)
-          validator
-        }
-      }
-    }
-    val documentSource = new StreamSource(document)
-    validator.validate(documentSource)
-  }
-
-  def initializeValidator(validator: javax.xml.validation.Validator, 
errHandler: ErrorHandler, resolver: DFDLCatalogResolver):
-    javax.xml.validation.Validator = {
-    validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
-    //
-    validator.setFeature("http://xml.org/sax/features/validation";, true)
-
-    // If you enable the feature below, it seems to do no validation at all. 
Just passes.
-    //          
validator.setFeature("http://apache.org/xml/features/validation/dynamic";, true)
-
-    validator.setFeature("http://apache.org/xml/features/validation/schema";, 
true)
-    
validator.setFeature("http://apache.org/xml/features/validation/schema-full-checking";,
 true)
-    validator.setErrorHandler(errHandler)
-    validator.setResourceResolver(resolver)
-    validator
-  }
-}
-
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/validation/Validators.scala 
b/daffodil-lib/src/main/scala/org/apache/daffodil/validation/Validators.scala
new file mode 100644
index 0000000..dc9c56a
--- /dev/null
+++ 
b/daffodil-lib/src/main/scala/org/apache/daffodil/validation/Validators.scala
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.validation
+
+import java.util.ServiceLoader
+
+import org.apache.daffodil.api.ValidatorFactory
+
+import scala.collection.JavaConverters._
+
+/**
+ * Access SPI registered [[org.apache.daffodil.api.ValidatorFactory]] 
instances.
+ *
+ * Registered instances provide a unique name for lookup.
+ */
+object Validators {
+  private lazy val impls: Map[String, ValidatorFactory] =
+    ServiceLoader
+      .load(classOf[ValidatorFactory])
+      .iterator()
+      .asScala
+      .map(v => v.name() -> v)
+      .toMap
+
+  /**
+   * Get the factory by name or throw
+   * @param name registered name of the validator factory
+   * @throws ValidatorNotRegisteredException when factory is not found in the 
registered services
+   * @return [[org.apache.daffodil.api.ValidatorFactory]] the factory instance
+   */
+  @throws(classOf[ValidatorNotRegisteredException])
+  def get(name: String): ValidatorFactory = impls.getOrElse(name, throw 
ValidatorNotRegisteredException(name))
+
+  /**
+   * Optionally find the factory
+   * @param name registered name of the validator factory
+   * @return [[org.apache.daffodil.api.ValidatorFactory]] optional factory 
instance
+   */
+  def find(name: String): Option[ValidatorFactory] = impls.get(name)
+
+  /**
+   * Check for registration of named factory
+   * @param name registered name of the validator factory
+   * @return is factory registered
+   */
+  def isRegistered(name: String): Boolean = impls.contains(name)
+}
+
+/**
+ * Thrown when the by-name lookup of a validator fails
+ * @param name the requested validator factory name
+ */
+case class ValidatorNotRegisteredException(name: String)
+  extends Exception(s"No ValidatorFactory is registered as $name")
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/validation/XercesValidator.scala
 
b/daffodil-lib/src/main/scala/org/apache/daffodil/validation/XercesValidator.scala
new file mode 100644
index 0000000..a88084b
--- /dev/null
+++ 
b/daffodil-lib/src/main/scala/org/apache/daffodil/validation/XercesValidator.scala
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.validation
+
+import java.net.URI
+
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigFactory
+import com.typesafe.config.ConfigValueFactory
+import javax.xml.XMLConstants
+import javax.xml.transform.stream.StreamSource
+import org.apache.daffodil.api.ValidationException
+import org.apache.daffodil.api.ValidationFailure
+import org.apache.daffodil.api.ValidationResult
+import org.apache.daffodil.api.ValidationWarning
+import org.apache.daffodil.api.Validator
+import org.apache.daffodil.api.ValidatorFactory
+import org.apache.daffodil.validation.XercesValidator.XercesValidatorImpl
+import org.apache.daffodil.xml.DFDLCatalogResolver
+import org.xml.sax.ErrorHandler
+import org.xml.sax.SAXParseException
+
+import scala.collection.JavaConverters._
+import scala.xml.SAXException
+
+/**
+ * Provides a XercesValidator instance
+ *
+ * SPI service name: xerces
+ * Configuration requirements:
+ *   - xerces: List[String] -  schema file names
+ */
+class XercesValidatorFactory extends ValidatorFactory {
+  def name(): String = XercesValidator.name
+  def make(config: Config): Validator = 
XercesValidatorFactory.makeValidator(config)
+}
+
+object XercesValidatorFactory {
+  def makeValidator(config: Config): Validator = {
+    val schemaFiles =
+      if(config.hasPath(XercesValidator.name))
+        config.getStringList(XercesValidator.name).asScala
+      else Seq.empty
+    new XercesValidator(schemaFiles)
+  }
+
+  def makeConfig(uris: Seq[String]): Config = {
+    val v = ConfigValueFactory.fromIterable(uris.asJava)
+    ConfigFactory.parseMap(Map(XercesValidator.name -> v).asJava)
+  }
+}
+
+/**
+ * Use this for extra validation passes in the TDML Runner
+ * to do a validation pass on the TDML expected Infoset w.r.t. the model and to
+ * do a validation pass on the actual result w.r.t. the model as an XML 
document.
+ */
+class XercesValidator(schemaFileNames: Seq[String])
+  extends Validator {
+
+  private val schemaSources: Seq[javax.xml.transform.Source] = 
schemaFileNames.map { fn =>
+    val uri = new URI(fn)
+    val is = uri.toURL.openStream()
+    val stream = new StreamSource(is)
+    stream.setSystemId(uri.toString) // must set this so that relative URIs 
will be created for import/include files.
+    stream
+  }
+
+  private val factory = new 
org.apache.xerces.jaxp.validation.XMLSchemaFactory()
+  private val resolver = DFDLCatalogResolver.get
+  factory.setResourceResolver(resolver)
+
+  private val schema = factory.newSchema(schemaSources.toArray)
+
+  private val validator = new ThreadLocal[XercesValidatorImpl] {
+    override def initialValue(): XercesValidatorImpl =
+      initializeValidator(schema.newValidator(), resolver)
+  }
+
+  def validateXML(document: java.io.InputStream): ValidationResult = {
+    val documentSource = new StreamSource(document)
+
+    // get the validator instance for this thread
+    val xv = validator.get()
+
+    // create a new error handler for this execution
+    val eh = new XercesErrorHandler
+    xv.setErrorHandler(eh)
+
+    // validate the document
+    xv.validate(documentSource)
+
+    // error handler contents as daffodil result
+    ValidationResult(eh.warnings, eh.errors)
+  }
+
+  private def initializeValidator(validator: XercesValidatorImpl, resolver: 
DFDLCatalogResolver): XercesValidatorImpl = {
+    validator.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
+    validator.setFeature("http://xml.org/sax/features/validation";, true)
+    validator.setFeature("http://apache.org/xml/features/validation/schema";, 
true)
+    
validator.setFeature("http://apache.org/xml/features/validation/schema-full-checking";,
 true)
+    validator.setResourceResolver(resolver)
+    validator
+  }
+}
+
+object XercesValidator {
+  private type XercesValidatorImpl = javax.xml.validation.Validator
+  val name = "xerces"
+}
+
+private class XercesErrorHandler extends ErrorHandler {
+  private var e = List.empty[ValidationFailure]
+  private var w = List.empty[ValidationWarning]
+
+  def errors: Seq[ValidationFailure] = e
+  def warnings: Seq[ValidationWarning] = w
+
+  override def warning(spe: SAXParseException): Unit =  w :+= 
SaxValidationWarning(spe)
+  override def error(spe: SAXParseException): Unit = e :+= 
SaxValidationError(spe)
+  override def fatalError(spe: SAXParseException): Unit = e :+= 
SaxValidationError(spe)
+}
+
+sealed abstract class SaxValidationResult(e: SAXException) extends 
Exception(e) with ValidationException
+case class SaxValidationError(e: SAXException) extends SaxValidationResult(e) 
with ValidationFailure with ValidationException
+case class SaxValidationWarning(e: SAXException) extends 
SaxValidationResult(e) with  ValidationWarning with ValidationException
diff --git a/daffodil-lib/src/test/resources/.keep 
b/daffodil-lib/src/test/resources/.keep
deleted file mode 100644
index e69de29..0000000
diff --git 
a/daffodil-lib/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
 
b/daffodil-lib/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
new file mode 100644
index 0000000..f6aeb35
--- /dev/null
+++ 
b/daffodil-lib/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You 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.
+#
+org.apache.daffodil.validation.PassingValidatorFactory
+org.apache.daffodil.validation.FailingValidatorFactory
diff --git 
a/daffodil-lib/src/test/resources/test/validation/testData1Infoset.xml 
b/daffodil-lib/src/test/resources/test/validation/testData1Infoset.xml
new file mode 100644
index 0000000..3868b51
--- /dev/null
+++ b/daffodil-lib/src/test/resources/test/validation/testData1Infoset.xml
@@ -0,0 +1,23 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+
+<tns:e1 xmlns:tns="http://example.com";>
+    <elementGroup>
+        <e2>12</e2>
+        <e3>345678</e3>
+    </elementGroup>
+</tns:e1>
diff --git 
a/daffodil-lib/src/test/resources/test/validation/testSchema1.dfdl.xsd 
b/daffodil-lib/src/test/resources/test/validation/testSchema1.dfdl.xsd
new file mode 100644
index 0000000..ca953b2
--- /dev/null
+++ b/daffodil-lib/src/test/resources/test/validation/testSchema1.dfdl.xsd
@@ -0,0 +1,46 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+
+<schema xmlns="http://www.w3.org/2001/XMLSchema";
+  targetNamespace="http://example.com"; 
xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/";
+  xmlns:xsd="http://www.w3.org/2001/XMLSchema";
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:tns="http://example.com";>
+
+  <annotation>
+    <appinfo source="http://www.ogf.org/dfdl/";>
+      <dfdl:format ref="tns:GeneralFormat" />
+    </appinfo>
+  </annotation>
+
+  <include 
schemaLocation="org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>
+
+  <element name="e1" dfdl:terminator="%NL;" type="tns:e1Type"/>
+
+  <complexType name="e1Type">
+    <sequence dfdl:initiator="" dfdl:separator="%SP;" 
dfdl:separatorPolicy="suppressed">
+      <element name="elementGroup">
+        <complexType>
+          <sequence dfdl:separator=":">
+            <element name="e2" type="xsd:int" dfdl:lengthKind="explicit" 
dfdl:length="2" />
+            <element name="e3" type="xsd:string" dfdl:lengthKind="explicit" 
dfdl:length="6" />
+          </sequence>
+        </complexType>
+      </element>
+    </sequence>
+  </complexType>
+
+</schema>
diff --git 
a/daffodil-lib/src/test/scala/org/apache/daffodil/validation/TestValidatorsSPI.scala
 
b/daffodil-lib/src/test/scala/org/apache/daffodil/validation/TestValidatorsSPI.scala
new file mode 100644
index 0000000..2e8484e
--- /dev/null
+++ 
b/daffodil-lib/src/test/scala/org/apache/daffodil/validation/TestValidatorsSPI.scala
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.validation
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class TestValidatorsSPI {
+  val schema = 
getClass.getResource("/test/validation/testSchema1.dfdl.xsd").toURI.toString
+  val infoset = 
getClass.getResourceAsStream("/test/validation/testData1Infoset.xml")
+
+  @Test def testValidatorGetNotFoundThrows(): Unit = {
+    assertThrows(classOf[ValidatorNotRegisteredException], () => 
Validators.get("dont exist"))
+  }
+
+  @Test def testValidatorFindNotFoundNone(): Unit = {
+    assertTrue(Validators.find("dont exist").isEmpty)
+  }
+
+  @Test def testValidatorNonExists(): Unit = {
+    assertFalse(Validators.isRegistered("dont exist"))
+  }
+
+  @Test def testDefaultIsRegistered(): Unit = {
+    val defaultName = XercesValidator.name
+    val defaultF = Validators.get(defaultName)
+
+    assertEquals(defaultName, defaultF.name())
+    assertTrue(Validators.find(defaultName).nonEmpty)
+    assertTrue(Validators.isRegistered(defaultName))
+  }
+
+  @Test def testPassingValidator(): Unit = {
+    val f = Validators.get(PassingValidator.name)
+    val v = f.make(XercesValidatorFactory.makeConfig(Seq(schema)))
+    val r = v.validateXML(infoset)
+
+    assertTrue(r.warnings.isEmpty)
+    assertTrue(r.errors.isEmpty)
+  }
+
+  @Test def testFailingValidator(): Unit = {
+    val f = Validators.get(FailingValidator.name)
+    val v = f.make(XercesValidatorFactory.makeConfig(Seq(schema)))
+    val r = v.validateXML(infoset)
+
+    assertTrue(r.warnings.isEmpty)
+    assertFalse(r.errors.isEmpty)
+
+    assertEquals(r.errors.iterator().next().getMessage, "boom")
+  }
+}
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala 
b/daffodil-lib/src/test/scala/org/apache/daffodil/validation/TestXercesValidator.scala
similarity index 59%
copy from 
daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
copy to 
daffodil-lib/src/test/scala/org/apache/daffodil/validation/TestXercesValidator.scala
index 3dc7a65..47621ff 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
+++ 
b/daffodil-lib/src/test/scala/org/apache/daffodil/validation/TestXercesValidator.scala
@@ -15,15 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.daffodil.api
+package org.apache.daffodil.validation
 
-import org.apache.daffodil.util.Enum
+import org.junit.Test
+import org.junit.Assert.assertTrue
 
-object ValidationMode extends Enum {
-  sealed abstract class Type protected (val mode: Int) extends EnumValueType 
with Ordered[Type] with Serializable {
-    def compare(that: ValidationMode.Type) = this.mode - that.mode
+class TestXercesValidator {
+  val schema = 
getClass.getResource("/test/validation/testSchema1.dfdl.xsd").toURI.toString
+  val infoset = 
getClass.getResourceAsStream("/test/validation/testData1Infoset.xml")
+
+  @Test def testFromSPI(): Unit = {
+    val f = Validators.get(XercesValidator.name)
+    val v = f.make(XercesValidatorFactory.makeConfig(Seq(schema)))
+    val r = v.validateXML(infoset)
+
+    assertTrue(r.warnings.isEmpty)
+    assertTrue(r.errors.isEmpty)
   }
-  case object Off extends Type(10)
-  case object Limited extends Type(20)
-  case object Full extends Type(30)
 }
diff --git 
a/daffodil-lib/src/test/scala/org/apache/daffodil/validation/ValidatorSPISupport.scala
 
b/daffodil-lib/src/test/scala/org/apache/daffodil/validation/ValidatorSPISupport.scala
new file mode 100644
index 0000000..3d81f14
--- /dev/null
+++ 
b/daffodil-lib/src/test/scala/org/apache/daffodil/validation/ValidatorSPISupport.scala
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.validation
+
+import java.io.InputStream
+
+import com.typesafe.config.Config
+import org.apache.daffodil.api.ValidationFailure
+import org.apache.daffodil.api.ValidationResult
+import org.apache.daffodil.api.ValidationWarning
+import org.apache.daffodil.api.Validator
+import org.apache.daffodil.api.ValidatorFactory
+
+class PassingValidatorFactory extends ValidatorFactory {
+  def name(): String = PassingValidator.name
+  def make(config: Config): Validator = new TestingValidatorSPI(Seq.empty, 
Seq.empty)
+}
+object PassingValidator {
+  val name = "passing-validator"
+}
+
+class FailingValidatorFactory extends ValidatorFactory {
+  def name(): String = FailingValidator.name
+  def make(config: Config): Validator = new TestingValidatorSPI(Seq.empty, 
Seq(ValFail("boom")))
+}
+object FailingValidator {
+  val name = "failing-validator"
+}
+
+class TestingValidatorSPI(w: Seq[ValidationWarning], f: 
Seq[ValidationFailure]) extends Validator {
+  def validateXML(document: InputStream): ValidationResult = 
ValidationResult(w, f)
+}
+
+case class ValFail(getMessage: String) extends ValidationFailure
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
index 6e79368..3d60450 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/DataProcessor.scala
@@ -31,8 +31,13 @@ import java.util.zip.GZIPOutputStream
 
 import scala.collection.immutable.Queue
 import scala.collection.mutable
-
-import org.apache.daffodil.Implicits._; object INoWarn4 {
+import org.apache.daffodil.Implicits._
+import org.apache.daffodil.api.ValidationException
+import org.apache.daffodil.api.ValidationFailure
+import org.apache.daffodil.api.ValidationResult
+import org.apache.daffodil.api.Validator
+import org.apache.daffodil.validation.XercesValidatorFactory
+; object INoWarn4 {
   ImplicitsSuppressUnusedImportWarning() }
 import org.apache.daffodil.api.DFDL
 import org.apache.daffodil.api.DaffodilTunables
@@ -50,7 +55,6 @@ import org.apache.daffodil.exceptions.UnsuppressableException
 import org.apache.daffodil.externalvars.Binding
 import org.apache.daffodil.externalvars.ExternalVariablesLoader
 import org.apache.daffodil.infoset.DIElement
-import org.apache.daffodil.infoset.InfosetElement
 import org.apache.daffodil.infoset.InfosetException
 import org.apache.daffodil.infoset.InfosetInputter
 import org.apache.daffodil.infoset.InfosetOutputter
@@ -70,14 +74,12 @@ import org.apache.daffodil.processors.unparsers.UnparseError
 import org.apache.daffodil.util.Maybe
 import org.apache.daffodil.util.Maybe._
 import org.apache.daffodil.util.Misc
-import org.apache.daffodil.util.Validator
 import org.apache.daffodil.xml.XMLUtils
 import org.xml.sax.ContentHandler
 import org.xml.sax.DTDHandler
 import org.xml.sax.EntityResolver
 import org.xml.sax.ErrorHandler
 import org.xml.sax.InputSource
-import org.xml.sax.SAXException
 import org.xml.sax.SAXNotRecognizedException
 import org.xml.sax.SAXNotSupportedException
 import org.xml.sax.SAXParseException
@@ -241,6 +243,17 @@ class DataProcessor private (
 
   def withValidationMode(mode:ValidationMode.Type): DataProcessor = 
copy(validationMode = mode)
 
+  def withValidator(validator: Validator): DataProcessor = 
withValidationMode(ValidationMode.Custom(validator))
+
+  lazy val validator: Validator = {
+    validationMode match {
+      case ValidationMode.Custom(cv) => cv
+      case _ =>
+        val cfg = 
XercesValidatorFactory.makeConfig(ssrd.elementRuntimeData.schemaURIStringsForFullValidation)
+        XercesValidatorFactory.makeValidator(cfg)
+    }
+  }
+
   // TODO Deprecate and replace usages with just tunables.
   def getTunables: DaffodilTunables = tunables
 
@@ -416,14 +429,16 @@ class DataProcessor private (
     // events are created rather than writing the entire infoset in memory and
     // then validating at the end of the parse. See DAFFODIL-2386
     //
-    val (outputter, maybeValidationBytes) =
-    if (validationMode == ValidationMode.Full) {
-      val bos = new java.io.ByteArrayOutputStream()
-      val xmlOutputter = new XMLTextInfosetOutputter(bos, false)
-      val teeOutputter = new TeeInfosetOutputter(output, xmlOutputter)
-      (teeOutputter, One(bos))
-    } else {
-      (output, Nope)
+    val (outputter, maybeValidationBytes) = {
+      validationMode match {
+        case ValidationMode.Full | ValidationMode.Custom(_) =>
+          val bos = new java.io.ByteArrayOutputStream()
+          val xmlOutputter = new XMLTextInfosetOutputter(bos, false)
+          val teeOutputter = new TeeInfosetOutputter(output, xmlOutputter)
+          (teeOutputter, One(bos))
+        case _ =>
+          (output, Nope)
+      }
     }
 
     val rootERD = ssrd.elementRuntimeData
@@ -688,41 +703,28 @@ class DataProcessor private (
 
 class ParseResult(dp: DataProcessor, override val resultState: PState)
   extends DFDL.ParseResult
-  with WithDiagnosticsImpl
-  with ErrorHandler {
+  with WithDiagnosticsImpl {
 
   /**
-   * To be successful here, we need to capture xerces parse/validation
+   * To be successful here, we need to capture parse/validation
    * errors and add them to the Diagnostics list in the PState.
    *
-   * @param state the initial parse state.
+   * @param bytes the parsed Infoset
    */
   def validateResult(bytes: Array[Byte]): Unit = {
     Assert.usage(resultState.processorStatus eq Success)
-    val schemaURIStrings = 
resultState.infoset.asInstanceOf[InfosetElement].runtimeData.schemaURIStringsForFullValidation
-    try {
-      val bis = new java.io.ByteArrayInputStream(bytes)
-      Validator.validateXMLSources(schemaURIStrings, bis, this)
-    } catch {
-      //
-      // Some SAX Parse errors are thrown even if you specify an error handler 
to the
-      // validator.
-      //
-      // So we also need this catch
-      //
-      case e: SAXException =>
-        resultState.validationErrorNoContext(e)
-    }
-  }
 
-  override def warning(spe: SAXParseException): Unit = {
-    resultState.validationErrorNoContext(spe)
-  }
-  override def error(spe: SAXParseException): Unit = {
-    resultState.validationErrorNoContext(spe)
-  }
-  override def fatalError(spe: SAXParseException): Unit = {
-    resultState.validationErrorNoContext(spe)
+    val bis = new java.io.ByteArrayInputStream(bytes)
+    dp.validator.validateXML(bis) match {
+      case ValidationResult(warnings, errors) =>
+        warnings.forEach{ w => resultState.validationError(w.getMessage) }
+        errors.forEach{
+          case e: ValidationException =>
+            resultState.validationErrorNoContext(e.getCause)
+          case f: ValidationFailure =>
+            resultState.validationError(f.getMessage)
+        }
+    }
   }
 }
 
diff --git 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
index 09bc00c..da76ce8 100644
--- 
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
+++ 
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/processors/RuntimeData.scala
@@ -672,7 +672,7 @@ sealed class ElementRuntimeData(
 
   def isSimpleType = optPrimType.isDefined
 
-  def schemaURIStringsForFullValidation = 
schemaURIStringsForFullValidation1.distinct
+  lazy val schemaURIStringsForFullValidation = 
schemaURIStringsForFullValidation1.distinct
   private def schemaURIStringsForFullValidation1: Seq[String] = 
(schemaFileLocation.uriString +:
     childERDs.flatMap { _.schemaURIStringsForFullValidation1 })
 
diff --git 
a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala 
b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala
index 19fb3ec..0d54ded 100644
--- a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala
+++ b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/Daffodil.scala
@@ -55,6 +55,7 @@ import org.apache.daffodil.processors.{ InvalidUsageException 
=> SInvalidUsageEx
 import java.net.URI
 
 import org.apache.daffodil.api.URISchemaSource
+import org.apache.daffodil.api.Validator
 import org.apache.daffodil.sapi.ValidationMode.ValidationMode
 import org.apache.daffodil.util.Maybe
 import org.apache.daffodil.util.Maybe._
@@ -105,6 +106,10 @@ object ValidationMode extends Enumeration {
   val Off = Value(10)
   val Limited = Value(20)
   val Full = Value(30)
+
+  case class Custom(v: Validator) extends ValidationMode {
+    val id: Int = 100
+  }
 }
 
 /**
@@ -563,6 +568,8 @@ class DataProcessor private[sapi] (private var dp: 
SDataProcessor)
     catch { case e: SInvalidUsageException => throw new 
InvalidUsageException(e) }
   }
 
+  def withValidator(validator: Validator): DataProcessor = 
withValidationMode(ValidationMode.Custom(validator))
+
 
   /**
    * Read external variables from a Daffodil configuration file
diff --git 
a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/packageprivate/Utils.scala
 
b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/packageprivate/Utils.scala
index 24eb415..2f66d73 100644
--- 
a/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/packageprivate/Utils.scala
+++ 
b/daffodil-sapi/src/main/scala/org/apache/daffodil/sapi/packageprivate/Utils.scala
@@ -71,6 +71,7 @@ private[sapi] object ValidationConversions {
       case ValidationMode.Off => SValidationMode.Off
       case ValidationMode.Limited => SValidationMode.Limited
       case ValidationMode.Full => SValidationMode.Full
+      case ValidationMode.Custom(v) => SValidationMode.Custom(v)
     }
     smode
   }
@@ -80,6 +81,7 @@ private[sapi] object ValidationConversions {
       case SValidationMode.Off => ValidationMode.Off
       case SValidationMode.Limited => ValidationMode.Limited
       case SValidationMode.Full => ValidationMode.Full
+      case SValidationMode.Custom(v) => ValidationMode.Custom(v)
     }
     mode
   }
diff --git 
a/daffodil-sapi/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
 
b/daffodil-sapi/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
new file mode 100644
index 0000000..9567de5
--- /dev/null
+++ 
b/daffodil-sapi/src/test/resources/META-INF/services/org.apache.daffodil.api.ValidatorFactory
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You 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.
+#
+org.apache.daffodil.example.PassingValidatorFactory
+org.apache.daffodil.example.FailingValidatorFactory
diff --git 
a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorApiExample.scala
similarity index 51%
copy from 
daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
copy to 
daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorApiExample.scala
index 3dc7a65..2061634 100644
--- a/daffodil-lib/src/main/scala/org/apache/daffodil/api/ValidationMode.scala
+++ 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorApiExample.scala
@@ -15,15 +15,28 @@
  * limitations under the License.
  */
 
-package org.apache.daffodil.api
+package org.apache.daffodil.example
 
-import org.apache.daffodil.util.Enum
+import org.junit.Test
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 
-object ValidationMode extends Enum {
-  sealed abstract class Type protected (val mode: Int) extends EnumValueType 
with Ordered[Type] with Serializable {
-    def compare(that: ValidationMode.Type) = this.mode - that.mode
+class ValidatorApiExample extends ValidatorExamplesSupport {
+  @Test
+  def testAlwaysPass(): Unit =
+    withSchema("/test/sapi/mySchema5.dfdl.xsd") { dp =>
+      withInput("/test/sapi/myData5.dat") { input =>
+        val res = dp.withValidator(Always.passes).parse(input, `/dev/null`)
+        assertFalse(res.isValidationError())
+      }
   }
-  case object Off extends Type(10)
-  case object Limited extends Type(20)
-  case object Full extends Type(30)
+
+  @Test
+  def testAlwaysFail(): Unit =
+    withSchema("/test/sapi/mySchema5.dfdl.xsd") { dp =>
+      withInput("/test/sapi/myData5.dat") { input =>
+        val res = dp.withValidator(Always.fails).parse(input, `/dev/null`)
+        assertTrue(res.isValidationError())
+      }
+    }
 }
diff --git 
a/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorExamplesSupport.scala
 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorExamplesSupport.scala
new file mode 100644
index 0000000..a9622e4
--- /dev/null
+++ 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorExamplesSupport.scala
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.example
+
+import java.io.File
+import java.io.FileInputStream
+import java.io.InputStream
+
+import com.typesafe.config.Config
+import org.apache.daffodil.api.ValidationFailure
+import org.apache.daffodil.api.ValidationResult
+import org.apache.daffodil.api.ValidationWarning
+import org.apache.daffodil.api.Validator
+import org.apache.daffodil.api.ValidatorFactory
+import org.apache.daffodil.sapi.Daffodil
+import org.apache.daffodil.sapi.DataProcessor
+import org.apache.daffodil.sapi.infoset.NullInfosetOutputter
+import org.apache.daffodil.sapi.io.InputSourceDataInputStream
+
+abstract class ValidatorExamplesSupport {
+  private def fileFromResource(path: String): File = new 
File(getClass.getResource(path).toURI)
+  val `/dev/null` = new NullInfosetOutputter()
+
+  def withSchema(name: String)(f: DataProcessor => Unit): Unit = {
+    val c = Daffodil.compiler()
+    val schemaFile = fileFromResource(name)
+    val pf = c.compileFile(schemaFile)
+    f(pf.onPath("/"))
+  }
+
+  def withInput(name: String)(f: InputSourceDataInputStream => Unit): Unit = {
+    f(new InputSourceDataInputStream(new 
FileInputStream(fileFromResource(name))))
+  }
+}
+
+class CustomValidator extends Validator {
+  def validateXML(document: InputStream): ValidationResult =
+    ValidationResult.empty
+}
+
+class CustomValidatorFactory extends ValidatorFactory {
+  def name(): String = "sapi-custom"
+  def make(config: Config): Validator = new CustomValidator
+}
+
+class AlwaysValidator(w: Seq[ValidationWarning], e: Seq[ValidationFailure]) 
extends Validator {
+  def validateXML(document: InputStream): ValidationResult =
+    ValidationResult(w, e)
+}
+
+object Boom extends ValidationFailure {
+  def getMessage: String = "boom"
+}
+
+object Always {
+  def fails: Validator = (_: InputStream) => ValidationResult(Seq.empty, 
Seq(Boom))
+  def passes: Validator = (_: InputStream) => ValidationResult.empty
+}
+
+class PassingValidatorFactory extends ValidatorFactory {
+  def name(): String = PassingValidator.name
+  def make(config: Config): Validator = new TestingValidatorSPI(Seq.empty, 
Seq.empty)
+}
+object PassingValidator {
+  val name = "passing-validator"
+}
+
+class FailingValidatorFactory extends ValidatorFactory {
+  def name(): String = FailingValidator.name
+  def make(config: Config): Validator = new TestingValidatorSPI(Seq.empty, 
Seq(ValFail("boom")))
+}
+object FailingValidator {
+  val name = "failing-validator"
+}
+
+class TestingValidatorSPI(w: Seq[ValidationWarning], f: 
Seq[ValidationFailure]) extends Validator {
+  def validateXML(document: InputStream): ValidationResult = 
ValidationResult(w, f)
+}
+
+case class ValFail(getMessage: String) extends ValidationFailure
diff --git 
a/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorSpiExample.scala
 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorSpiExample.scala
new file mode 100644
index 0000000..4afd974
--- /dev/null
+++ 
b/daffodil-sapi/src/test/scala/org/apache/daffodil/example/ValidatorSpiExample.scala
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.daffodil.example
+
+import com.typesafe.config.ConfigFactory
+import org.apache.daffodil.validation.Validators
+import org.junit.Test
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+
+// there is no support for passing validators by name into the dp as of yet
+// so these tests simply load via spi a validator and pass it into the SAPI
+class ValidatorSpiExample extends ValidatorExamplesSupport {
+  @Test
+  def testAlwaysPass(): Unit =
+    withSchema("/test/sapi/mySchema5.dfdl.xsd") { dp =>
+      withInput("/test/sapi/myData5.dat") { input =>
+        val v = Validators.get(PassingValidator.name).make(ConfigFactory.empty)
+        val res = dp.withValidator(v).parse(input, `/dev/null`)
+        assertFalse(res.isValidationError())
+      }
+    }
+
+  @Test
+  def testAlwaysFail(): Unit =
+    withSchema("/test/sapi/mySchema5.dfdl.xsd") { dp =>
+      withInput("/test/sapi/myData5.dat") { input =>
+        val v = Validators.get(FailingValidator.name).make(ConfigFactory.empty)
+        val res = dp.withValidator(v).parse(input, `/dev/null`)
+        assertTrue(res.isValidationError())
+      }
+    }
+}
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index 91a7ebb..fd076c7 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -29,6 +29,7 @@ object Dependencies {
     "xml-resolver" % "xml-resolver" % "1.2",
     "commons-io" % "commons-io" % "2.8.0",
     "jline" % "jline" % "2.14.6",
+    "com.typesafe" % "config" % "1.4.0"
   )
 
   lazy val infoset = Seq(

Reply via email to