You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sl...@apache.org on 2020/12/02 18:20:15 UTC

[incubator-daffodil] branch master updated: Add validation support for tunables

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 7867d8e  Add validation support for tunables
7867d8e is described below

commit 7867d8e0cb99ec6b09f0e1ce0791ce850642f7f2
Author: Steve Lawrence <sl...@apache.org>
AuthorDate: Fri Nov 20 13:22:02 2020 -0500

    Add validation support for tunables
    
    - Adds xs:restrictions to tunables for which there is only a valid range
      within the range of the primitive type. Modifies the tunable generator
      to convert these restrictions to code checks and throw an
      IllegalArgumentException if outside the valid range.
    - Also sorts the tunable by name so they are organized.
    
    DAFFODIL-2432
---
 .../daffodil/processor/TestSAXUnparseAPI.scala     |  26 +--
 .../resources/org/apache/daffodil/xsd/dafext.xsd   | 211 ++++++++++++++++-----
 .../apache/daffodil/propGen/TunableGenerator.scala | 103 ++++++++--
 3 files changed, 258 insertions(+), 82 deletions(-)

diff --git a/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXUnparseAPI.scala b/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXUnparseAPI.scala
index de6f263..afd3937 100644
--- a/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXUnparseAPI.scala
+++ b/daffodil-core/src/test/scala/org/apache/daffodil/processor/TestSAXUnparseAPI.scala
@@ -20,8 +20,6 @@ package org.apache.daffodil.processor
 import java.io.ByteArrayInputStream
 import java.io.ByteArrayOutputStream
 
-import scala.xml.SAXException
-
 import javax.xml.parsers.SAXParserFactory
 import org.apache.daffodil.Implicits.intercept
 import org.apache.daffodil.xml.XMLUtils
@@ -50,24 +48,16 @@ class TestSAXUnparseAPI {
   }
 
   /**
-   * Test the case when a user supplies 0 as the batch size. Minimum batchsize must be 1
+   * Test the case when a user supplies 0 as the batch size as soon as an
+   * invalid tunable is set. Minimum batchsize must be 1.
    */
   @Test def testUnparseContentHandler_unparse_saxUnparseEventBatchSize_0(): Unit = {
-    val dpT = testDataProcessor(testSchema, Map("saxUnparseEventBatchSize" -> "0"))
-    val xmlReader: XMLReader = SAXParserFactory.newInstance.newSAXParser.getXMLReader
-    val bao = new ByteArrayOutputStream()
-    val wbc = java.nio.channels.Channels.newChannel(bao)
-    val unparseContentHandler = dpT.newContentHandlerInstance(wbc)
-    xmlReader.setContentHandler(unparseContentHandler)
-    xmlReader.setFeature(XMLUtils.SAX_NAMESPACES_FEATURE, true)
-    xmlReader.setFeature(XMLUtils.SAX_NAMESPACE_PREFIXES_FEATURE, true)
-    val bai = new ByteArrayInputStream(testInfosetString.getBytes)
-    val e = intercept[SAXException] {
-      xmlReader.parse(new InputSource(bai))
-    }
-    val eMsg = e.getMessage
-    assertTrue(eMsg.contains("invalid saxUnparseEventBatchSize"))
-    assertTrue(eMsg.contains("minimum value is 1"))
+     val e = intercept[java.lang.IllegalArgumentException] {
+      testDataProcessor(testSchema, Map("saxUnparseEventBatchSize" -> "0"))
+     }
+     val eMsg = e.getMessage
+     assertTrue(eMsg.contains("saxUnparseEventBatchSize"))
+     assertTrue(eMsg.contains("0"))
   }
 
   @Test def testUnparseContentHandler_unparse_namespace_feature(): Unit = {
diff --git a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
index 6a7548c..fcdb128 100644
--- a/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
+++ b/daffodil-propgen/src/main/resources/org/apache/daffodil/xsd/dafext.xsd
@@ -79,6 +79,25 @@
     </xs:simpleContent>
   </xs:complexType>
 
+  <!--
+    This file is used by TunableGenerator.scala to generate code that parses and
+    validates tunable values. The following schema elements define these tunables
+    to be generated. Although this schema is verbose and repetitive, it makes it
+    relatively straightforward to parse and ensure we generate correct code. As
+    such, one must follow a strict pattern to ensure correct code is generated.
+
+    Each element representing a tunable must have an schema primitive as its type,
+    as determined by the tunables allowed value space. Each tunable must also have
+    a default value that is valid for the type. If an element needs additional
+    limitations beyond the value space of the type, then an xs:simpleType with
+    xs:restriction must be used. The base attribute of the restriction must be a
+    schema primitive type. Only the following restrictions are currently supported:
+
+      - minInclusive
+      - maxInclusive
+      - minExclusive
+      - maxExclusive
+  -->
   <xs:element name="tunables">
     <xs:complexType>
       <xs:all>
@@ -110,55 +129,39 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
-        <xs:element name="blobChunkSizeInBytes" type="xs:int" default="4096" minOccurs="0">
+        <xs:element name="blobChunkSizeInBytes" default="4096" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               When reading/writing blob data, the maximum number of bytes to read/write at a time.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="outputStreamChunkSizeInBytes" type="xs:int" default="65536" minOccurs="0">
-          <xs:annotation>
-            <xs:documentation>
-              When writing file data to the output stream during unparse, this
-              is the maximum number of bytes to write at a time.
-            </xs:documentation>
-          </xs:annotation>
-        </xs:element>
-        <xs:element name="maxByteArrayOutputStreamBufferSizeInBytes" type="xs:long" default="2097152000" minOccurs="0">
-          <xs:annotation>
-            <xs:documentation>
-              When unparsing, this is the maximum size of the buffer that the
-              ByteArrayOutputStream can grow to before switching to a file based
-              output stream.
-            </xs:documentation>
-          </xs:annotation>
-        </xs:element>
-        <xs:element name="tempFilePath" type="xs:string" default="" minOccurs="0">
+        <xs:element name="defaultEmptyElementParsePolicy" type="daf:TunableEmptyElementParsePolicy" default="treatAsEmpty" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
-              When unparsing, use this path to store temporary files that may be genrated.
-              The default value (empty string) will result in the use of the java.io.tmpdir
-              property being used as the path.
+              Defines the default empty element parse policy to use if it is not defined
+              in a schema. This is only used if requireEmptyElementParsePolicyProperty is
+              false.
             </xs:documentation>
           </xs:annotation>
         </xs:element>
-        <xs:element name="defaultInitialRegexMatchLimitInChars" type="xs:int" default="32" minOccurs="0">
+        <xs:element name="defaultInitialRegexMatchLimitInChars" default="32" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Deprecated. This tunable no longer has any affect and is only kept for
               backwards compatability.
             </xs:documentation>
           </xs:annotation>
-        </xs:element>
-        <xs:element name="defaultEmptyElementParsePolicy" type="daf:TunableEmptyElementParsePolicy" default="treatAsEmpty" minOccurs="0">
-          <xs:annotation>
-            <xs:documentation>
-              Defines the default empty element parse policy to use if it is not defined
-              in a schema. This is only used if requireEmptyElementParsePolicyProperty is
-              false.
-            </xs:documentation>
-          </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
         <xs:element name="errorOnUnsupportedJavaVersion" type="xs:boolean" default="true" minOccurs="0">
           <xs:annotation>
@@ -176,14 +179,19 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
-        <xs:element name="initialElementOccurrencesHint" type="xs:int" default="10" minOccurs="0">
+        <xs:element name="initialElementOccurrencesHint" default="10" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Initial array buffer size allocated for recurring elements/arrays.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="initialRegexMatchLimitInCharacters" type="xs:int" default="64" minOccurs="0">
+        <xs:element name="initialRegexMatchLimitInCharacters" default="64" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Initial number of characters to match when performing regular expression
@@ -191,6 +199,11 @@
               consumed up to the maximumRegexMatchLengthInCharacters tunable.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
         <xs:element name="inputFileMemoryMapLowThreshold" type="xs:int" default="33554432" minOccurs="0">
           <xs:annotation>
@@ -200,20 +213,44 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
-        <xs:element name="maxBinaryDecimalVirtualPoint" type="xs:int" default="200" minOccurs="0">
+        <xs:element name="maxBinaryDecimalVirtualPoint" default="200" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               The largest allowed value of the dfdl:binaryDecimalVirtualPoint property.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
+        <xs:element name="maxByteArrayOutputStreamBufferSizeInBytes" default="2097152000" minOccurs="0">
+          <xs:annotation>
+            <xs:documentation>
+              When unparsing, this is the maximum size of the buffer that the
+              ByteArrayOutputStream can grow to before switching to a file based
+              output stream.
+            </xs:documentation>
+          </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="0" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="maxDataDumpSizeInBytes" type="xs:int" default="256" minOccurs="0">
+        <xs:element name="maxDataDumpSizeInBytes" default="256" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
-              The maximum size of data to retrive when When getting data to display
+              The maximum size of data to retrive when when getting data to display
               for debugging.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
         <xs:element name="maxFieldContentLengthInBytes" type="xs:int" default="1048576" minOccurs="0">
           <xs:annotation>
@@ -223,7 +260,7 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
-        <xs:element name="maxLengthForVariableLengthDelimiterDisplay" type="xs:int" default="10" minOccurs="0">
+        <xs:element name="maxLengthForVariableLengthDelimiterDisplay" default="10" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               When unexpected text is found where a delimiter is expected, this is the maximum
@@ -231,8 +268,13 @@
               length delimiter.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="maxLookaheadFunctionBits" type="xs:long" default="512" minOccurs="0">
+        <xs:element name="maxLookaheadFunctionBits" default="512" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Max distance that the DPath lookahead function is permitted to look.
@@ -240,23 +282,37 @@
               so it is offset+bitsize.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:long">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="maxOccursBounds" type="xs:int" default="2147483647" minOccurs="0">
+        <xs:element name="maxOccursBounds" default="2147483647" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Maximum number of occurances of an array element.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:long">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="maxSkipLengthInBytes" type="xs:int" default="1024" minOccurs="0">
+        <xs:element name="maxSkipLengthInBytes" default="1024" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Maximum number of bytes allowed to skip in a skip region.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        
-        <xs:element name="maxValidYear" type="xs:int" default="9999" minOccurs="0">
+        <xs:element name="maxValidYear" default="9999" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Due to differences in the DFDL spec and ICU4J SimpleDateFormat, we must
@@ -265,28 +321,48 @@
               tunable tunable sets an upper limit for values to prevent overflow.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="maximumRegexMatchLengthInCharacters" type="xs:int" default="1048576" minOccurs="0">
+        <xs:element name="maximumRegexMatchLengthInCharacters" default="1048576" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Maximum number of characters to match when performing regular expression
               matches on input data.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="maximumSimpleElementSizeInCharacters" type="xs:int" default="1048576" minOccurs="0">
+        <xs:element name="maximumSimpleElementSizeInCharacters" default="1048576" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Maximum number of characters to parse when parsing string data.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="minBinaryDecimalVirtualPoint" type="xs:int" default="-200" minOccurs="0">
+        <xs:element name="minBinaryDecimalVirtualPoint" default="-200" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               The smallest allowed value of the dfdl:binaryDecimalVirtualPoint property.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:maxInclusive value="-1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
         <xs:element name="minValidYear" type="xs:int" default="0" minOccurs="0">
           <xs:annotation>
@@ -298,6 +374,19 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
+        <xs:element name="outputStreamChunkSizeInBytes" default="65536" minOccurs="0">
+          <xs:annotation>
+            <xs:documentation>
+              When writing file data to the output stream during unparse, this
+              is the maximum number of bytes to write at a time.
+            </xs:documentation>
+          </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
+        </xs:element>
         <xs:element name="parseUnparsePolicy" type="daf:TunableParseUnparsePolicyTunable" default="fromRoot" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
@@ -390,7 +479,7 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
-        <xs:element name="saxUnparseEventBatchSize" type="xs:int" default="100" minOccurs="0">
+        <xs:element name="saxUnparseEventBatchSize" default="100" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               Daffodil's SAX Unparse API allows events to be batched in memory to minimize the
@@ -401,6 +490,11 @@
               frequency of context switching, but increase the memory footprint.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
         <xs:element name="suppressSchemaDefinitionWarnings" type="daf:TunableSuppressSchemaDefinitionWarnings" default="emptyElementParsePolicyError" minOccurs="0">
           <xs:annotation>
@@ -410,6 +504,15 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
+        <xs:element name="tempFilePath" type="xs:string" default="This string is ignored. Default value is taken from java.io.tmpdir property"  minOccurs="0">
+          <xs:annotation>
+            <xs:documentation>
+              When unparsing, use this path to store temporary files that may be genrated.
+              The default value (empty string) will result in the use of the java.io.tmpdir
+              property being used as the path.
+            </xs:documentation>
+          </xs:annotation>
+        </xs:element>
         <xs:element name="unqualifiedPathStepPolicy" type="daf:TunableUnqualifiedPathStepPolicy" default="noNamespace" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
@@ -422,7 +525,7 @@
             </xs:documentation>
           </xs:annotation>
         </xs:element>
-        <xs:element name="unparseSuspensionWaitOld" type="xs:int" default="100" minOccurs="0">
+        <xs:element name="unparseSuspensionWaitOld" default="100" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               While unparsing, some unparse actions require "suspending" which
@@ -440,13 +543,23 @@
               young and old suspensions, respectively.
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
-        <xs:element name="unparseSuspensionWaitYoung" type="xs:int" default="5" minOccurs="0">
+        <xs:element name="unparseSuspensionWaitYoung" default="5" minOccurs="0">
           <xs:annotation>
             <xs:documentation>
               See unparseSuspensionWaitOld
             </xs:documentation>
           </xs:annotation>
+          <xs:simpleType>
+            <xs:restriction base="xs:int">
+              <xs:minInclusive value="1" />
+            </xs:restriction>
+          </xs:simpleType>
         </xs:element>
       </xs:all>
     </xs:complexType>
diff --git a/daffodil-propgen/src/main/scala/org/apache/daffodil/propGen/TunableGenerator.scala b/daffodil-propgen/src/main/scala/org/apache/daffodil/propGen/TunableGenerator.scala
index 821c543..b235a1e 100644
--- a/daffodil-propgen/src/main/scala/org/apache/daffodil/propGen/TunableGenerator.scala
+++ b/daffodil-propgen/src/main/scala/org/apache/daffodil/propGen/TunableGenerator.scala
@@ -117,6 +117,10 @@ class TunableGenerator(schemaRootConfig: scala.xml.Node, schemaRootExt: scala.xm
     |    !suppressSchemaDefinitionWarnings.contains(warnID) &&
     |      !suppressSchemaDefinitionWarnings.contains(WarnID.All)
     |
+    |  private def throwInvalidTunableValue(tunable: String, value: String) = {
+    |    throw new IllegalArgumentException("Invalid value for tunable " + tunable + ": " + value)
+    |  }
+    |
     |}
     """.trim.stripMargin
 
@@ -134,9 +138,15 @@ class TunableGenerator(schemaRootConfig: scala.xml.Node, schemaRootExt: scala.xm
     val tunables =
       tunableNodes.map { tunableNode =>
         val schemaName = tunableNode \@ "name"
-        val schemaType = tunableNode \@ "type"
+        val schemaType =
+          if (tunableNode \@ "type" != "") tunableNode \@ "type"
+          else tunableNode \\ "restriction" \@ "base"
         val schemaDefault = tunableNode \@ "default"
 
+        if (schemaName == "") throw new Exception("Tunable missing mandatory name attribute: " + tunableNode)
+        if (schemaType == "") throw new Exception("Tunable missing mandatory type or restriction base attribute: " + schemaName)
+        if (schemaDefault == "") throw new Exception("Tunable missing mandatory default attribute: " + schemaName)
+
         val tunable =
           if (schemaName == "suppressSchemaDefinitionWarnings") {
             // special case, list of enums
@@ -146,21 +156,37 @@ class TunableGenerator(schemaRootConfig: scala.xml.Node, schemaRootExt: scala.xm
             new EnumTunable(schemaName, schemaType, schemaDefault)
           } else if (schemaName == "tempFilePath") {
             // special case, creates actual file object instead of string
-            new TempFilePathTunable()
+            new FileTunable("tempFilePath", """System.getProperty("java.io.tmpdir")""")
           } else {
             // primitive type
-            new PrimitiveTunable(schemaName, schemaType, schemaDefault)
+            new PrimitiveTunable(schemaName, schemaType, schemaDefault, tunableNode)
           }
         tunable
-      }
+      }.sortBy(_.name)
+
+    val definitionString =
+      tunables
+        .map(_.scalaDefinition)
+        .mkString("  ", ",\n  ", ")")
+
+    val conversionString =
+      tunables
+        .map { tunable =>
+          tunable
+            .scalaConversion
+            .split("\n")
+            .filter(_.trim.length > 0)
+            .mkString("      ", "\n      ", "")
+        }
+        .mkString("\n")
 
     w.write(top)
     w.write("\n")
-    w.write(tunables.map(_.scalaDefinition).mkString("  ", ",\n  ", ")"))
+    w.write(definitionString)
     w.write("\n")
     w.write(middle)
     w.write("\n")
-    w.write(tunables.map(_.scalaConversion.split("\n").mkString("      ", "\n      ", "")).mkString("\n"))
+    w.write(conversionString)
     w.write("\n")
     w.write(bottom)
     w.write("\n")
@@ -180,9 +206,14 @@ class TunableGenerator(schemaRootConfig: scala.xml.Node, schemaRootExt: scala.xm
 abstract class TunableBase {
   def scalaDefinition: String
   def scalaConversion: String
+  def name: String
 }
 
-class PrimitiveTunable(name: String, schemaType: String, schemaDefault: String)
+class PrimitiveTunable(
+  override val name: String,
+  schemaType: String,
+  schemaDefault: String,
+  node: scala.xml.Node)
   extends TunableBase {
 
   private val scalaType = schemaType match {
@@ -190,6 +221,7 @@ class PrimitiveTunable(name: String, schemaType: String, schemaDefault: String)
     case "xs:int" => "Int"
     case "xs:long" => "Long"
     case "xs:string" => "String"
+    case _ => throw new Exception("Type not supported for tunable: " + schemaType)
   }
 
   private val scalaDefault = schemaType match {
@@ -197,16 +229,53 @@ class PrimitiveTunable(name: String, schemaType: String, schemaDefault: String)
     case _ => schemaDefault
   }
 
+  private def restrictionCheck(rCheck: String, rValue: String): Option[String] = {
+    if (rValue != "") {
+      Some(s"""  if (!(v ${rCheck} ${rValue})) throwInvalidTunableValue(tunable, value)""")
+    } else {
+      None
+    }
+  }
+
+  private val minInclusive = node \\ "minInclusive" \@ "value"
+  private val maxInclusive = node \\ "maxInclusive" \@ "value"
+  private val minExclusive = node \\ "minExclusive" \@ "value"
+  private val maxExclusive = node \\ "maxExclusive" \@ "value"
+
+  private val restrictionChecks = Seq(
+    restrictionCheck(">=", minInclusive),
+    restrictionCheck("<=", maxInclusive),
+    restrictionCheck(">", minExclusive),
+    restrictionCheck("<", maxExclusive),
+  ).flatten.mkString("\n")
+
   override val scalaDefinition = s"""val ${name}: ${scalaType} = ${scalaDefault}"""
-  override val scalaConversion = s"""case "${name}" => this.copy(${name} = value.to${scalaType})"""
+  override val scalaConversion = s"""
+    |case "${name}" => {
+    |  val v = scala.util.Try(value.to${scalaType}).getOrElse { throwInvalidTunableValue(tunable, value) }
+    |${restrictionChecks}
+    |  this.copy(${name} = v)
+    |}
+    """.trim.stripMargin
 }
 
-class TempFilePathTunable() extends TunableBase {
-  override val scalaDefinition = s"""val tempFilePath: java.io.File = new java.io.File(System.getProperty(\"java.io.tmpdir\"))"""
-  override val scalaConversion = s"""case "tempFilePath" => this.copy(tempFilePath = new java.io.File(value))"""
+class FileTunable(
+  override val name: String,
+  default: String)
+  extends TunableBase {
+
+  override val scalaDefinition = s"""val ${name}: java.io.File = new java.io.File(${default})"""
+  override val scalaConversion = s"""
+    |case "${name}" => {
+    |  this.copy(${name} = new java.io.File(value))
+    |}
+    """.trim.stripMargin
 }
 
-class EnumTunable(name: String, schemaType: String, schemaDefault: String)
+class EnumTunable(
+  override val name: String,
+  schemaType: String,
+  schemaDefault: String)
   extends TunableBase {
 
   private val scalaType = schemaType.stripPrefix("daf:Tunable")
@@ -216,13 +285,17 @@ class EnumTunable(name: String, schemaType: String, schemaDefault: String)
   override val scalaConversion = s"""
     |case "${name}" => {
     |  val vOpt = ${scalaType}.optionStringToEnum("${scalaType}", value)
-    |  val v = vOpt.getOrElse(throw new IllegalArgumentException("For input string: \\"" + value + "\\""))
+    |  val v = vOpt.getOrElse { throwInvalidTunableValue(tunable, value) }
     |  this.copy(${name} = v)
     |}
     """.trim.stripMargin
 }
 
-class EnumListTunable(name: String, schemaType: String, schemaDefault: String, listType: String)
+class EnumListTunable(
+  override val name: String,
+  schemaType: String,
+  schemaDefault: String,
+  listType: String)
   extends TunableBase {
 
   val scalaDefault = {
@@ -241,7 +314,7 @@ class EnumListTunable(name: String, schemaType: String, schemaDefault: String, l
     |case "${name}" => {
     |  val values = value.split("\\\\s+").toSeq.map { v =>
     |    val vOpt = ${listType}.optionStringToEnum("${listType}", v)
-    |    vOpt.getOrElse(throw new IllegalArgumentException("For input string: \\"" + v + "\\""))
+    |    vOpt.getOrElse { throwInvalidTunableValue(tunable, value) }
     |  }
     |  this.copy(${name} = values)
     |}