org.fusesource.camel.tooling.util.RouteXml.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of camel-tooling-util Show documentation
Show all versions of camel-tooling-util Show documentation
FuseSource :: Camel Tooling Util
/*
* Copyright (C) FuseSource, Inc.
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.camel.tooling.util
import java.{util => ju}
import java.net.URL
import javax.xml.bind.{Marshaller, JAXBContext}
import javax.xml.parsers.{DocumentBuilder, DocumentBuilderFactory}
import collection.Map
import collection.immutable.TreeSet
import collection.mutable.HashMap
import collection.mutable.ListBuffer
import collection.JavaConversions._
import org.apache.camel.CamelContext
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.impl.DefaultCamelContext
import org.apache.camel.model.{RoutesDefinition, RouteDefinition, Constants}
import org.apache.camel.blueprint.{CamelEndpointFactoryBean => BlueprintCamelEndpointFactoryBean}
import org.apache.camel.spring.{CamelEndpointFactoryBean, CamelContextFactoryBean}
import org.fusesource.scalate.util.{ClassLoaders, Logging, IOUtil}
import de.pdark.decentxml._
import org.xml.sax.{SAXParseException, ErrorHandler, InputSource}
import javax.xml.XMLConstants
import javax.xml.transform.{Source, TransformerFactory}
import javax.xml.transform.stream.{StreamSource, StreamResult}
import javax.xml.validation.{Schema, SchemaFactory}
import java.io._
import parser.PatchedXMLParser
case class XsdDetails(path: String, uri: String, aClass: Class[_]) {
def classLoader = aClass.getClassLoader
}
trait SchemaFinder {
def findSchema(xsd: XsdDetails): URL
}
object CamelNamespaces extends Logging {
val springNS = "http://camel.apache.org/schema/spring"
val blueprintNS = "http://camel.apache.org/schema/blueprint"
val camelNamespaces = Set(springNS, blueprintNS)
val springNamespace = new Namespace("", "http://www.springframework.org/schema/beans")
val droolsNamespace = new Namespace("drools", "http://drools.org/schema/drools-spring")
private var _schema: Schema = _
lazy val elementsWithDescription: Set[String] = {
val file = "camelDescriptionElements.txt"
var text = findResource(file) match {
case Some(url) =>
IOUtil.loadText(url.openStream())
case _ =>
warn("Could not find file " + file + " on the class path")
""
}
text += " camelContext"
val arr = text.split(' ')
Set(arr:_*)
}
def nodesByNamespace(doc: Document, namespaceUri: String, localName: String): ju.List[Node] = {
val filter = new NodeFilter[Node]() {
override def matches(n: Node) = n match {
case e: Element =>
val ns = e.getNamespace
// TODO this doesn't work with empty prefixes!
if (!e.getName().equals(localName)) false else {
val uri = getNamespaceURI(e)
ns != null // && namespaceUri.equals(uri)
}
case _ => false
}
}
return findNodes(doc, filter)
}
def findNodes(node: NodeWithChildren, filter: NodeFilter[Node]): ju.List[Node] = {
val answer = node.getNodes(filter)
val children = node.getNodes()
for (child <- children) {
child match {
case childElem: Element =>
val childMatched = findNodes(childElem, filter)
answer.addAll(childMatched)
case _ =>
}
}
return answer
}
def nodeWithNamespacesToText(parseNode: Node, namespacesNode: Element): String = {
// we need to add any namespace prefixes defined in the root directory
val copy = parseNode.copy()
copy match {
case e: Element =>
parseNode match {
case pe: Element =>
moveCommentsIntoDescriptionElements(e, pe)
addParentNamespaces(e, namespacesNode.getParent())
case _ =>
}
case _ =>
}
val xmlText = xmlToText(copy)
xmlText
}
protected def moveCommentsIntoDescriptionElements(e: Element, root: Element): Unit = {
// lets iterate through finding all comments which are then added to a description node
var idx = 0
for (node <- e.getNodes().toArray) {
node match {
case c: Comment =>
val token = c.getToken()
if (token != null) {
var text = token.getText.stripPrefix("").trim()
var descr = findOrCreateDescriptionOnNextElement(e, idx, root)
if (descr == null) {
// lets move the comment node to before the root element...
warn("No description node found")
e.removeNode(c)
val grandParent = root.getParent
if (grandParent != null) {
grandParent.addNode(grandParent.nodeIndexOf(root), c)
} else {
warn("Cannot save the comment '" + text + "' as there's no parent in the DOM" )
}
} else {
if (descr.getNodes().size() > 0) {
text = "\n" + text
}
descr.addNode(new Text(text))
}
}
case child: Element =>
moveCommentsIntoDescriptionElements(child, root)
case _ =>
}
idx += 1
}
}
protected def findOrCreateDescriptionOnNextElement(element: Element, commentIndex: Int, root: Parent): Element = {
// lets find the next peer element node and if it can contain a description lets use that
val array = element.getNodes.toArray()
for (i <- (commentIndex + 1).to(array.length - 1)) {
array(i) match {
case e: Element =>
if (elementsWithDescription.contains(e.getName)) return findOrCreateDescrptionElement(e, root)
case _ =>
}
}
return findOrCreateDescrptionElement(element, root)
}
protected def findOrCreateDescrptionElement(element: Element, root: Parent): Element = {
for (node <- element.getNodes()) {
node match {
case child: Element =>
if (child.getName == "description")
return child
case _ =>
}
}
val parent = element.getParentElement
if (element == root || parent == null || parent == root) {
null
} else if (elementsWithDescription.contains(element.getName)) {
// lets check for a namespace prefix
val ebn = element.getBeginName
val idx = ebn.indexOf(":")
val name = if (idx > 0) ebn.substring(0, idx + 1) + "description" else "description"
val description = new Element(name, element.getNamespace)
element.addNode(0, description)
description
} else {
findOrCreateDescrptionElement(parent, root)
}
}
def xmlToText(node: Node): String = {
val buffer = new StringWriter
val writer = new XMLWriter(buffer)
node.toXML(writer)
writer.close()
/*
val transformer = transformerFactory.newTransformer
transformer.transform(new DOMSource(doc), new StreamResult(buffer))
*/
buffer.toString
}
def getNamespaceURI(node: Node): String = {
return node match {
case e: Element =>
val ns = e.getNamespace()
if (ns != null) {
val uri = ns.getURI
if (uri == null || uri.length() == 0) {
val uriAttr = if (ns.getPrefix == "") e.getAttributeValue("xmlns") else null
if (uriAttr != null) {
uriAttr
}
else getNamespaceURI(e.getParent)
} else {
uri
}
} else null
case _ => return null
}
}
def addParentNamespaces(element: Element, parent: Parent): Unit = {
parent match {
case parentE: Element =>
for (attr <- parentE.getAttributes) {
val name = attr.getName
if (name.startsWith("xmlns") && element.getAttributeValue(name) == null) {
element.setAttribute(name, attr.getValue)
}
}
addParentNamespaces(element, parentE.getParent)
case _ =>
}
}
def getOwnerDocument(node: Node): Document = {
node match {
case e: Element => e.getDocument
case d: Document => d
case _ => null
}
}
def replaceChild(parent: Parent, newChild: Node, oldNode: Node): Unit = {
val idx = parent.nodeIndexOf(oldNode)
if (idx < 0) {
parent.addNode(newChild)
} else {
parent.removeNode(idx)
parent.addNode(idx, newChild)
}
}
// TODO zap when Scalate 1.4 released....
def findResource(name: String, classLoaders: Traversable[ClassLoader] = ClassLoaders.defaultClassLoaders): Option[URL] = {
def tryLoadClass(classLoader: ClassLoader) = {
try {
classLoader.getResource(name)
}
catch {
case e => null
}
}
classLoaders.map(tryLoadClass).find(_ != null)
}
def loadSchemasWith(finder: SchemaFinder): Unit = {
def fn(xsd: XsdDetails): URL = {
finder.findSchema(xsd)
}
loadSchemas(fn)
}
def loadSchemas(fn: XsdDetails => URL): Unit = {
val factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
val xsds = List(XsdDetails("camel-spring.xsd", "http://camel.apache.org/schema/spring/camel-spring.xsd", classOf[CamelEndpointFactoryBean]),
XsdDetails("camel-blueprint.xsd", "http://camel.apache.org/schema/blueprint/camel-blueprint.xsd", classOf[BlueprintCamelEndpointFactoryBean]))
val sources: Array[Source] = xsds.map {
xsd =>
import xsd._
val url = fn(xsd)
if (url != null) {
new StreamSource(url.openStream(), uri)
} else {
println("Warning could not find local resource " + path + " on classpath")
new StreamSource(uri)
}
}.toArray
_schema = factory.newSchema(sources)
}
def camelSchemas: Schema = {
if (_schema == null) {
loadSchemas(xsd => xsd.classLoader.getResource(xsd.path))
}
_schema
}
}
import CamelNamespaces._
case class XmlModel(contextElement: CamelContextFactoryBean,
doc: Document,
beans: Map[String, String],
node: Option[Node],
ns: String = CamelNamespaces.springNS,
justRoutes: Boolean = false) extends Logging {
/**
* Returns the root element to be marshalled as XML
*/
def marshalRootElement: AnyRef = {
if (justRoutes) {
val routes = new RoutesDefinition()
routes.setRoutes(contextElement.getRoutes)
routes
} else {
contextElement
}
}
val hasMissingId = routeDefinitions.exists(_.getId == null)
def getRouteDefinitionList: ju.List[RouteDefinition] = contextElement.getRoutes
def routeDefinitions: List[RouteDefinition] = getRouteDefinitionList.toList
/**
* Creates a new model using the given context
*/
def update(newContext: CamelContextFactoryBean) = copy(contextElement = newContext)
def camelContext: CamelContext = createContext(contextElement.getRoutes)
/**
* Returns the endpoint URIs used in the context
*/
def endpointUris: Set[String] = {
try {
// we must use reflection for now until Camel supports the getEndpoints() method
// https://issues.apache.org/jira/browse/CAMEL-3644
val field = contextElement.getClass.getDeclaredField("endpoints")
field.setAccessible(true)
val endpoints = field.get(contextElement).asInstanceOf[ju.List[CamelEndpointFactoryBean]]
val uris = ListBuffer[String]()
if (endpoints != null) {
uris ++= endpoints.map(_.getUri)
}
// lets detect any drools endpoints...
val sessions = nodesByNamespace(doc, droolsNamespace.getURI, "ksession")
if (sessions != null) {
for (session <- sessions) {
session match {
case e: Element =>
val node = e.getAttributeValue("node")
val sid = e.getAttributeValue("id")
if (node != null && node.length > 0 && sid != null && sid.length > 0) {
val du = "drools:" + node + "/" + sid
if (!uris.exists{_.startsWith(du)}) {
uris += du
}
}
case _ =>
}
}
}
TreeSet(uris: _*)
}
catch {
case e =>
println("Caught: " + e)
e.printStackTrace()
Set()
}
}
def endpointUriSet: ju.Set[String] = endpointUris
/**
* Returns a Java API for accessing the bean map
*/
def beanMap: ju.Map[String, String] = beans
def validate: ValidationHandler = {
val v = new ValidationHandler()
v.validate(doc)
v
}
protected def createContext(routes: ju.Collection[RouteDefinition]): CamelContext = {
val context = new DefaultCamelContext
context.addRouteDefinitions(routes)
context
}
}
/**
* Helper class for loading and saving XML for use at design time
*/
class RouteXml extends Logging {
private var _jaxbContext: JAXBContext = _
var classLoader: ClassLoader = classOf[CamelContextFactoryBean].getClassLoader
var validating = false
protected lazy val transformerFactory = TransformerFactory.newInstance
protected lazy val documentBuilder = createDocumentBuilder
protected def documentBuilder(handler: ErrorHandler) = {
val db = createDocumentBuilder
db.setErrorHandler(handler)
db
}
def jaxbContext: JAXBContext = {
if (_jaxbContext == null) {
val packageName = Constants.JAXB_CONTEXT_PACKAGES + ":org.apache.camel.spring"
_jaxbContext = JAXBContext.newInstance(packageName, classLoader)
}
_jaxbContext
}
def jaxbContext_=(value: JAXBContext): Unit = {
_jaxbContext = value
}
protected def createExemplarDoc() = {
val exemplar = "org/fusesource/camel/tooling/exemplar.xml"
findResource(exemplar) match {
case Some(url) =>
parse(new XMLIOSource(url))
case _ =>
warn("Could not find file " + exemplar + " on the class path")
val d = new de.pdark.decentxml.Document()
d.addNode(new de.pdark.decentxml.Element("beans", springNamespace))
d
}
}
protected def parse(source: XMLSource): de.pdark.decentxml.Document = {
/*
val parser = new DOMParser()
parser.setFeature("http://apache.org/xml/features/dom/include-ignorable-whitespace", false)
parser.parse(inputSource)
return parser.getDocument
*/
val parser = new PatchedXMLParser()
return parser.parse(source)
}
def unmarshal(file: File): XmlModel = {
val doc = if (file.exists) {
parse(new XMLIOSource(file))
/*
// lets find the header stuff
val root = doc.getRootElement
if (root != null) {
val name = root.getNodeName()
println("====== node name is: " + name)
val text = IOUtil.loadTextFile(file)
val idx = text.indexOf("<" + name)
if (idx > 0) {
header = text.substring(0, idx)
println("header: " + header)
}
}
*/
} else {
createExemplarDoc()
}
val answer = unmarshal(doc, "XML File " + file)
answer
}
def unmarshal(text: String): XmlModel = {
val doc = if (text.trim().length > 0) {
//documentBuilder.parse(new InputSource(new StringReader(text)))
parse(new XMLStringSource(text))
} else {
createExemplarDoc()
}
unmarshal(doc, "Text")
}
def unmarshal(doc: Document): XmlModel = unmarshal(doc, "XML document " + doc)
def unmarshal(doc: Document, message: => String): XmlModel = {
val unmarshaller = jaxbContext.createUnmarshaller
//("bean", springNamespace)
var beans = new HashMap[String, String]()
// lets pull out the spring beans...
val beanElems = nodesByNamespace(doc, springNS, "bean")
for (n <- beanElems) {
n match {
case e: Element =>
val id = e.getAttributeValue("id")
val cn = e.getAttributeValue("class")
if (id != null && cn != null) {
beans += (id -> cn)
}
case _ =>
}
}
// now lets pull out the jaxb routes...
val search = List((springNS, "camelContext"), (springNS, "routes"), (blueprintNS, "camelContext"), (blueprintNS, "routes"))
val found = search.flatMap {
case (ns, en) =>
val nodes = nodesByNamespace(doc, ns, en)
nodes.size() match {
case 0 =>
None
/*
val root = doc.getRootElement
// for root elements being the same...
if (en == root.getLocalName && ns == root.getNamespaceURI)
Some(root)
else
*/
case n =>
if (n > 1) {
warn(message + " contains " + n + " elements. Only the first wone will be used")
}
val node = nodes(0)
if (node != null) Some(node) else None
}
}
found match {
case node :: _ =>
val ns = getNamespaceURI(node)
val parseNode = if (ns != springNS) {
cloneAndReplaceNamespace(node, ns, springNS)
} else {
node
}
var justRoutes = false
val xmlText = nodeWithNamespacesToText(parseNode, node.asInstanceOf[Element])
val context = unmarshaller.unmarshal(new StringReader(xmlText)) match {
case sc: CamelContextFactoryBean =>
debug("Found a valid CamelContextFactoryBean! " + sc)
sc
case rd: RoutesDefinition =>
justRoutes = true
val sc = new CamelContextFactoryBean()
sc.setRoutes(rd.getRoutes)
sc
case n =>
warn("Unmarshalled not a CamelContext: " + n)
new CamelContextFactoryBean
}
XmlModel(context, doc, beans, Some(node), ns, justRoutes)
case n =>
info(message + " does not contain a CamelContext. Maybe the XML namespace is not spring: '" + springNS + "' or blueprint: '" + blueprintNS + "'?")
// lets create a new collection
XmlModel(new CamelContextFactoryBean(), doc, beans, None)
}
}
protected def cloneAndReplaceNamespace(node: Node, oldNS: String, newNS: String): Node = {
val answer = node.copy()
replaceNamespace(answer, oldNS, newNS)
}
protected def replaceNamespace(node: Node, oldNS: String, newNS: String): Node = {
var newNode = node
node match {
case e: Element =>
val ns = getNamespaceURI(e)
if (ns == oldNS) {
val ns = e.getNamespace()
if (ns != null) {
if (ns.getURI == oldNS) {
e.setNamespace(new Namespace(ns.getPrefix(), newNS))
}
}
for (attr <- e.getAttributes) {
if (attr.getName.startsWith("xmlns")) {
val value = attr.getValue
if (value != null && value == oldNS) {
attr.setValue(newNS)
}
}
}
}
case _ =>
}
newNode match {
case nodeWithChildren: NodeWithChildren =>
var list = nodeWithChildren.getNodes
for (n <- list) {
replaceNamespace(n, oldNS, newNS)
}
case _ =>
}
newNode
}
def marshal(file: File, context: CamelContextFactoryBean): Unit = marshal(file) {
m =>
m.update(context)
}
def marshal(file: File, context: CamelContext): Unit = marshal(file) {
m =>
copyRoutesToElement(context, m.contextElement)
m
}
def copyRoutesToElement(routeDefinitionList: ju.List[RouteDefinition], contextElement: CamelContextFactoryBean): Unit = {
val routes = contextElement.getRoutes
routes.clear
routes.addAll(routeDefinitionList)
}
def copyRoutesToElement(context: CamelContext, contextElement: CamelContextFactoryBean): Unit = {
context match {
case mcc: ModelCamelContext => copyRoutesToElement(mcc.getRouteDefinitions, contextElement);
case _=> println("Invalid camel context!") }
}
/**
* Loads the given file then updates the route definitions from the given list then stores the file again
*/
def marshal(file: File, routeDefinitionList: ju.List[RouteDefinition]): Unit = {
marshal(file) {
model: XmlModel =>
copyRoutesToElement(routeDefinitionList, model.contextElement)
model
}
}
def marshal(file: File)(fn: XmlModel => XmlModel): Unit = {
// lets load the file first in case its been edited since we last loaded it
val model = unmarshal(file)
marshal(file, fn(model))
}
def marshalToText(text: String, routeDefinitionList: ju.List[RouteDefinition]): String = {
marshalToText(text) {
model: XmlModel =>
copyRoutesToElement(routeDefinitionList, model.contextElement)
model
}
}
def marshalToText(text: String)(fn: XmlModel => XmlModel): String = {
val model = unmarshal(text)
marshalToText(fn(model))
}
def marshal(file: File, model: XmlModel): Unit = {
marshalToDoc(model)
writeXml(model.doc, file)
}
def marshalToText(model: XmlModel): String = {
marshalToDoc(model)
xmlToText(model.doc)
}
protected def replaceCamelElement(docElem: Element, camelElem: Node, oldNode: Node) {
replaceChild(docElem, camelElem, oldNode)
// lets replace the camel namespace, copying any namespace from the old node as well
camelElem match {
case camelElement: Element =>
oldNode match {
case oldElement: Element =>
for (attr <- oldElement.getAttributes) {
if (attr.getName.startsWith("xmlns:")) {
camelElement.setAttribute(attr.getName, attr.getValue)
}
}
case _ =>
}
case _ =>
}
}
/**
* Marshals the model to XML and updates the model's doc property to contain the
* new marshalled model
*/
def marshalToDoc(model: XmlModel): Unit = {
val marshaller = jaxbContext.createMarshaller
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, java.lang.Boolean.TRUE)
try {
marshaller.setProperty("com.sun.xml.bind.indentString", " ")
}
catch {
case e => debug("Property not supported: " + e)
}
val value = model.marshalRootElement
val doc = model.doc
val docElem = doc.getRootElement
// JAXB only seems to do nice whitespace/namespace stuff when writing to stream
// rather than DOM directly
// marshaller.marshal(value, docElem)
val buffer = new StringWriter
marshaller.marshal(value, buffer)
// now lets parse the XML and insert the root element into the doc
var xml = buffer.toString
if (model.ns != springNS) {
xml = xml.replaceAll(springNS, model.ns)
}
val camelDoc = parse(new XMLStringSource(xml))
var element: Node = camelDoc.getRootElement
// TODO
//val camelElem = doc.importNode(element, true)
val camelElem = element
if (model.justRoutes) {
replaceChild(doc, camelElem, docElem)
} else {
model.node match {
case Some(n) =>
replaceCamelElement(docElem, camelElem, n)
case _ =>
docElem.addNode(camelElem)
}
}
}
def writeXml(doc: Document, file: File): Unit = {
val parentDir = file.getParentFile
if (parentDir != null) parentDir.mkdirs
val writer = new XMLWriter(new FileWriter(file))
doc.toXML(writer)
writer.close()
/*
val format = new OutputFormat(doc)
format.setPreserveSpace(true)
val out = new FileOutputStream(file)
try {
val serial = new XMLSerializer(out, format);
serial.asDOMSerializer()
serial.serialize(doc)
out.flush()
}
finally {
out.close()
}
*/
/*
val transformer = transformerFactory.newTransformer
transformer.transform(new DOMSource(doc), new StreamResult(file))
*/
}
protected def createDocumentBuilder: DocumentBuilder = {
val JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"
val JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource"
val W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"
val dbf = DocumentBuilderFactory.newInstance
if (validating) {
try {
dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
dbf.setAttribute(JAXP_SCHEMA_SOURCE, camelSchemas)
dbf.setValidating(validating)
}
catch {
case e => // ignore
}
}
dbf.setExpandEntityReferences(false)
dbf.setIgnoringComments(false)
dbf.setIgnoringElementContentWhitespace(false)
dbf.setCoalescing(false)
dbf.setNamespaceAware(true)
dbf.newDocumentBuilder
}
}
case class ValidationException(errors: List[SAXParseException], fatalErrors: List[SAXParseException], warnings: List[SAXParseException])
extends Exception("Validation failed: " + ((errors ++ fatalErrors).map(_.getMessage).mkString(", "))) {
def userMessage = {
var text = (errors ++ fatalErrors).map(_.getMessage).mkString(", ")
val idx = text.indexOf(":")
if (text.startsWith("cvc-complex-type") && idx > 0) {
text = text.drop(idx + 1).trim
}
for (uri <- camelNamespaces) {
text = text.replaceAllLiterally("\"" + uri + "\":", "")
}
text
}
}
class ValidationHandler extends ErrorHandler {
var warnings = List[SAXParseException]()
var errors = List[SAXParseException]()
var fatalErrors = List[SAXParseException]()
def warning(e: SAXParseException): Unit = {
warnings :+= e
}
def fatalError(e: SAXParseException): Unit = {
fatalErrors :+= e
}
def error(e: SAXParseException): Unit = {
errors :+= e
}
def userMessage: String = {
var text = (errors ++ fatalErrors).map(_.getMessage).mkString(", ")
val idx = text.indexOf(":")
if (text.startsWith("cvc-complex-type") && idx > 0) {
text = text.drop(idx + 1).trim
}
for (uri <- camelNamespaces) {
text = text.replaceAllLiterally("\"" + uri + "\":", "")
}
text
}
def validate(doc: Document): Unit = {
val validator = camelSchemas.newValidator()
validator.setErrorHandler(this)
def validate(e: Element): Unit = {
val uri = getNamespaceURI(e)
if (uri != null && camelNamespaces.contains(uri)) {
val text = nodeWithNamespacesToText(e, e)
validator.validate(new StreamSource(new StringReader(text)))
} else {
val children = e.getNodes
for (node <- e.getNodes()) {
node match {
case c: Element => validate(c)
case _ =>
}
}
}
}
validate(doc.getRootElement)
}
def hasErrors: Boolean = {
!errors.isEmpty || !fatalErrors.isEmpty
}
def checkForErrors: Unit = {
if (hasErrors) {
throw new ValidationException(errors, fatalErrors, warnings)
}
}
}