/*
* 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.tuscany.sca.contribution.processor;
import java.util.logging.Logger;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.StreamReaderDelegate;
import javax.xml.validation.Schema;
import javax.xml.validation.ValidatorHandler;
import org.apache.tuscany.sca.monitor.Monitor;
import org.apache.tuscany.sca.monitor.Problem;
import org.apache.tuscany.sca.monitor.Problem.Severity;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.AttributesImpl;
/**
*
* A validating XMLStreamReader that reports XMLSchema validation errors.
*
* @version $Rev: 833494 $ $Date: 2009-11-06 17:47:45 +0000 (Fri, 06 Nov 2009) $
*/
class ValidatingXMLStreamReader extends StreamReaderDelegate implements XMLStreamReader {
private static final Logger logger = Logger.getLogger(ValidatingXMLStreamReader.class.getName());
private ValidatorHandler handler;
private Schema schema;
private Monitor monitor;
/**
* Constructs a new ValidatingXMLStreamReader.
*
* @param reader
* @param schema
* @throws XMLStreamException
*/
ValidatingXMLStreamReader(XMLStreamReader reader, Schema schema, Monitor monitor) throws XMLStreamException {
super(reader);
this.monitor = monitor;
this.schema = schema;
}
void setMonitor(Monitor monitor) {
this.monitor = monitor;
}
private synchronized ValidatorHandler getHandler() throws XMLStreamException {
if (schema == null || handler!=null) {
return handler;
}
handler = schema.newValidatorHandler();
handler.setDocumentLocator(new LocatorAdapter());
try {
handler.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
} catch (SAXException e) {
XMLStreamException xse = new XMLStreamException(e);
error("XMLStreamException", handler, xse);
throw xse;
}
// These validation errors are just warnings for us as we want to support
// running from an XML document with XSD validation errors, as long as we can
// get the metadata we need from the document
handler.setErrorHandler(new ErrorHandler() {
private String getMessage(SAXParseException e) {
return "XMLSchema validation problem in: " + getArtifactName( e.getSystemId() ) + ", line: " + e.getLineNumber() + ", column: " + e.getColumnNumber() + "\n" + e.getMessage();
}
public void error(SAXParseException exception) throws SAXException {
if (ValidatingXMLStreamReader.this.monitor == null)
logger.warning(getMessage(exception));
else
ValidatingXMLStreamReader.this.error("SchemaError", ValidatingXMLStreamReader.this.getClass(), getArtifactName( exception.getSystemId() ),
exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage());
}
public void fatalError(SAXParseException exception) throws SAXException {
if (ValidatingXMLStreamReader.this.monitor == null)
logger.warning(getMessage(exception));
else
ValidatingXMLStreamReader.this.error("SchemaFatalError", ValidatingXMLStreamReader.this.getClass(), getArtifactName( exception.getSystemId() ),
exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage());
}
public void warning(SAXParseException exception) throws SAXException {
if (ValidatingXMLStreamReader.this.monitor == null)
logger.warning(getMessage(exception));
else
ValidatingXMLStreamReader.this.warning("SchemaWarning", ValidatingXMLStreamReader.this.getClass(), getArtifactName( exception.getSystemId() ),
exception.getLineNumber(), exception.getColumnNumber(), exception.getMessage());
}
private String getArtifactName( String input ) {
String artifactName = null;
if( ValidatingXMLStreamReader.this.monitor != null ) {
artifactName = ValidatingXMLStreamReader.this.monitor.getArtifactName();
}
if (artifactName == null){
artifactName = input;
}
return artifactName;
}
});
return handler;
}
/**
* Report a warning.
*
* @param problems
* @param message
* @param model
*/
private void warning(String message, Object model, Object... messageParameters) {
if (monitor != null) {
Problem problem = monitor.createProblem(this.getClass().getName(), "contribution-validation-messages", Severity.WARNING, model, message, (Object[])messageParameters);
monitor.problem(problem);
}
}
/**
* Report a error.
*
* @param problems
* @param message
* @param model
*/
private void error(String message, Object model, Object... messageParameters) {
if (monitor != null) {
Problem problem = monitor.createProblem(this.getClass().getName(), "contribution-validation-messages", Severity.ERROR, model, message, (Object[])messageParameters);
monitor.problem(problem);
}
}
@Override
public int next() throws XMLStreamException {
if (getHandler() == null) {
return super.next();
}
int event = super.getEventType();
try {
if (event == START_DOCUMENT) {
// We need to trigger the startDocument()
handler.startDocument();
}
event = super.next();
validate(event);
} catch (SAXException e) {
XMLStreamException xse = new XMLStreamException(e.getMessage(), e);
error("XMLStreamException", handler, xse);
throw xse;
}
return event;
}
private void validate(int event) throws SAXException {
switch (event) {
case START_DOCUMENT:
handler.startDocument();
break;
case START_ELEMENT:
handleStartElement();
break;
case PROCESSING_INSTRUCTION:
handler.processingInstruction(super.getPITarget(), super.getPIData());
break;
case CHARACTERS:
case CDATA:
case SPACE:
case ENTITY_REFERENCE:
handler.characters(super.getTextCharacters(), super.getTextStart(), super.getTextLength());
break;
case END_ELEMENT:
handleEndElement();
break;
case END_DOCUMENT:
handler.endDocument();
break;
}
}
@Override
public int nextTag() throws XMLStreamException {
if (getHandler() == null) {
return super.nextTag();
}
while (true) {
int event = super.getEventType();
try {
if (event == START_DOCUMENT) {
// We need to trigger the startDocument()
handler.startDocument();
}
event = super.next();
validate(event);
} catch (SAXException e) {
XMLStreamException xse = new XMLStreamException(e);
error("XMLStreamException", handler, xse);
throw xse;
}
if ((event == CHARACTERS && isWhiteSpace()) // skip whitespace
|| (event == CDATA && isWhiteSpace())
// skip whitespace
|| event == SPACE
|| event == PROCESSING_INSTRUCTION
|| event == COMMENT) {
continue;
}
if (event != START_ELEMENT && event != END_ELEMENT) {
throw new XMLStreamException("expected start or end tag", getLocation());
}
return event;
}
}
@Override
public String getElementText() throws XMLStreamException {
if (getHandler() == null) {
return super.getElementText();
}
if (getEventType() != START_ELEMENT) {
return super.getElementText();
}
StringBuffer text = new StringBuffer();
for (;;) {
int event = next();
switch (event) {
case END_ELEMENT:
return text.toString();
case COMMENT:
case PROCESSING_INSTRUCTION:
continue;
case CHARACTERS:
case CDATA:
case SPACE:
case ENTITY_REFERENCE:
text.append(getText());
break;
default:
break;
}
}
}
@Override
public NamespaceContext getNamespaceContext(){
return super.getNamespaceContext();
}
/**
* Handle a start element event.
*
* @throws SAXException
*/
private void handleStartElement() throws SAXException {
// send startPrefixMapping events immediately before startElement event
int nsCount = super.getNamespaceCount();
for (int i = 0; i < nsCount; i++) {
String prefix = super.getNamespacePrefix(i);
if (prefix == null) { // true for default namespace
prefix = "";
}
handler.startPrefixMapping(prefix, super.getNamespaceURI(i));
}
// fire startElement
QName qname = super.getName();
String prefix = qname.getPrefix();
String rawname;
if (prefix == null || prefix.length() == 0) {
rawname = qname.getLocalPart();
} else {
rawname = prefix + ':' + qname.getLocalPart();
}
Attributes attrs = getAttributes();
handler.startElement(qname.getNamespaceURI(), qname.getLocalPart(), rawname, attrs);
}
/**
* Handle an endElement event.
*
* @throws SAXException
*/
private void handleEndElement() throws SAXException {
// fire endElement
QName qname = super.getName();
handler.endElement(qname.getNamespaceURI(), qname.getLocalPart(), qname.toString());
// send endPrefixMapping events immediately after endElement event
// we send them in the opposite order to that returned but this is not
// actually required by SAX
int nsCount = super.getNamespaceCount();
for (int i = nsCount - 1; i >= 0; i--) {
String prefix = super.getNamespacePrefix(i);
if (prefix == null) { // true for default namespace
prefix = "";
}
handler.endPrefixMapping(prefix);
}
}
/**
* Get the attributes associated with the current START_ELEMENT event.
*
* @return the StAX attributes converted to org.xml.sax.Attributes
*/
private Attributes getAttributes() {
AttributesImpl attrs = new AttributesImpl();
// add namespace declarations
for (int i = 0; i < super.getNamespaceCount(); i++) {
String prefix = super.getNamespacePrefix(i);
String uri = super.getNamespaceURI(i);
if (prefix == null) {
attrs.addAttribute("", "", "xmlns", "CDATA", uri);
} else {
attrs.addAttribute("", "", "xmlns:" + prefix, "CDATA", uri);
}
}
// Regular attributes
for (int i = 0; i < super.getAttributeCount(); i++) {
String uri = super.getAttributeNamespace(i);
if (uri == null) {
uri = "";
}
String localName = super.getAttributeLocalName(i);
String prefix = super.getAttributePrefix(i);
String qname;
if (prefix == null || prefix.length() == 0) {
qname = localName;
} else {
qname = prefix + ':' + localName;
}
String type = super.getAttributeType(i);
String value = super.getAttributeValue(i);
attrs.addAttribute(uri, localName, qname, type, value);
}
return attrs;
}
/**
* Adapter for mapping Locator information.
*/
private final class LocatorAdapter implements Locator {
private LocatorAdapter() {
}
public int getColumnNumber() {
Location location = getLocation();
return location == null ? 0 : location.getColumnNumber();
}
public int getLineNumber() {
Location location = getLocation();
return location == null ? 0 : location.getLineNumber();
}
public String getPublicId() {
Location location = getLocation();
return location == null ? "" : location.getPublicId();
}
public String getSystemId() {
Location location = getLocation();
return location == null ? "" : location.getSystemId();
}
}
}