| /* |
| * 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: |
| * <prefix, uri>. |
| */ |
| 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 |