blob: 4c69e70c0e4898d9098457db54a671fcac43d2c0 [file] [log] [blame]
/*
* reserved comment block
* DO NOT REMOVE OR ALTER!
*/
/*
* Copyright 2005 The Apache Software Foundation.
*
* 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 com.sun.org.apache.xerces.internal.jaxp.validation;
import java.io.IOException;
import java.util.Enumeration;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import com.sun.org.apache.xerces.internal.impl.Constants;
import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
import com.sun.org.apache.xerces.internal.impl.validation.EntityState;
import com.sun.org.apache.xerces.internal.impl.validation.ValidationManager;
import com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator;
import com.sun.org.apache.xerces.internal.impl.xs.util.SimpleLocator;
import com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl;
import com.sun.org.apache.xerces.internal.util.NamespaceSupport;
import com.sun.org.apache.xerces.internal.util.SymbolTable;
import com.sun.org.apache.xerces.internal.util.XMLAttributesImpl;
import com.sun.org.apache.xerces.internal.util.XMLSymbols;
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLParseException;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Entity;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
/**
* <p>A validator helper for <code>DOMSource</code>s.</p>
*
* @author Michael Glavassevich, IBM
* @version $Id: DOMValidatorHelper.java,v 1.9 2010-11-01 04:40:08 joehw Exp $
*/
final class DOMValidatorHelper implements ValidatorHelper, EntityState {
//
// Constants
//
/** Chunk size (1024). */
private static final int CHUNK_SIZE = (1 << 10);
/** Chunk mask (CHUNK_SIZE - 1). */
private static final int CHUNK_MASK = CHUNK_SIZE - 1;
// property identifiers
/** Property identifier: error reporter. */
private static final String ERROR_REPORTER =
Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
/** Property identifier: namespace context. */
private static final String NAMESPACE_CONTEXT =
Constants.XERCES_PROPERTY_PREFIX + Constants.NAMESPACE_CONTEXT_PROPERTY;
/** Property identifier: XML Schema validator. */
private static final String SCHEMA_VALIDATOR =
Constants.XERCES_PROPERTY_PREFIX + Constants.SCHEMA_VALIDATOR_PROPERTY;
/** Property identifier: symbol table. */
private static final String SYMBOL_TABLE =
Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
/** Property identifier: validation manager. */
private static final String VALIDATION_MANAGER =
Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;
//
// Data
//
/** Error reporter. */
private XMLErrorReporter fErrorReporter;
/** The namespace context of this document: stores namespaces in scope. **/
private NamespaceSupport fNamespaceContext;
/** The namespace context of the DOMSource, includes context from ancestor nodes. **/
private DOMNamespaceContext fDOMNamespaceContext = new DOMNamespaceContext();
/** Schema validator. **/
private XMLSchemaValidator fSchemaValidator;
/** Symbol table **/
private SymbolTable fSymbolTable;
/** Validation manager. **/
private ValidationManager fValidationManager;
/** Component manager. **/
private XMLSchemaValidatorComponentManager fComponentManager;
/** Simple Locator. **/
private final SimpleLocator fXMLLocator = new SimpleLocator(null, null, -1, -1, -1);
/** DOM document handler. **/
private DOMDocumentHandler fDOMValidatorHandler;
/** DOM result augmentor. **/
private final DOMResultAugmentor fDOMResultAugmentor = new DOMResultAugmentor(this);
/** DOM result builder. **/
private final DOMResultBuilder fDOMResultBuilder = new DOMResultBuilder();
/** Map for tracking unparsed entities. **/
private NamedNodeMap fEntities = null;
/** Array for holding character data. **/
private char [] fCharBuffer = new char[CHUNK_SIZE];
/** Root node. **/
private Node fRoot;
/** Current element. **/
private Node fCurrentElement;
/** Fields for start element, end element and characters. **/
final QName fElementQName = new QName();
final QName fAttributeQName = new QName();
final XMLAttributesImpl fAttributes = new XMLAttributesImpl();
final XMLString fTempString = new XMLString();
public DOMValidatorHelper(XMLSchemaValidatorComponentManager componentManager) {
fComponentManager = componentManager;
fErrorReporter = (XMLErrorReporter) fComponentManager.getProperty(ERROR_REPORTER);
fNamespaceContext = (NamespaceSupport) fComponentManager.getProperty(NAMESPACE_CONTEXT);
fSchemaValidator = (XMLSchemaValidator) fComponentManager.getProperty(SCHEMA_VALIDATOR);
fSymbolTable = (SymbolTable) fComponentManager.getProperty(SYMBOL_TABLE);
fValidationManager = (ValidationManager) fComponentManager.getProperty(VALIDATION_MANAGER);
}
/*
* ValidatorHelper methods
*/
public void validate(Source source, Result result)
throws SAXException, IOException {
if (result instanceof DOMResult || result == null) {
final DOMSource domSource = (DOMSource) source;
final DOMResult domResult = (DOMResult) result;
Node node = domSource.getNode();
fRoot = node;
if (node != null) {
fComponentManager.reset();
fValidationManager.setEntityState(this);
fDOMNamespaceContext.reset();
String systemId = domSource.getSystemId();
fXMLLocator.setLiteralSystemId(systemId);
fXMLLocator.setExpandedSystemId(systemId);
fErrorReporter.setDocumentLocator(fXMLLocator);
try {
// regardless of what type of node this is, fire start and end document events
setupEntityMap((node.getNodeType() == Node.DOCUMENT_NODE) ? (Document) node : node.getOwnerDocument());
setupDOMResultHandler(domSource, domResult);
fSchemaValidator.startDocument(fXMLLocator, null, fDOMNamespaceContext, null);
validate(node);
fSchemaValidator.endDocument(null);
}
catch (XMLParseException e) {
throw Util.toSAXParseException(e);
}
catch (XNIException e) {
throw Util.toSAXException(e);
}
finally {
// Release references to application objects
fRoot = null;
//fCurrentElement = null; -- keep the reference to support current-element-node property
fEntities = null;
if (fDOMValidatorHandler != null) {
fDOMValidatorHandler.setDOMResult(null);
}
}
}
return;
}
throw new IllegalArgumentException(JAXPValidationMessageFormatter.formatMessage(fComponentManager.getLocale(),
"SourceResultMismatch",
new Object [] {source.getClass().getName(), result.getClass().getName()}));
}
/*
* EntityState methods
*/
public boolean isEntityDeclared(String name) {
return false;
}
public boolean isEntityUnparsed(String name) {
if (fEntities != null) {
Entity entity = (Entity) fEntities.getNamedItem(name);
if (entity != null) {
return (entity.getNotationName() != null);
}
}
return false;
}
/*
* Other methods
*/
/** Traverse the DOM and fire events to the schema validator. */
private void validate(Node node) {
final Node top = node;
// Performs a non-recursive traversal of the DOM. This
// will avoid a stack overflow for DOMs with high depth.
while (node != null) {
beginNode(node);
Node next = node.getFirstChild();
while (next == null) {
finishNode(node);
if (top == node) {
break;
}
next = node.getNextSibling();
if (next == null) {
node = node.getParentNode();
if (node == null || top == node) {
if (node != null) {
finishNode(node);
}
next = null;
break;
}
}
}
node = next;
}
}
/** Do processing for the start of a node. */
private void beginNode(Node node) {
switch (node.getNodeType()) {
case Node.ELEMENT_NODE:
fCurrentElement = node;
// push namespace context
fNamespaceContext.pushContext();
// start element
fillQName(fElementQName, node);
processAttributes(node.getAttributes());
fSchemaValidator.startElement(fElementQName, fAttributes, null);
break;
case Node.TEXT_NODE:
if (fDOMValidatorHandler != null) {
fDOMValidatorHandler.setIgnoringCharacters(true);
sendCharactersToValidator(node.getNodeValue());
fDOMValidatorHandler.setIgnoringCharacters(false);
fDOMValidatorHandler.characters((Text) node);
}
else {
sendCharactersToValidator(node.getNodeValue());
}
break;
case Node.CDATA_SECTION_NODE:
if (fDOMValidatorHandler != null) {
fDOMValidatorHandler.setIgnoringCharacters(true);
fSchemaValidator.startCDATA(null);
sendCharactersToValidator(node.getNodeValue());
fSchemaValidator.endCDATA(null);
fDOMValidatorHandler.setIgnoringCharacters(false);
fDOMValidatorHandler.cdata((CDATASection) node);
}
else {
fSchemaValidator.startCDATA(null);
sendCharactersToValidator(node.getNodeValue());
fSchemaValidator.endCDATA(null);
}
break;
case Node.PROCESSING_INSTRUCTION_NODE:
/**
* The validator does nothing with processing instructions so bypass it.
* Send the ProcessingInstruction node directly to the result builder.
*/
if (fDOMValidatorHandler != null) {
fDOMValidatorHandler.processingInstruction((ProcessingInstruction) node);
}
break;
case Node.COMMENT_NODE:
/**
* The validator does nothing with comments so bypass it.
* Send the Comment node directly to the result builder.
*/
if (fDOMValidatorHandler != null) {
fDOMValidatorHandler.comment((Comment) node);
}
break;
case Node.DOCUMENT_TYPE_NODE:
/**
* Send the DocumentType node directly to the result builder.
*/
if (fDOMValidatorHandler != null) {
fDOMValidatorHandler.doctypeDecl((DocumentType) node);
}
break;
default: // Ignore other node types.
break;
}
}
/** Do processing for the end of a node. */
private void finishNode(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
fCurrentElement = node;
// end element
fillQName(fElementQName, node);
fSchemaValidator.endElement(fElementQName, null);
// pop namespace context
fNamespaceContext.popContext();
}
}
/**
* Extracts NamedNodeMap of entities. We need this to validate
* elements and attributes of type xs:ENTITY, xs:ENTITIES or
* types dervied from them.
*/
private void setupEntityMap(Document doc) {
if (doc != null) {
DocumentType docType = doc.getDoctype();
if (docType != null) {
fEntities = docType.getEntities();
return;
}
}
fEntities = null;
}
/**
* Sets up handler for <code>DOMResult</code>.
*/
private void setupDOMResultHandler(DOMSource source, DOMResult result) throws SAXException {
// If there's no DOMResult, unset the validator handler
if (result == null) {
fDOMValidatorHandler = null;
fSchemaValidator.setDocumentHandler(null);
return;
}
final Node nodeResult = result.getNode();
// If the source node and result node are the same use the DOMResultAugmentor.
// Otherwise use the DOMResultBuilder.
if (source.getNode() == nodeResult) {
fDOMValidatorHandler = fDOMResultAugmentor;
fDOMResultAugmentor.setDOMResult(result);
fSchemaValidator.setDocumentHandler(fDOMResultAugmentor);
return;
}
if (result.getNode() == null) {
try {
DocumentBuilderFactory factory = fComponentManager.getFeature(Constants.ORACLE_FEATURE_SERVICE_MECHANISM) ?
DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
result.setNode(builder.newDocument());
}
catch (ParserConfigurationException e) {
throw new SAXException(e);
}
}
fDOMValidatorHandler = fDOMResultBuilder;
fDOMResultBuilder.setDOMResult(result);
fSchemaValidator.setDocumentHandler(fDOMResultBuilder);
}
private void fillQName(QName toFill, Node node) {
final String prefix = node.getPrefix();
final String localName = node.getLocalName();
final String rawName = node.getNodeName();
final String namespace = node.getNamespaceURI();
toFill.uri = (namespace != null && namespace.length() > 0) ? fSymbolTable.addSymbol(namespace) : null;
toFill.rawname = (rawName != null) ? fSymbolTable.addSymbol(rawName) : XMLSymbols.EMPTY_STRING;
// Is this a DOM level1 document?
if (localName == null) {
int k = rawName.indexOf(':');
if (k > 0) {
toFill.prefix = fSymbolTable.addSymbol(rawName.substring(0, k));
toFill.localpart = fSymbolTable.addSymbol(rawName.substring(k + 1));
}
else {
toFill.prefix = XMLSymbols.EMPTY_STRING;
toFill.localpart = toFill.rawname;
}
}
else {
toFill.prefix = (prefix != null) ? fSymbolTable.addSymbol(prefix) : XMLSymbols.EMPTY_STRING;
toFill.localpart = (localName != null) ? fSymbolTable.addSymbol(localName) : XMLSymbols.EMPTY_STRING;
}
}
private void processAttributes(NamedNodeMap attrMap) {
final int attrCount = attrMap.getLength();
fAttributes.removeAllAttributes();
for (int i = 0; i < attrCount; ++i) {
Attr attr = (Attr) attrMap.item(i);
String value = attr.getValue();
if (value == null) {
value = XMLSymbols.EMPTY_STRING;
}
fillQName(fAttributeQName, attr);
// REVISIT: Assuming all attributes are of type CDATA. The actual type may not matter. -- mrglavas
fAttributes.addAttributeNS(fAttributeQName, XMLSymbols.fCDATASymbol, value);
fAttributes.setSpecified(i, attr.getSpecified());
// REVISIT: Should we be looking at non-namespace attributes
// for additional mappings? Should we detect illegal namespace
// declarations and exclude them from the context? -- mrglavas
if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
// process namespace attribute
if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
fNamespaceContext.declarePrefix(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
}
else {
fNamespaceContext.declarePrefix(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
}
}
}
}
private void sendCharactersToValidator(String str) {
if (str != null) {
final int length = str.length();
final int remainder = length & CHUNK_MASK;
if (remainder > 0) {
str.getChars(0, remainder, fCharBuffer, 0);
fTempString.setValues(fCharBuffer, 0, remainder);
fSchemaValidator.characters(fTempString, null);
}
int i = remainder;
while (i < length) {
str.getChars(i, i += CHUNK_SIZE, fCharBuffer, 0);
fTempString.setValues(fCharBuffer, 0, CHUNK_SIZE);
fSchemaValidator.characters(fTempString, null);
}
}
}
Node getCurrentElement() {
return fCurrentElement;
}
/**
* NamespaceContext for the DOMSource, includes context for ancestor nodes.
*/
final class DOMNamespaceContext implements NamespaceContext {
//
// Data
//
/**
* Namespace binding information. This array is composed of a
* series of tuples containing the namespace binding information:
* &lt;prefix, uri&gt;.
*/
protected String[] fNamespace = new String[16 * 2];
/** The size of the namespace information array. */
protected int fNamespaceSize = 0;
/**
* Flag indicating whether the namespace context
* has been from the root node's ancestors.
*/
protected boolean fDOMContextBuilt = false;
//
// Methods
//
public void pushContext() {
fNamespaceContext.pushContext();
}
public void popContext() {
fNamespaceContext.popContext();
}
public boolean declarePrefix(String prefix, String uri) {
return fNamespaceContext.declarePrefix(prefix, uri);
}
public String getURI(String prefix) {
String uri = fNamespaceContext.getURI(prefix);
if (uri == null) {
if (!fDOMContextBuilt) {
fillNamespaceContext();
fDOMContextBuilt = true;
}
if (fNamespaceSize > 0 &&
!fNamespaceContext.containsPrefix(prefix)) {
uri = getURI0(prefix);
}
}
return uri;
}
public String getPrefix(String uri) {
return fNamespaceContext.getPrefix(uri);
}
public int getDeclaredPrefixCount() {
return fNamespaceContext.getDeclaredPrefixCount();
}
public String getDeclaredPrefixAt(int index) {
return fNamespaceContext.getDeclaredPrefixAt(index);
}
public Enumeration getAllPrefixes() {
return fNamespaceContext.getAllPrefixes();
}
public void reset() {
fDOMContextBuilt = false;
fNamespaceSize = 0;
}
private void fillNamespaceContext() {
if (fRoot != null) {
Node currentNode = fRoot.getParentNode();
while (currentNode != null) {
if (Node.ELEMENT_NODE == currentNode.getNodeType()) {
NamedNodeMap attributes = currentNode.getAttributes();
final int attrCount = attributes.getLength();
for (int i = 0; i < attrCount; ++i) {
Attr attr = (Attr) attributes.item(i);
String value = attr.getValue();
if (value == null) {
value = XMLSymbols.EMPTY_STRING;
}
fillQName(fAttributeQName, attr);
// REVISIT: Should we be looking at non-namespace attributes
// for additional mappings? Should we detect illegal namespace
// declarations and exclude them from the context? -- mrglavas
if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
// process namespace attribute
if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
declarePrefix0(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
}
else {
declarePrefix0(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
}
}
}
}
currentNode = currentNode.getParentNode();
}
}
}
private void declarePrefix0(String prefix, String uri) {
// resize array, if needed
if (fNamespaceSize == fNamespace.length) {
String[] namespacearray = new String[fNamespaceSize * 2];
System.arraycopy(fNamespace, 0, namespacearray, 0, fNamespaceSize);
fNamespace = namespacearray;
}
// bind prefix to uri in current context
fNamespace[fNamespaceSize++] = prefix;
fNamespace[fNamespaceSize++] = uri;
}
private String getURI0(String prefix) {
// find prefix in the DOM context
for (int i = 0; i < fNamespaceSize; i += 2) {
if (fNamespace[i] == prefix) {
return fNamespace[i + 1];
}
}
// prefix not found
return null;
}
}
} // DOMValidatorHelper