blob: 5d1c1d1669389b1da509998b723990cd65cad2a4 [file] [log] [blame]
/* Copyright (c) 2012-2015 Tresys Technology, LLC. All rights reserved.
*
* Developed by: Tresys Technology, LLC
* http://www.tresys.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the names of Tresys Technology, nor the names of its contributors
* may be used to endorse or promote products derived from this Software
* without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
package edu.illinois.ncsa.daffodil.processors
import edu.illinois.ncsa.daffodil.Implicits._; object INoWarn4 { ImplicitsSuppressUnusedImportWarning() }
import edu.illinois.ncsa.daffodil.equality._; object EqualityNoWarn3 { EqualitySuppressUnusedImportWarning() }
import edu.illinois.ncsa.daffodil.api.WithDiagnostics
import edu.illinois.ncsa.daffodil.xml._
import edu.illinois.ncsa.daffodil.exceptions.Assert
import edu.illinois.ncsa.daffodil.dsom._
import edu.illinois.ncsa.daffodil.ExecutionMode
import edu.illinois.ncsa.daffodil.api.DFDL
import edu.illinois.ncsa.daffodil.api.WithDiagnostics
import edu.illinois.ncsa.daffodil.api.DFDL
import edu.illinois.ncsa.daffodil.dsom.ValidationError
import edu.illinois.ncsa.daffodil.util.Validator
import org.xml.sax.SAXParseException
import org.xml.sax.SAXException
import edu.illinois.ncsa.daffodil.util.ValidationException
import edu.illinois.ncsa.daffodil.api.ValidationMode
import edu.illinois.ncsa.daffodil.externalvars.ExternalVariablesLoader
import java.io.File
import edu.illinois.ncsa.daffodil.externalvars.Binding
import java.nio.channels.Channels
import java.nio.channels.FileChannel
import edu.illinois.ncsa.daffodil.util.Maybe
import edu.illinois.ncsa.daffodil.util.Maybe._
import java.io.ObjectOutputStream
import edu.illinois.ncsa.daffodil.util.Logging
import edu.illinois.ncsa.daffodil.api.DaffodilTunableParameters.TunableLimitExceededError
import edu.illinois.ncsa.daffodil.api.DaffodilTunableParameters
import edu.illinois.ncsa.daffodil.debugger.Debugger
import java.util.zip.GZIPOutputStream
import edu.illinois.ncsa.daffodil.processors.unparsers.UState
import edu.illinois.ncsa.daffodil.processors.unparsers.InfosetCursor
import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.BitOrder
import edu.illinois.ncsa.daffodil.processors.unparsers.UnparseError
import edu.illinois.ncsa.daffodil.dsom.oolag.ErrorAlreadyHandled
import edu.illinois.ncsa.daffodil.events.MultipleEventHandler
import edu.illinois.ncsa.daffodil.io.DataStreamCommon
import edu.illinois.ncsa.daffodil.io.DirectOrBufferedDataOutputStream
import edu.illinois.ncsa.daffodil.util.LogLevel
/**
* Implementation mixin - provides simple helper methods
*/
trait WithDiagnosticsImpl extends WithDiagnostics {
// final lazy val hasDiagnostics = {
// getDiagnostics.size > 0
// }
}
class InvalidUsageException(msg: String, cause: Throwable = null) extends Exception(msg, cause)
/**
* This is the DataProcessor constructed from a saved Parser.
*/
class SerializableDataProcessor(val data: SchemaSetRuntimeData)
extends DataProcessor(data) {
override def setValidationMode(mode: ValidationMode.Type): Unit = {
if (mode == ValidationMode.Full) { throw new InvalidUsageException("'Full' validation not allowed when using a restored parser.") }
super.setValidationMode(mode)
}
}
trait HasSetDebugger {
def setDebugger(dbg: Debugger): Unit
def setDebugging(b: Boolean): Unit
}
/**
* The very last aspects of compilation, and the start of the
* back-end runtime.
*/
class DataProcessor(val ssrd: SchemaSetRuntimeData)
extends DFDL.DataProcessor with Logging
with HasSetDebugger with Serializable
with MultipleEventHandler {
def setValidationMode(mode: ValidationMode.Type): Unit = { ssrd.validationMode = mode }
def getValidationMode() = ssrd.validationMode
def getVariables = ssrd.variables
@transient private var areDebugging_ = false
def areDebugging = areDebugging_
@transient private var optDebugger_ : Option[Debugger] = None
private def optDebugger = {
if (optDebugger_ == null) {
// transient value restored as null
optDebugger_ = None
}
optDebugger_
}
def debugger = {
Assert.invariant(areDebugging)
optDebugger.get
}
def setDebugger(dbg: Debugger) {
optDebugger_ = Some(dbg)
}
def setDebugging(flag: Boolean) {
areDebugging_ = flag
}
def setExternalVariables(extVars: Map[String, String]): Unit = {
val bindings = ExternalVariablesLoader.getVariables(extVars)
ExternalVariablesLoader.loadVariables(bindings, ssrd, ssrd.variables)
ssrd.variables = ExternalVariablesLoader.loadVariables(extVars, ssrd, ssrd.variables)
}
def setExternalVariables(extVars: File): Unit = {
ssrd.variables = ExternalVariablesLoader.loadVariables(extVars, ssrd, ssrd.variables)
}
def setExternalVariables(extVars: Seq[Binding]): Unit = {
ssrd.variables = ExternalVariablesLoader.loadVariables(extVars, ssrd, ssrd.variables)
}
override def isError = false // really there is no compiling at all currently, so there can be no errors.
override def getDiagnostics = ssrd.diagnostics
def save(output: DFDL.Output): Unit = {
val oos = new ObjectOutputStream(new GZIPOutputStream(Channels.newOutputStream(output)))
oos.writeObject(new SerializableDataProcessor(ssrd))
oos.close()
}
/**
* Here begins the parser runtime. Compiler-oriented mechanisms (OOLAG etc.) aren't used in the
* runtime. Instead we deal with success and failure statuses.
*/
def parse(input: DFDL.Input, lengthLimitInBits: Long = -1): DFDL.ParseResult = {
Assert.usage(!this.isError)
val rootERD = ssrd.elementRuntimeData
val initialState =
PState.createInitialPState(rootERD,
input,
this,
bitOffset = 0,
bitLengthLimit = lengthLimitInBits) // TODO also want to pass here the externally set variables, other flags/settings.
parse(initialState)
}
def parse(file: File): DFDL.ParseResult = {
Assert.usage(!this.isError)
val initialState =
PState.createInitialPState(ssrd.elementRuntimeData,
FileChannel.open(file.toPath),
this,
bitOffset = 0,
bitLengthLimit = file.length * 8) // TODO also want to pass here the externally set variables, other flags/settings.
parse(initialState)
}
def parse(state: PState): ParseResult = {
ExecutionMode.usingRuntimeMode {
if (areDebugging) {
Assert.invariant(optDebugger.isDefined)
addEventHandler(debugger)
state.notifyDebugging(true)
}
state.dataProc.get.init(ssrd.parser)
doParse(ssrd.parser, state)
val pr = new ParseResult(this, state)
pr.validateResult(state)
val s = state
val dp = s.dataProc
val ssrdParser = ssrd.parser
if (dp.isDefined) dp.value.fini(ssrdParser)
pr
}
}
object dataInputStreamLimits extends DataStreamCommon.Limits {
def maximumSimpleElementSizeInBytes: Long = DaffodilTunableParameters.maxFieldContentLengthInBytes
def maximumSimpleElementSizeInCharacters: Long = DaffodilTunableParameters.maxFieldContentLengthInBytes
def maximumForwardSpeculationLengthInBytes: Long = DaffodilTunableParameters.maxFieldContentLengthInBytes
def maximumRegexMatchLengthInCharacters: Long = DaffodilTunableParameters.maxFieldContentLengthInBytes
def defaultInitialRegexMatchLimitInChars: Long = DaffodilTunableParameters.defaultInitialRegexMatchLimitInChars
}
private def doParse(p: Parser, state: PState) {
try {
state.dataInputStream.setLimits(dataInputStreamLimits)
this.startElement(state, p)
p.parse1(state)
this.endElement(state, p)
} catch {
// technically, runtime shouldn't throw. It's really too heavyweight a construct. And "failure"
// when parsing isn't exceptional, it's routine behavior. So ought not be implemented via an
// exception handling construct.
//
// But we might not catch everything inside...
//
case pe: ParseError => {
// if we get one here, then someone threw instead of returning a status.
Assert.invariantFailed("ParseError caught. ParseErrors should be returned as failed status, not thrown. Fix please.")
}
case procErr: ProcessingError => {
val x = procErr
state.setFailed(x.toParseError)
}
case sde: SchemaDefinitionError => {
// A SDE was detected at runtime (perhaps due to a runtime-valued property like byteOrder or encoding)
// These are fatal, and there's no notion of backtracking them, so they propagate to top level
// here.
state.setFailed(sde)
}
case rsde: RuntimeSchemaDefinitionError => {
state.setFailed(rsde)
}
case e: ErrorAlreadyHandled => {
state.setFailed(e.th)
}
case e: TunableLimitExceededError => {
state.setFailed(e)
}
}
state.dataInputStream.validateFinalStreamState
}
def unparse(output: DFDL.Output, xmlEventCursor: XMLEventCursor): DFDL.UnparseResult = {
val rootERD = ssrd.elementRuntimeData
val infosetCursor = InfosetCursor.fromXMLEventCursor(xmlEventCursor, rootERD)
unparse(output, infosetCursor)
}
def unparse(output: DFDL.Output, infosetXML: scala.xml.Node): DFDL.UnparseResult = {
val rootERD = ssrd.elementRuntimeData
val is = InfosetCursor.fromXMLNode(infosetXML, rootERD)
unparse(output, is)
}
def unparse(output: DFDL.Output, infosetCursor: InfosetCursor): DFDL.UnparseResult = {
Assert.usage(!this.isError)
val outStream = java.nio.channels.Channels.newOutputStream(output)
val out = DirectOrBufferedDataOutputStream(outStream, null) // null means no other stream created this one.
out.setBitOrder(BitOrder.MostSignificantBitFirst) // FIXME: derive from rootERD (doesn't have currently.) Note: only needed if starting bit position isn't 0
val unparserState =
UState.createInitialUState(
out,
this,
infosetCursor) // TODO also want to pass here the externally set variables, other flags/settings.
val res = try {
if (areDebugging) {
Assert.invariant(optDebugger.isDefined)
addEventHandler(debugger)
unparserState.notifyDebugging(true)
}
unparserState.dataProc.get.init(ssrd.unparser)
unparse(unparserState)
unparserState.evalSuspensions(unparserState) // handles outputValueCalc that were suspended due to forward references.
unparserState.unparseResult
} catch {
case ue: UnparseError => {
unparserState.addUnparseError(ue)
unparserState.unparseResult
}
case procErr: ProcessingError => {
val x = procErr
unparserState.setFailed(x.toUnparseError)
unparserState.unparseResult
}
case sde: SchemaDefinitionError => {
// A SDE was detected at runtime (perhaps due to a runtime-valued property like byteOrder or encoding)
// These are fatal, and there's no notion of backtracking them, so they propagate to top level
// here.
unparserState.setFailed(sde)
unparserState.unparseResult
}
case rsde: RuntimeSchemaDefinitionError => {
unparserState.setFailed(rsde)
unparserState.unparseResult
}
case e: ErrorAlreadyHandled => {
unparserState.setFailed(e.th)
unparserState.unparseResult
}
case e: TunableLimitExceededError => {
unparserState.setFailed(e)
unparserState.unparseResult
}
case se: org.xml.sax.SAXException => {
unparserState.setFailed(new UnparseError(None, None, se.getMessage))
unparserState.unparseResult
}
case e: scala.xml.parsing.FatalError => {
unparserState.setFailed(new UnparseError(None, None, e.getMessage))
unparserState.unparseResult
}
}
if (unparserState.dataProc.isDefined) unparserState.dataProc.value.fini(ssrd.unparser)
infosetCursor.fini
res
}
def unparse(state: UState): Unit = {
val rootUnparser = ssrd.unparser
// LoggingDefaults.setLoggingLevel(LogLevel.Debug)
rootUnparser.unparse(state)
//
// All the DOS that precede the last one
// will get setFinished by the suspension that created them. The last one after the
// final suspension gets setFinished here.
//
// TODO: revisit this in light of API changes that allow a series of calls
// that unparse to the same output stream. Sort of like a cursor.
// The idea here is that I'd have an incoming stream of Infoset events, and
// an outgoing java.io.outputStream, and each unparse call would consume
// events, and write to the java.io.outputStream, but neither would be closed.
// An application could do this in a loop calling unparse repeatedly
// without having to create a new infoset event stream or outputstream.
//
Assert.invariant(!state.dataOutputStream.isFinished)
state.dataOutputStream.setFinished()
log(LogLevel.Debug, "%s final stream for %s finished.", this, state)
val ev = state.advanceMaybe
if (ev.isDefined) {
UnparseError(Nope, One(state.currentLocation), "Expected no remaining events, but received %s.", ev.get)
}
}
}
class ParseResult(dp: DataProcessor, override val resultState: PState)
extends DFDL.ParseResult
with WithDiagnosticsImpl {
def toWriter(writer: java.io.Writer) = {
resultState.infoset.toWriter(writer)
writer.flush()
}
/**
* Xerces validation.
*/
private def validateWithXerces(state: PState): Unit = {
if (state.status eq Success) {
val schemaURIStrings = state.infoset.asInstanceOf[InfosetElement].runtimeData.schemaURIStringsForFullValidation
Validator.validateXMLSources(schemaURIStrings, result)
} else {
Assert.abort(new IllegalStateException("There is no result. Should check by calling isError() first."))
}
}
/**
* To be successful here, we need to capture xerces parse/validation
* errors and add them to the Diagnostics list in the PState.
*
* @param state the initial parse state.
*/
def validateResult(state: PState) {
if (dp.getValidationMode == ValidationMode.Full) {
try {
validateWithXerces(state)
} catch {
case (spe: SAXParseException) =>
state.reportValidationErrorNoContext(spe.getMessage)
case (se: SAXException) =>
state.reportValidationErrorNoContext(se.getMessage)
case (ve: ValidationException) =>
state.reportValidationErrorNoContext(ve.getMessage)
}
}
}
lazy val isValidationSuccess = {
val res = dp.getValidationMode match {
case ValidationMode.Off => true
case _ => {
val res = resultState.diagnostics.exists { d =>
d match {
case ve: ValidationError => true
case _ => false
}
}
res
}
}
res
}
lazy val result =
if (resultState.status eq Success) {
resultAsScalaXMLElement
} else {
Assert.abort(new IllegalStateException("There is no result. Should check by calling isError() first."))
}
lazy val resultAsScalaXMLElement =
if (resultState.status eq Success) {
val xmlClean = {
val nodeSeq = resultState.infoset.toXML()
val Seq(eNoHidden) = XMLUtils.removeHiddenElements(nodeSeq)
// val eNoAttribs = XMLUtils.removeAttributes(eNoHidden)
// eNoAttribs
eNoHidden
}
xmlClean
} else {
Assert.abort(new IllegalStateException("There is no result. Should check by calling isError() first."))
}
}
class UnparseResult(dp: DataProcessor, ustate: UState)
extends DFDL.UnparseResult
with WithDiagnosticsImpl {
override def resultState = ustate
private def maybeEncodingInfo =
if (Maybe.WithNulls.isDefined(ustate.currentInfosetNode))
One(ustate.currentInfosetNode.asInstanceOf[DIElement].runtimeData.encodingInfo)
else
Nope
private def encodingInfo = maybeEncodingInfo.getOrElse(dp.ssrd.elementRuntimeData.encodingInfo)
def summaryEncoding = encodingInfo.summaryEncoding
override def isScannable = encodingInfo.isScannable
override def encodingName = {
Assert.invariant(encodingInfo.isKnownEncoding)
// we're not supporting runtime-calculated encodings yet so not
// capturing that information (what the actual runtime-value of encoding was
encodingInfo.knownEncodingName
}
}