blob: 27b090f051b73690c6c8e0ec57f2c0583f729752 [file] [log] [blame]
/*
* 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 org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.schema.annotation.props.gen.ParseUnparsePolicy
import org.apache.daffodil.util.LogLevel
import org.apache.daffodil.util.Logging
import org.apache.daffodil.util.Misc
import org.apache.daffodil.xml.DaffodilXMLLoader
object DaffodilTunables {
def apply(tunables: Map[String, String]): DaffodilTunables = {
apply().setTunables(tunables)
}
def apply(tunable: String, value: String): DaffodilTunables = {
apply().setTunable(tunable, value)
}
def apply(): DaffodilTunables = {
// override tunables from the global configuration file on the class path, if it exists
val (configOpt, _) = Misc.getResourceOption("/daffodil-config.xml")
val configTunables: Map[String, String] =
if (configOpt.isDefined) {
val loader = new DaffodilXMLLoader()
val node = loader.load(new URISchemaSource(configOpt.get))
val trimmed = scala.xml.Utility.trim(node)
val tunablesNode = (trimmed \ "tunables").headOption
val tunablesMap: Map[String, String] = tunablesNode match {
case None => Map.empty
case Some(tunableNode) => {
tunableNode.child.map { n => (n.label, n.text) }.toMap
}
}
tunablesMap
} else {
Map.empty
}
new DaffodilTunables().setTunables(configTunables)
}
}
case class DaffodilTunables(
val maxSkipLengthInBytes: Long = 1024, // applicable to leadingSkip and trailingSkip
val maxBinaryDecimalVirtualPoint: Int = 200, // Can be as large as Int.MaxValue
val minBinaryDecimalVirtualPoint: Int = -200, // Can be as small as Int.MinValue
val generatedNamespacePrefixStem: String = "tns",
val readerByteBufferSize: Long = 8192,
//
// If true, require that the bitOrder property is specified. If false, use a
// default value for bitOrder if not defined in a schema
//
// Looks to be compile-time as it gets 'tunable' from Term.
//
val requireBitOrderProperty: Boolean = false,
//
// If true, require that the encodingErrorPolicy property is specified. If
// false, use a default value not defined in a schema
//
// Looks to be compile-time as it gets 'tunable' from Term.
//
val requireEncodingErrorPolicyProperty: Boolean = false,
//
// Whether to compile a schema to support parsing, unparsing, both, or to use
// the daf:parseUnparsePolicy from the root node. None means to use the
// policy from the schema, otherwise use whatever the value is
//
// Looks to be compile-time. Set in Compiler.
///
val parseUnparsePolicy: Option[ParseUnparsePolicy] = None,
val maxFieldContentLengthInBytes: Long = 1024 * 1024, // Can be as large as Int.MaxValue
val defaultInitRegexMatchLimitInChars: Long = 32,
val maxDataDumpSizeInBytes: Long = 256,
val maxOccursBounds: Long = 1024, // Can be as large as Int.MaxValue
//
// When unexpected text is found where a delimiter is expected, this is the maximum
// number of bytes (characters) to display when the expected delimiter is a variable
// length delimiter.
//
val maxLengthForVariableLengthDelimiterDisplay: Int = 10, // will display this number of bytes
//
// In certain I/O optimized situations (text-only, encodingErrorPolicy='replace', fixed-width encoding)
// input files larger than this will be mmapped. Input files smaller than this
// will be simply read using ordinary I/O (because for small files that is just faster).
// This exists because mmap is more expensive than ordinary I/O for small files.
//
val inputFileMemoryMapLowThreshold: Long = 32 * 1024 * 1024, // 32Meg
//
// TODO: In the future, when we can stream and handle input larger than the JVM single
// object limits, input files larger than this will be streamed, i.e., using java.io.InputStream.
// They will not be memory mapped. A CharBuffer 2x larger may be created, and that
// cannot exceed the JVM maximum size, so this has to be no bigger (and perhaps quite a bit
// smaller) than 1/2 the maximum JVM object size.
//
// val inputFileMemoryMapHighThreshold: Long = 256 * 1024 * 1024, // 256 Meg
//
// Initial array buffer size allocated for recurring elements (aka arrays)
//
// Applies to InfosetImpl, as such is a run-time thing
//
val initialElementOccurrencesHint: Long = 10,
val unqualifiedPathStepPolicy: UnqualifiedPathStepPolicy.Type = UnqualifiedPathStepPolicy.NoNamespace,
val suppressSchemaDefinitionWarnings: List[WarnID] = Nil,
// By default, path expressions in Daffodil will only work correctly if path
// steps are used in an expression defined in the schema when compiled. To
// enable the use of other expressions (e.g. during debugging, where not all
// expressions are known at schema compile time), set this tunable to true.
// This may cause a degredation of performance in path expression evaluation,
// so this should be avoided when in production. This flag is automatically
// enabled when debugging is enabled.
val allowExternalPathExpressions: Boolean = false,
// A bug exists in Java 7 that causes unexpected behavior when decode errors
// occur in the specific ways that Daffodil decodes data. For this reason,
// Daffodil throws an exception when it detects that Daffodil is not running
// under Java 8 or has this decoder bug. However, there are some cases where
// a user has no choice but to run on Java 7. Setting this tunable to false
// will cause Daffodil to log a warning rather than throw an exception so
// that a user can run Daffodil on unsupported Java versions, with the
// understanding that it is not fully tested and behavior may not be well
// defined. This boolean is experimental and should only be used by those
// that fully understand the risks.
val errorOnUnsupportedJavaVersion: Boolean = true)
extends Serializable
with Logging
with DataStreamLimits {
/* Appear to be Compile-Time as the tunable is obtained from:
* Term, SchemaComponent, Element, etc.
*
* maxSkipLengthInBytes
* maxBinaryDecimalVirtualPoint
* minBinaryDecimalVirtualPoint
* requireEncodingErrorPolicyProperty
* requireBitOrderProperty
* generatedNamespacePrefixStem
* parseUnparsePolicy
*
* Used by StepQNameFactory. Appears to get tunable from:
* Expressions's DPathCompileInfo
* CompiledExpression's ElementRuntimeData
*
* unqualifiedPathStepPolicy
*
* Appear to be Run-Time as the tunable is obtained from:
* PState/UState
*
* initialElementOccurrencesHint
* maxOccursBounds
* maxDataDumpSizeInBytes
*
* Only used in Main.scala for DataInputStream:
*
* maxFieldContentLengthInBytes
*
* DataInputStream objects:
*
* defaultInitRegexMatchLimitInChars
*
* Unused?
*
* maxLengthForVariableLengthDelimiterDisplay
*
* */
def setTunables(tunables: Map[String, String]): DaffodilTunables = {
var t = this
tunables.foreach { case (k, v) => t = t.setTunable(k, v) }
t
}
def setTunable(tunable: String, value: String): DaffodilTunables = {
tunable.toLowerCase match {
case "maxfieldcontentlengthinbytes" => this.copy(maxFieldContentLengthInBytes = java.lang.Long.valueOf(value))
case "defaultinitialregexmatchlimitinchars" => this.copy(defaultInitRegexMatchLimitInChars = java.lang.Long.valueOf(value))
case "maxdatadumpsizeinbytes" => this.copy(maxDataDumpSizeInBytes = java.lang.Long.valueOf(value))
case "maxoccursbounds" => this.copy(maxOccursBounds = java.lang.Long.valueOf(value))
case "maxlengthforvariablelengthdelimiterdisplay" => this.copy(maxLengthForVariableLengthDelimiterDisplay = java.lang.Integer.valueOf(value))
case "inputfilememorymaplowthreshold" => this.copy(inputFileMemoryMapLowThreshold = java.lang.Long.valueOf(value))
case "initialelementoccurrenceshint" => this.copy(initialElementOccurrencesHint = java.lang.Long.valueOf(value))
case "unqualifiedpathsteppolicy" => {
val policy = value.toLowerCase match {
case "nonamespace" => UnqualifiedPathStepPolicy.NoNamespace
case "defaultnamespace" => UnqualifiedPathStepPolicy.DefaultNamespace
case "preferdefaultnamespace" => UnqualifiedPathStepPolicy.PreferDefaultNamespace
case _ => Assert.usageError("Unknown value for unqualifiedPathStepPolicy tunable. Value must be \"noNamespace\", \"defaultNamespace\", or \"perferDefaultNamespace\".")
}
this.copy(unqualifiedPathStepPolicy = policy)
}
case "requirebitorderproperty" => this.copy(requireBitOrderProperty = java.lang.Boolean.valueOf(value))
case "requireencodingerrorpolicyproperty" => this.copy(requireEncodingErrorPolicyProperty = java.lang.Boolean.valueOf(value))
case "maxskiplengthinbytes" => this.copy(maxSkipLengthInBytes = java.lang.Long.valueOf(value))
case "maxbinarydecimalvirtualpoint" => this.copy(maxBinaryDecimalVirtualPoint = java.lang.Integer.valueOf(value))
case "minbinarydecimalvirtualpoint" => this.copy(minBinaryDecimalVirtualPoint = java.lang.Integer.valueOf(value))
case "generatednamespaceprefixstem" => this.copy(generatedNamespacePrefixStem = value)
case "readerbytebuffersize" => this.copy(readerByteBufferSize = java.lang.Long.valueOf(value))
case "parseunparsepolicy" => {
val policy = value.toLowerCase match {
case "parseonly" => Some(ParseUnparsePolicy.ParseOnly)
case "unparseonly" => Some(ParseUnparsePolicy.UnparseOnly)
case "both" => Some(ParseUnparsePolicy.Both)
case "schema" => None
case _ => Assert.usageError("Unknown value for parseUnparsePolicy tunable. Value must be \"parseOnly\", \"unparseOnly\", \"both\", or \"schema\".")
}
this.copy(parseUnparsePolicy = policy)
}
case "suppressschemadefinitionwarnings" => {
val ws = """\s+"""
// value is whitespace separated list of warning identifier strings
val warnIDs = value.split(ws).toSeq
var warningsList: List[WarnID] = Nil
warnIDs.foreach { warnIDString =>
{
WarnID.find(warnIDString) match {
case None => log(LogLevel.Warning, "Ignoring unknown warning identifier: %s", warnIDString)
case Some(foundID) => warningsList = foundID :: warningsList
}
}
}
this.copy(suppressSchemaDefinitionWarnings = warningsList)
}
case "allowexternalpathexpressions" => this.copy(allowExternalPathExpressions = java.lang.Boolean.valueOf(value))
case "erroronunsupportedjavaversion" => this.copy(errorOnUnsupportedJavaVersion = java.lang.Boolean.valueOf(value))
case _ => {
log(LogLevel.Warning, "Ignoring unknown tunable: %s", tunable)
this
}
}
}
def notSuppressedWarning(warnID: WarnID) =
!suppressSchemaDefinitionWarnings.contains(warnID) &&
!suppressSchemaDefinitionWarnings.contains(WarnID.All)
def maximumSimpleElementSizeInBytes: Long = this.maxFieldContentLengthInBytes
def maximumSimpleElementSizeInCharacters: Long = this.maxFieldContentLengthInBytes
def maximumForwardSpeculationLengthInBytes: Long = this.maxFieldContentLengthInBytes
def maximumRegexMatchLengthInCharacters: Long = this.maxFieldContentLengthInBytes
def defaultInitialRegexMatchLimitInChars: Long = this.defaultInitRegexMatchLimitInChars
}