001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.xerces.jaxp.validation;
019:
020: import java.io.IOException;
021: import java.util.Enumeration;
022: import java.util.Locale;
023:
024: import javax.xml.parsers.DocumentBuilder;
025: import javax.xml.parsers.DocumentBuilderFactory;
026: import javax.xml.parsers.ParserConfigurationException;
027: import javax.xml.transform.Result;
028: import javax.xml.transform.Source;
029: import javax.xml.transform.dom.DOMResult;
030: import javax.xml.transform.dom.DOMSource;
031:
032: import org.apache.xerces.dom.NodeImpl;
033: import org.apache.xerces.impl.Constants;
034: import org.apache.xerces.impl.XMLErrorReporter;
035: import org.apache.xerces.impl.validation.EntityState;
036: import org.apache.xerces.impl.validation.ValidationManager;
037: import org.apache.xerces.impl.xs.XMLSchemaValidator;
038: import org.apache.xerces.impl.xs.util.SimpleLocator;
039: import org.apache.xerces.util.NamespaceSupport;
040: import org.apache.xerces.util.SymbolTable;
041: import org.apache.xerces.util.XMLAttributesImpl;
042: import org.apache.xerces.util.XMLSymbols;
043: import org.apache.xerces.xni.NamespaceContext;
044: import org.apache.xerces.xni.QName;
045: import org.apache.xerces.xni.XMLString;
046: import org.apache.xerces.xni.XNIException;
047: import org.apache.xerces.xni.parser.XMLParseException;
048: import org.w3c.dom.Attr;
049: import org.w3c.dom.CDATASection;
050: import org.w3c.dom.Comment;
051: import org.w3c.dom.Document;
052: import org.w3c.dom.DocumentType;
053: import org.w3c.dom.Entity;
054: import org.w3c.dom.NamedNodeMap;
055: import org.w3c.dom.Node;
056: import org.w3c.dom.ProcessingInstruction;
057: import org.w3c.dom.Text;
058: import org.xml.sax.SAXException;
059:
060: /**
061: * <p>A validator helper for <code>DOMSource</code>s.</p>
062: *
063: * @author Michael Glavassevich, IBM
064: * @version $Id: DOMValidatorHelper.java 542517 2007-05-29 13:47:06Z mrglavas $
065: */
066: final class DOMValidatorHelper implements ValidatorHelper, EntityState {
067:
068: //
069: // Constants
070: //
071:
072: /** Chunk size (1024). */
073: private static final int CHUNK_SIZE = (1 << 10);
074:
075: /** Chunk mask (CHUNK_SIZE - 1). */
076: private static final int CHUNK_MASK = CHUNK_SIZE - 1;
077:
078: // property identifiers
079:
080: /** Property identifier: error reporter. */
081: private static final String ERROR_REPORTER = Constants.XERCES_PROPERTY_PREFIX
082: + Constants.ERROR_REPORTER_PROPERTY;
083:
084: /** Property identifier: namespace context. */
085: private static final String NAMESPACE_CONTEXT = Constants.XERCES_PROPERTY_PREFIX
086: + Constants.NAMESPACE_CONTEXT_PROPERTY;
087:
088: /** Property identifier: XML Schema validator. */
089: private static final String SCHEMA_VALIDATOR = Constants.XERCES_PROPERTY_PREFIX
090: + Constants.SCHEMA_VALIDATOR_PROPERTY;
091:
092: /** Property identifier: symbol table. */
093: private static final String SYMBOL_TABLE = Constants.XERCES_PROPERTY_PREFIX
094: + Constants.SYMBOL_TABLE_PROPERTY;
095:
096: /** Property identifier: validation manager. */
097: private static final String VALIDATION_MANAGER = Constants.XERCES_PROPERTY_PREFIX
098: + Constants.VALIDATION_MANAGER_PROPERTY;
099:
100: //
101: // Data
102: //
103:
104: /** Error reporter. */
105: private final XMLErrorReporter fErrorReporter;
106:
107: /** The namespace context of this document: stores namespaces in scope. **/
108: private final NamespaceSupport fNamespaceContext;
109:
110: /** The namespace context of the DOMSource, includes context from ancestor nodes. **/
111: private final DOMNamespaceContext fDOMNamespaceContext = new DOMNamespaceContext();
112:
113: /** Schema validator. **/
114: private final XMLSchemaValidator fSchemaValidator;
115:
116: /** Symbol table **/
117: private final SymbolTable fSymbolTable;
118:
119: /** Validation manager. **/
120: private final ValidationManager fValidationManager;
121:
122: /** Component manager. **/
123: private final XMLSchemaValidatorComponentManager fComponentManager;
124:
125: /** Simple Locator. **/
126: private final SimpleLocator fXMLLocator = new SimpleLocator(null,
127: null, -1, -1, -1);
128:
129: /** DOM document handler. **/
130: private DOMDocumentHandler fDOMValidatorHandler;
131:
132: /** DOM result augmentor. **/
133: private final DOMResultAugmentor fDOMResultAugmentor = new DOMResultAugmentor(
134: this );
135:
136: /** DOM result builder. **/
137: private final DOMResultBuilder fDOMResultBuilder = new DOMResultBuilder();
138:
139: /** Map for tracking unparsed entities. **/
140: private NamedNodeMap fEntities = null;
141:
142: /** Array for holding character data. **/
143: private final char[] fCharBuffer = new char[CHUNK_SIZE];
144:
145: /** Root node. **/
146: private Node fRoot;
147:
148: /** Current element. **/
149: private Node fCurrentElement;
150:
151: /** Fields for start element, end element and characters. **/
152: final QName fElementQName = new QName();
153: final QName fAttributeQName = new QName();
154: final XMLAttributesImpl fAttributes = new XMLAttributesImpl();
155: final XMLString fTempString = new XMLString();
156:
157: public DOMValidatorHelper(
158: XMLSchemaValidatorComponentManager componentManager) {
159: fComponentManager = componentManager;
160: fErrorReporter = (XMLErrorReporter) fComponentManager
161: .getProperty(ERROR_REPORTER);
162: fNamespaceContext = (NamespaceSupport) fComponentManager
163: .getProperty(NAMESPACE_CONTEXT);
164: fSchemaValidator = (XMLSchemaValidator) fComponentManager
165: .getProperty(SCHEMA_VALIDATOR);
166: fSymbolTable = (SymbolTable) fComponentManager
167: .getProperty(SYMBOL_TABLE);
168: fValidationManager = (ValidationManager) fComponentManager
169: .getProperty(VALIDATION_MANAGER);
170: }
171:
172: /*
173: * ValidatorHelper methods
174: */
175:
176: public void validate(Source source, Result result)
177: throws SAXException, IOException {
178: if (result instanceof DOMResult || result == null) {
179: final DOMSource domSource = (DOMSource) source;
180: final DOMResult domResult = (DOMResult) result;
181: Node node = domSource.getNode();
182: fRoot = node;
183: if (node != null) {
184: fComponentManager.reset();
185: fValidationManager.setEntityState(this );
186: fDOMNamespaceContext.reset();
187: String systemId = domSource.getSystemId();
188: fXMLLocator.setLiteralSystemId(systemId);
189: fXMLLocator.setExpandedSystemId(systemId);
190: fErrorReporter.setDocumentLocator(fXMLLocator);
191: try {
192: // regardless of what type of node this is, fire start and end document events
193: setupEntityMap((node.getNodeType() == Node.DOCUMENT_NODE) ? (Document) node
194: : node.getOwnerDocument());
195: setupDOMResultHandler(domSource, domResult);
196: fSchemaValidator.startDocument(fXMLLocator, null,
197: fDOMNamespaceContext, null);
198: validate(node);
199: fSchemaValidator.endDocument(null);
200: } catch (XMLParseException e) {
201: throw Util.toSAXParseException(e);
202: } catch (XNIException e) {
203: throw Util.toSAXException(e);
204: } finally {
205: // Release references to application objects
206: fRoot = null;
207: fCurrentElement = null;
208: fEntities = null;
209: if (fDOMValidatorHandler != null) {
210: fDOMValidatorHandler.setDOMResult(null);
211: }
212: }
213: }
214: return;
215: }
216: throw new IllegalArgumentException(
217: JAXPValidationMessageFormatter.formatMessage(Locale
218: .getDefault(), "SourceResultMismatch",
219: new Object[] { source.getClass().getName(),
220: result.getClass().getName() }));
221: }
222:
223: /*
224: * EntityState methods
225: */
226:
227: public boolean isEntityDeclared(String name) {
228: return false;
229: }
230:
231: public boolean isEntityUnparsed(String name) {
232: if (fEntities != null) {
233: Entity entity = (Entity) fEntities.getNamedItem(name);
234: if (entity != null) {
235: return (entity.getNotationName() != null);
236: }
237: }
238: return false;
239: }
240:
241: /*
242: * Other methods
243: */
244:
245: /** Traverse the DOM and fire events to the schema validator. */
246: private void validate(Node node) {
247: final Node top = node;
248: final boolean useIsSameNode = useIsSameNode(top);
249: // Performs a non-recursive traversal of the DOM. This
250: // will avoid a stack overflow for DOMs with high depth.
251: while (node != null) {
252: beginNode(node);
253: Node next = node.getFirstChild();
254: while (next == null) {
255: finishNode(node);
256: if (top == node) {
257: break;
258: }
259: next = node.getNextSibling();
260: if (next == null) {
261: node = node.getParentNode();
262: if (node == null
263: || ((useIsSameNode) ? top.isSameNode(node)
264: : top == node)) {
265: if (node != null) {
266: finishNode(node);
267: }
268: next = null;
269: break;
270: }
271: }
272: }
273: node = next;
274: }
275: }
276:
277: /** Do processing for the start of a node. */
278: private void beginNode(Node node) {
279: switch (node.getNodeType()) {
280: case Node.ELEMENT_NODE:
281: fCurrentElement = node;
282: // push namespace context
283: fNamespaceContext.pushContext();
284: // start element
285: fillQName(fElementQName, node);
286: processAttributes(node.getAttributes());
287: fSchemaValidator.startElement(fElementQName, fAttributes,
288: null);
289: break;
290: case Node.TEXT_NODE:
291: if (fDOMValidatorHandler != null) {
292: fDOMValidatorHandler.setIgnoringCharacters(true);
293: sendCharactersToValidator(node.getNodeValue());
294: fDOMValidatorHandler.setIgnoringCharacters(false);
295: fDOMValidatorHandler.characters((Text) node);
296: } else {
297: sendCharactersToValidator(node.getNodeValue());
298: }
299: break;
300: case Node.CDATA_SECTION_NODE:
301: if (fDOMValidatorHandler != null) {
302: fDOMValidatorHandler.setIgnoringCharacters(true);
303: fSchemaValidator.startCDATA(null);
304: sendCharactersToValidator(node.getNodeValue());
305: fSchemaValidator.endCDATA(null);
306: fDOMValidatorHandler.setIgnoringCharacters(false);
307: fDOMValidatorHandler.cdata((CDATASection) node);
308: } else {
309: fSchemaValidator.startCDATA(null);
310: sendCharactersToValidator(node.getNodeValue());
311: fSchemaValidator.endCDATA(null);
312: }
313: break;
314: case Node.PROCESSING_INSTRUCTION_NODE:
315: /**
316: * The validator does nothing with processing instructions so bypass it.
317: * Send the ProcessingInstruction node directly to the result builder.
318: */
319: if (fDOMValidatorHandler != null) {
320: fDOMValidatorHandler
321: .processingInstruction((ProcessingInstruction) node);
322: }
323: break;
324: case Node.COMMENT_NODE:
325: /**
326: * The validator does nothing with comments so bypass it.
327: * Send the Comment node directly to the result builder.
328: */
329: if (fDOMValidatorHandler != null) {
330: fDOMValidatorHandler.comment((Comment) node);
331: }
332: break;
333: case Node.DOCUMENT_TYPE_NODE:
334: /**
335: * Send the DocumentType node directly to the result builder.
336: */
337: if (fDOMValidatorHandler != null) {
338: fDOMValidatorHandler.doctypeDecl((DocumentType) node);
339: }
340: break;
341: default: // Ignore other node types.
342: break;
343: }
344: }
345:
346: /** Do processing for the end of a node. */
347: private void finishNode(Node node) {
348: if (node.getNodeType() == Node.ELEMENT_NODE) {
349: fCurrentElement = node;
350: // end element
351: fillQName(fElementQName, node);
352: fSchemaValidator.endElement(fElementQName, null);
353: // pop namespace context
354: fNamespaceContext.popContext();
355: }
356: }
357:
358: /**
359: * Extracts NamedNodeMap of entities. We need this to validate
360: * elements and attributes of type xs:ENTITY, xs:ENTITIES or
361: * types dervied from them.
362: */
363: private void setupEntityMap(Document doc) {
364: if (doc != null) {
365: DocumentType docType = doc.getDoctype();
366: if (docType != null) {
367: fEntities = docType.getEntities();
368: return;
369: }
370: }
371: fEntities = null;
372: }
373:
374: /**
375: * Sets up handler for <code>DOMResult</code>.
376: */
377: private void setupDOMResultHandler(DOMSource source,
378: DOMResult result) throws SAXException {
379: // If there's no DOMResult, unset the validator handler
380: if (result == null) {
381: fDOMValidatorHandler = null;
382: fSchemaValidator.setDocumentHandler(null);
383: return;
384: }
385: final Node nodeResult = result.getNode();
386: // If the source node and result node are the same use the DOMResultAugmentor.
387: // Otherwise use the DOMResultBuilder.
388: if (source.getNode() == nodeResult) {
389: fDOMValidatorHandler = fDOMResultAugmentor;
390: fDOMResultAugmentor.setDOMResult(result);
391: fSchemaValidator.setDocumentHandler(fDOMResultAugmentor);
392: return;
393: }
394: if (result.getNode() == null) {
395: try {
396: DocumentBuilderFactory factory = DocumentBuilderFactory
397: .newInstance();
398: factory.setNamespaceAware(true);
399: DocumentBuilder builder = factory.newDocumentBuilder();
400: result.setNode(builder.newDocument());
401: } catch (ParserConfigurationException e) {
402: throw new SAXException(e);
403: }
404: }
405: fDOMValidatorHandler = fDOMResultBuilder;
406: fDOMResultBuilder.setDOMResult(result);
407: fSchemaValidator.setDocumentHandler(fDOMResultBuilder);
408: }
409:
410: private void fillQName(QName toFill, Node node) {
411: final String prefix = node.getPrefix();
412: final String localName = node.getLocalName();
413: final String rawName = node.getNodeName();
414: final String namespace = node.getNamespaceURI();
415: toFill.prefix = (prefix != null) ? fSymbolTable
416: .addSymbol(prefix) : XMLSymbols.EMPTY_STRING;
417: toFill.localpart = (localName != null) ? fSymbolTable
418: .addSymbol(localName) : XMLSymbols.EMPTY_STRING;
419: toFill.rawname = (rawName != null) ? fSymbolTable
420: .addSymbol(rawName) : XMLSymbols.EMPTY_STRING;
421: toFill.uri = (namespace != null && namespace.length() > 0) ? fSymbolTable
422: .addSymbol(namespace)
423: : null;
424: }
425:
426: private void processAttributes(NamedNodeMap attrMap) {
427: final int attrCount = attrMap.getLength();
428: fAttributes.removeAllAttributes();
429: for (int i = 0; i < attrCount; ++i) {
430: Attr attr = (Attr) attrMap.item(i);
431: String value = attr.getValue();
432: if (value == null) {
433: value = XMLSymbols.EMPTY_STRING;
434: }
435: fillQName(fAttributeQName, attr);
436: // REVISIT: Assuming all attributes are of type CDATA. The actual type may not matter. -- mrglavas
437: fAttributes.addAttributeNS(fAttributeQName,
438: XMLSymbols.fCDATASymbol, value);
439: fAttributes.setSpecified(i, attr.getSpecified());
440: // REVISIT: Should we be looking at non-namespace attributes
441: // for additional mappings? Should we detect illegal namespace
442: // declarations and exclude them from the context? -- mrglavas
443: if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
444: // process namespace attribute
445: if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
446: fNamespaceContext.declarePrefix(
447: fAttributeQName.localpart,
448: value.length() != 0 ? fSymbolTable
449: .addSymbol(value) : null);
450: } else {
451: fNamespaceContext.declarePrefix(
452: XMLSymbols.EMPTY_STRING,
453: value.length() != 0 ? fSymbolTable
454: .addSymbol(value) : null);
455: }
456: }
457: }
458: }
459:
460: private void sendCharactersToValidator(String str) {
461: if (str != null) {
462: final int length = str.length();
463: final int remainder = length & CHUNK_MASK;
464: if (remainder > 0) {
465: str.getChars(0, remainder, fCharBuffer, 0);
466: fTempString.setValues(fCharBuffer, 0, remainder);
467: fSchemaValidator.characters(fTempString, null);
468: }
469: int i = remainder;
470: while (i < length) {
471: str.getChars(i, i += CHUNK_SIZE, fCharBuffer, 0);
472: fTempString.setValues(fCharBuffer, 0, CHUNK_SIZE);
473: fSchemaValidator.characters(fTempString, null);
474: }
475: }
476: }
477:
478: /**
479: * Use isSameNode() for testing node identity if the DOM implementation
480: * supports DOM Level 3 core and it isn't the Xerces implementation.
481: */
482: private boolean useIsSameNode(Node node) {
483: if (node instanceof NodeImpl) {
484: return false;
485: }
486: Document doc = node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node
487: : node.getOwnerDocument();
488: return (doc != null && doc.getImplementation().hasFeature(
489: "Core", "3.0"));
490: }
491:
492: /**
493: * Returns the current element node.
494: */
495: Node getCurrentElement() {
496: return fCurrentElement;
497: }
498:
499: /**
500: * NamespaceContext for the DOMSource, includes context for ancestor nodes.
501: */
502: final class DOMNamespaceContext implements NamespaceContext {
503:
504: //
505: // Data
506: //
507:
508: /**
509: * Namespace binding information. This array is composed of a
510: * series of tuples containing the namespace binding information:
511: * <prefix, uri>.
512: */
513: protected String[] fNamespace = new String[16 * 2];
514:
515: /** The size of the namespace information array. */
516: protected int fNamespaceSize = 0;
517:
518: /**
519: * Flag indicating whether the namespace context
520: * has been from the root node's ancestors.
521: */
522: protected boolean fDOMContextBuilt = false;
523:
524: //
525: // Methods
526: //
527:
528: public void pushContext() {
529: fNamespaceContext.pushContext();
530: }
531:
532: public void popContext() {
533: fNamespaceContext.popContext();
534: }
535:
536: public boolean declarePrefix(String prefix, String uri) {
537: return fNamespaceContext.declarePrefix(prefix, uri);
538: }
539:
540: public String getURI(String prefix) {
541: String uri = fNamespaceContext.getURI(prefix);
542: if (uri == null) {
543: if (!fDOMContextBuilt) {
544: fillNamespaceContext();
545: fDOMContextBuilt = true;
546: }
547: if (fNamespaceSize > 0
548: && !fNamespaceContext.containsPrefix(prefix)) {
549: uri = getURI0(prefix);
550: }
551: }
552: return uri;
553: }
554:
555: public String getPrefix(String uri) {
556: return fNamespaceContext.getPrefix(uri);
557: }
558:
559: public int getDeclaredPrefixCount() {
560: return fNamespaceContext.getDeclaredPrefixCount();
561: }
562:
563: public String getDeclaredPrefixAt(int index) {
564: return fNamespaceContext.getDeclaredPrefixAt(index);
565: }
566:
567: public Enumeration getAllPrefixes() {
568: return fNamespaceContext.getAllPrefixes();
569: }
570:
571: public void reset() {
572: fDOMContextBuilt = false;
573: fNamespaceSize = 0;
574: }
575:
576: private void fillNamespaceContext() {
577: if (fRoot != null) {
578: Node currentNode = fRoot.getParentNode();
579: while (currentNode != null) {
580: if (Node.ELEMENT_NODE == currentNode.getNodeType()) {
581: NamedNodeMap attributes = currentNode
582: .getAttributes();
583: final int attrCount = attributes.getLength();
584: for (int i = 0; i < attrCount; ++i) {
585: Attr attr = (Attr) attributes.item(i);
586: String value = attr.getValue();
587: if (value == null) {
588: value = XMLSymbols.EMPTY_STRING;
589: }
590: fillQName(fAttributeQName, attr);
591: // REVISIT: Should we be looking at non-namespace attributes
592: // for additional mappings? Should we detect illegal namespace
593: // declarations and exclude them from the context? -- mrglavas
594: if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
595: // process namespace attribute
596: if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
597: declarePrefix0(
598: fAttributeQName.localpart,
599: value.length() != 0 ? fSymbolTable
600: .addSymbol(value)
601: : null);
602: } else {
603: declarePrefix0(
604: XMLSymbols.EMPTY_STRING,
605: value.length() != 0 ? fSymbolTable
606: .addSymbol(value)
607: : null);
608: }
609: }
610: }
611:
612: }
613: currentNode = currentNode.getParentNode();
614: }
615: }
616: }
617:
618: private void declarePrefix0(String prefix, String uri) {
619: // resize array, if needed
620: if (fNamespaceSize == fNamespace.length) {
621: String[] namespacearray = new String[fNamespaceSize * 2];
622: System.arraycopy(fNamespace, 0, namespacearray, 0,
623: fNamespaceSize);
624: fNamespace = namespacearray;
625: }
626:
627: // bind prefix to uri in current context
628: fNamespace[fNamespaceSize++] = prefix;
629: fNamespace[fNamespaceSize++] = uri;
630: }
631:
632: private String getURI0(String prefix) {
633: // find prefix in the DOM context
634: for (int i = 0; i < fNamespaceSize; i += 2) {
635: if (fNamespace[i] == prefix) {
636: return fNamespace[i + 1];
637: }
638: }
639: // prefix not found
640: return null;
641: }
642: }
643:
644: } // DOMValidatorHelper
|