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;
019:
020: import java.io.IOException;
021:
022: import javax.xml.validation.TypeInfoProvider;
023: import javax.xml.validation.ValidatorHandler;
024:
025: import org.apache.xerces.dom.DOMInputImpl;
026: import org.apache.xerces.impl.Constants;
027: import org.apache.xerces.impl.XMLErrorReporter;
028: import org.apache.xerces.impl.xs.opti.DefaultXMLDocumentHandler;
029: import org.apache.xerces.util.AttributesProxy;
030: import org.apache.xerces.util.AugmentationsImpl;
031: import org.apache.xerces.util.ErrorHandlerProxy;
032: import org.apache.xerces.util.ErrorHandlerWrapper;
033: import org.apache.xerces.util.LocatorProxy;
034: import org.apache.xerces.util.SymbolTable;
035: import org.apache.xerces.util.XMLResourceIdentifierImpl;
036: import org.apache.xerces.xni.Augmentations;
037: import org.apache.xerces.xni.NamespaceContext;
038: import org.apache.xerces.xni.QName;
039: import org.apache.xerces.xni.XMLAttributes;
040: import org.apache.xerces.xni.XMLDocumentHandler;
041: import org.apache.xerces.xni.XMLLocator;
042: import org.apache.xerces.xni.XMLString;
043: import org.apache.xerces.xni.XNIException;
044: import org.apache.xerces.xni.parser.XMLComponent;
045: import org.apache.xerces.xni.parser.XMLComponentManager;
046: import org.apache.xerces.xni.parser.XMLConfigurationException;
047: import org.apache.xerces.xni.parser.XMLEntityResolver;
048: import org.apache.xerces.xni.parser.XMLErrorHandler;
049: import org.apache.xerces.xni.parser.XMLInputSource;
050: import org.w3c.dom.TypeInfo;
051: import org.w3c.dom.ls.LSInput;
052: import org.w3c.dom.ls.LSResourceResolver;
053: import org.xml.sax.Attributes;
054: import org.xml.sax.ContentHandler;
055: import org.xml.sax.ErrorHandler;
056: import org.xml.sax.SAXException;
057: import org.xml.sax.SAXParseException;
058: import org.xml.sax.helpers.DefaultHandler;
059:
060: /**
061: * Runs events through a {@link javax.xml.validation.ValidatorHandler}
062: * and performs validation/infoset-augmentation by an external validator.
063: *
064: * <p>
065: * This component sets up the pipeline as follows:
066: *
067: * <!-- this picture may look teribble on your IDE but it is correct. -->
068: * <pre>
069: * __ __
070: * / |==> XNI2SAX --> Validator --> SAX2XNI ==>|
071: * / | |
072: * ==>| Tee| | next
073: * \ | | component
074: * \ |============other XNI events============>|
075: * ~~ ~~
076: * </pre>
077: * <p>
078: * only those events that need to go through Validator will go the 1st route,
079: * and other events go the 2nd direct route.
080: *
081: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
082: * @version $Id: JAXPValidatorComponent.java 548088 2007-06-17 18:25:17Z mrglavas $
083: */
084: final class JAXPValidatorComponent extends TeeXMLDocumentFilterImpl
085: implements XMLComponent {
086:
087: /** Property identifier: entity manager. */
088: private static final String ENTITY_MANAGER = Constants.XERCES_PROPERTY_PREFIX
089: + Constants.ENTITY_MANAGER_PROPERTY;
090:
091: /** Property identifier: error reporter. */
092: private static final String ERROR_REPORTER = Constants.XERCES_PROPERTY_PREFIX
093: + Constants.ERROR_REPORTER_PROPERTY;
094:
095: /** Property identifier: symbol table. */
096: private static final String SYMBOL_TABLE = Constants.XERCES_PROPERTY_PREFIX
097: + Constants.SYMBOL_TABLE_PROPERTY;
098:
099: // pipeline parts
100: private final ValidatorHandler validator;
101: private final XNI2SAX xni2sax = new XNI2SAX();
102: private final SAX2XNI sax2xni = new SAX2XNI();
103:
104: // never be null
105: private final TypeInfoProvider typeInfoProvider;
106:
107: /**
108: * Used to store the {@link Augmentations} associated with the
109: * current event, so that we can pick it up again
110: * when the event is forwarded by the {@link ValidatorHandler}.
111: *
112: * UGLY HACK.
113: */
114: private Augmentations fCurrentAug;
115:
116: /**
117: * {@link XMLAttributes} version of {@link #fCurrentAug}.
118: */
119: private XMLAttributes fCurrentAttributes;
120:
121: // components obtained from a manager / property
122:
123: private SymbolTable fSymbolTable;
124: private XMLErrorReporter fErrorReporter;
125: private XMLEntityResolver fEntityResolver;
126:
127: /**
128: * @param validatorHandler may not be null.
129: */
130: public JAXPValidatorComponent(ValidatorHandler validatorHandler) {
131: this .validator = validatorHandler;
132: TypeInfoProvider tip = validatorHandler.getTypeInfoProvider();
133: if (tip == null)
134: tip = noInfoProvider;
135: this .typeInfoProvider = tip;
136:
137: // configure wiring between internal components.
138: xni2sax.setContentHandler(validator);
139: validator.setContentHandler(sax2xni);
140: this .setSide(xni2sax);
141:
142: // configure validator with proper EntityResolver/ErrorHandler.
143: validator.setErrorHandler(new ErrorHandlerProxy() {
144: protected XMLErrorHandler getErrorHandler() {
145: XMLErrorHandler handler = fErrorReporter
146: .getErrorHandler();
147: if (handler != null)
148: return handler;
149: return new ErrorHandlerWrapper(DraconianErrorHandler
150: .getInstance());
151: }
152: });
153: validator.setResourceResolver(new LSResourceResolver() {
154: public LSInput resolveResource(String type, String ns,
155: String publicId, String systemId, String baseUri) {
156: if (fEntityResolver == null)
157: return null;
158: try {
159: XMLInputSource is = fEntityResolver
160: .resolveEntity(new XMLResourceIdentifierImpl(
161: publicId, systemId, baseUri, null));
162: if (is == null)
163: return null;
164:
165: LSInput di = new DOMInputImpl();
166: di.setBaseURI(is.getBaseSystemId());
167: di.setByteStream(is.getByteStream());
168: di.setCharacterStream(is.getCharacterStream());
169: di.setEncoding(is.getEncoding());
170: di.setPublicId(is.getPublicId());
171: di.setSystemId(is.getSystemId());
172:
173: return di;
174: } catch (IOException e) {
175: // erors thrown by the callback is not supposed to be
176: // reported to users.
177: throw new XNIException(e);
178: }
179: }
180: });
181: }
182:
183: public void startElement(QName element, XMLAttributes attributes,
184: Augmentations augs) throws XNIException {
185: fCurrentAttributes = attributes;
186: fCurrentAug = augs;
187: xni2sax.startElement(element, attributes, null);
188: fCurrentAttributes = null; // mostly to make it easy to find any bug.
189: }
190:
191: public void endElement(QName element, Augmentations augs)
192: throws XNIException {
193: fCurrentAug = augs;
194: xni2sax.endElement(element, null);
195: }
196:
197: public void emptyElement(QName element, XMLAttributes attributes,
198: Augmentations augs) throws XNIException {
199: startElement(element, attributes, augs);
200: endElement(element, augs);
201: }
202:
203: public void characters(XMLString text, Augmentations augs)
204: throws XNIException {
205: // since a validator may change the contents,
206: // let this one go through a validator
207: fCurrentAug = augs;
208: xni2sax.characters(text, null);
209: }
210:
211: public void ignorableWhitespace(XMLString text, Augmentations augs)
212: throws XNIException {
213: // since a validator may change the contents,
214: // let this one go through a validator
215: fCurrentAug = augs;
216: xni2sax.ignorableWhitespace(text, null);
217: }
218:
219: public void reset(XMLComponentManager componentManager)
220: throws XMLConfigurationException {
221: // obtain references from the manager
222: fSymbolTable = (SymbolTable) componentManager
223: .getProperty(SYMBOL_TABLE);
224: fErrorReporter = (XMLErrorReporter) componentManager
225: .getProperty(ERROR_REPORTER);
226: try {
227: fEntityResolver = (XMLEntityResolver) componentManager
228: .getProperty(ENTITY_MANAGER);
229: } catch (XMLConfigurationException e) {
230: fEntityResolver = null;
231: }
232: }
233:
234: /**
235: *
236: * Uses {@link DefaultHandler} as a default implementation of
237: * {@link ContentHandler}.
238: *
239: * <p>
240: * We only forward certain events from a {@link ValidatorHandler}.
241: * Other events should go "the 2nd direct route".
242: */
243: private final class SAX2XNI extends DefaultHandler {
244:
245: /**
246: * {@link Augmentations} to send along with events.
247: * We reuse one object for efficiency.
248: */
249: private final Augmentations fAugmentations = new AugmentationsImpl();
250:
251: /**
252: * {@link QName} to send along events.
253: * we reuse one QName for efficiency.
254: */
255: private final QName fQName = new QName();
256:
257: public void characters(char[] ch, int start, int len)
258: throws SAXException {
259: try {
260: handler().characters(new XMLString(ch, start, len),
261: aug());
262: } catch (XNIException e) {
263: throw toSAXException(e);
264: }
265: }
266:
267: public void ignorableWhitespace(char[] ch, int start, int len)
268: throws SAXException {
269: try {
270: handler().ignorableWhitespace(
271: new XMLString(ch, start, len), aug());
272: } catch (XNIException e) {
273: throw toSAXException(e);
274: }
275: }
276:
277: public void startElement(String uri, String localName,
278: String qname, Attributes atts) throws SAXException {
279: try {
280: updateAttributes(atts);
281: handler().startElement(toQName(uri, localName, qname),
282: fCurrentAttributes, elementAug());
283: } catch (XNIException e) {
284: throw toSAXException(e);
285: }
286: }
287:
288: public void endElement(String uri, String localName,
289: String qname) throws SAXException {
290: try {
291: handler().endElement(toQName(uri, localName, qname),
292: aug());
293: } catch (XNIException e) {
294: throw toSAXException(e);
295: }
296: }
297:
298: private Augmentations elementAug() {
299: Augmentations aug = aug();
300: /** aug.putItem(Constants.TYPEINFO,typeInfoProvider.getElementTypeInfo()); **/
301: return aug;
302: }
303:
304: /**
305: * Gets the {@link Augmentations} that should be associated with
306: * the current event.
307: */
308: private Augmentations aug() {
309: if (fCurrentAug != null) {
310: Augmentations r = fCurrentAug;
311: fCurrentAug = null; // we "consumed" this augmentation.
312: return r;
313: }
314: fAugmentations.removeAllItems();
315: return fAugmentations;
316: }
317:
318: /**
319: * Get the handler to which we should send events.
320: */
321: private XMLDocumentHandler handler() {
322: return JAXPValidatorComponent.this .getDocumentHandler();
323: }
324:
325: /**
326: * Converts the {@link XNIException} received from a downstream
327: * component to a {@link SAXException}.
328: */
329: private SAXException toSAXException(XNIException xe) {
330: Exception e = xe.getException();
331: if (e == null)
332: e = xe;
333: if (e instanceof SAXException)
334: return (SAXException) e;
335: return new SAXException(e);
336: }
337:
338: /**
339: * Creates a proper {@link QName} object from 3 parts.
340: * <p>
341: * This method does the symbolization.
342: */
343: private QName toQName(String uri, String localName, String qname) {
344: String prefix = null;
345: int idx = qname.indexOf(':');
346: if (idx > 0)
347: prefix = symbolize(qname.substring(0, idx));
348:
349: localName = symbolize(localName);
350: qname = symbolize(qname);
351: uri = symbolize(uri);
352:
353: // notify handlers
354: fQName.setValues(prefix, localName, qname, uri);
355: return fQName;
356: }
357: }
358:
359: /**
360: * Converts {@link XNI} events to {@link ContentHandler} events.
361: *
362: * <p>
363: * Deriving from {@link DefaultXMLDocumentHandler}
364: * to reuse its default {@link org.apache.xerces.xni.XMLDocumentHandler}
365: * implementation.
366: *
367: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
368: */
369: private static final class XNI2SAX extends
370: DefaultXMLDocumentHandler {
371:
372: private ContentHandler fContentHandler;
373:
374: private String fVersion;
375:
376: /** Namespace context */
377: protected NamespaceContext fNamespaceContext;
378:
379: /**
380: * For efficiency, we reuse one instance.
381: */
382: private final AttributesProxy fAttributesProxy = new AttributesProxy(
383: null);
384:
385: public void setContentHandler(ContentHandler handler) {
386: this .fContentHandler = handler;
387: }
388:
389: public ContentHandler getContentHandler() {
390: return fContentHandler;
391: }
392:
393: public void xmlDecl(String version, String encoding,
394: String standalone, Augmentations augs)
395: throws XNIException {
396: this .fVersion = version;
397: }
398:
399: public void startDocument(XMLLocator locator, String encoding,
400: NamespaceContext namespaceContext, Augmentations augs)
401: throws XNIException {
402: fNamespaceContext = namespaceContext;
403: fContentHandler
404: .setDocumentLocator(new LocatorProxy(locator));
405: try {
406: fContentHandler.startDocument();
407: } catch (SAXException e) {
408: throw new XNIException(e);
409: }
410: }
411:
412: public void endDocument(Augmentations augs) throws XNIException {
413: try {
414: fContentHandler.endDocument();
415: } catch (SAXException e) {
416: throw new XNIException(e);
417: }
418: }
419:
420: public void processingInstruction(String target,
421: XMLString data, Augmentations augs) throws XNIException {
422: try {
423: fContentHandler.processingInstruction(target, data
424: .toString());
425: } catch (SAXException e) {
426: throw new XNIException(e);
427: }
428: }
429:
430: public void startElement(QName element,
431: XMLAttributes attributes, Augmentations augs)
432: throws XNIException {
433: try {
434: // start namespace prefix mappings
435: int count = fNamespaceContext.getDeclaredPrefixCount();
436: if (count > 0) {
437: String prefix = null;
438: String uri = null;
439: for (int i = 0; i < count; i++) {
440: prefix = fNamespaceContext
441: .getDeclaredPrefixAt(i);
442: uri = fNamespaceContext.getURI(prefix);
443: fContentHandler.startPrefixMapping(prefix,
444: (uri == null) ? "" : uri);
445: }
446: }
447:
448: String uri = element.uri != null ? element.uri : "";
449: String localpart = element.localpart;
450: fAttributesProxy.setAttributes(attributes);
451: fContentHandler.startElement(uri, localpart,
452: element.rawname, fAttributesProxy);
453: } catch (SAXException e) {
454: throw new XNIException(e);
455: }
456: }
457:
458: public void endElement(QName element, Augmentations augs)
459: throws XNIException {
460: try {
461: String uri = element.uri != null ? element.uri : "";
462: String localpart = element.localpart;
463: fContentHandler.endElement(uri, localpart,
464: element.rawname);
465:
466: // send endPrefixMapping events
467: int count = fNamespaceContext.getDeclaredPrefixCount();
468: if (count > 0) {
469: for (int i = 0; i < count; i++) {
470: fContentHandler
471: .endPrefixMapping(fNamespaceContext
472: .getDeclaredPrefixAt(i));
473: }
474: }
475: } catch (SAXException e) {
476: throw new XNIException(e);
477: }
478: }
479:
480: public void emptyElement(QName element,
481: XMLAttributes attributes, Augmentations augs)
482: throws XNIException {
483: startElement(element, attributes, augs);
484: endElement(element, augs);
485: }
486:
487: public void characters(XMLString text, Augmentations augs)
488: throws XNIException {
489: try {
490: fContentHandler.characters(text.ch, text.offset,
491: text.length);
492: } catch (SAXException e) {
493: throw new XNIException(e);
494: }
495: }
496:
497: public void ignorableWhitespace(XMLString text,
498: Augmentations augs) throws XNIException {
499: try {
500: fContentHandler.ignorableWhitespace(text.ch,
501: text.offset, text.length);
502: } catch (SAXException e) {
503: throw new XNIException(e);
504: }
505: }
506: }
507:
508: private static final class DraconianErrorHandler implements
509: ErrorHandler {
510:
511: /**
512: * Singleton instance.
513: */
514: private static final DraconianErrorHandler ERROR_HANDLER_INSTANCE = new DraconianErrorHandler();
515:
516: private DraconianErrorHandler() {
517: }
518:
519: /** Returns the one and only instance of this error handler. */
520: public static DraconianErrorHandler getInstance() {
521: return ERROR_HANDLER_INSTANCE;
522: }
523:
524: /** Warning: Ignore. */
525: public void warning(SAXParseException e) throws SAXException {
526: // noop
527: }
528:
529: /** Error: Throws back SAXParseException. */
530: public void error(SAXParseException e) throws SAXException {
531: throw e;
532: }
533:
534: /** Fatal Error: Throws back SAXParseException. */
535: public void fatalError(SAXParseException e) throws SAXException {
536: throw e;
537: }
538:
539: } // DraconianErrorHandler
540:
541: /**
542: * Compares the given {@link Attributes} with {@link #fCurrentAttributes}
543: * and update the latter accordingly.
544: */
545: private void updateAttributes(Attributes atts) {
546: int len = atts.getLength();
547: for (int i = 0; i < len; i++) {
548: String aqn = atts.getQName(i);
549: int j = fCurrentAttributes.getIndex(aqn);
550: String av = atts.getValue(i);
551: if (j == -1) {
552: // newly added attribute. add to the current attribute list.
553:
554: String prefix;
555: int idx = aqn.indexOf(':');
556: if (idx < 0) {
557: prefix = null;
558: } else {
559: prefix = symbolize(aqn.substring(0, idx));
560: }
561:
562: j = fCurrentAttributes.addAttribute(new QName(prefix,
563: symbolize(atts.getLocalName(i)),
564: symbolize(aqn), symbolize(atts.getURI(i))),
565: atts.getType(i), av);
566: } else {
567: // the attribute is present.
568: if (!av.equals(fCurrentAttributes.getValue(j))) {
569: // but the value was changed.
570: fCurrentAttributes.setValue(j, av);
571: }
572: }
573:
574: /** Augmentations augs = fCurrentAttributes.getAugmentations(j);
575: augs.putItem( Constants.TYPEINFO,
576: typeInfoProvider.getAttributeTypeInfo(i) );
577: augs.putItem( Constants.ID_ATTRIBUTE,
578: typeInfoProvider.isIdAttribute(i)?Boolean.TRUE:Boolean.FALSE ); **/
579: }
580: }
581:
582: private String symbolize(String s) {
583: return fSymbolTable.addSymbol(s);
584: }
585:
586: /**
587: * {@link TypeInfoProvider} that returns no info.
588: */
589: private static final TypeInfoProvider noInfoProvider = new TypeInfoProvider() {
590: public TypeInfo getElementTypeInfo() {
591: return null;
592: }
593:
594: public TypeInfo getAttributeTypeInfo(int index) {
595: return null;
596: }
597:
598: public TypeInfo getAttributeTypeInfo(String attributeQName) {
599: return null;
600: }
601:
602: public TypeInfo getAttributeTypeInfo(String attributeUri,
603: String attributeLocalName) {
604: return null;
605: }
606:
607: public boolean isIdAttribute(int index) {
608: return false;
609: }
610:
611: public boolean isSpecified(int index) {
612: return false;
613: }
614: };
615:
616: //
617: //
618: // XMLComponent implementation.
619: //
620: //
621:
622: // no property/feature supported
623: public String[] getRecognizedFeatures() {
624: return null;
625: }
626:
627: public void setFeature(String featureId, boolean state)
628: throws XMLConfigurationException {
629: }
630:
631: public String[] getRecognizedProperties() {
632: return new String[] { ENTITY_MANAGER, ERROR_REPORTER,
633: SYMBOL_TABLE };
634: }
635:
636: public void setProperty(String propertyId, Object value)
637: throws XMLConfigurationException {
638: }
639:
640: public Boolean getFeatureDefault(String featureId) {
641: return null;
642: }
643:
644: public Object getPropertyDefault(String propertyId) {
645: return null;
646: }
647:
648: }
|