1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xerces.internal.jaxp;
23
24import java.io.IOException;
25
26import javax.xml.validation.TypeInfoProvider;
27import javax.xml.validation.ValidatorHandler;
28
29import com.sun.org.apache.xerces.internal.dom.DOMInputImpl;
30import com.sun.org.apache.xerces.internal.impl.Constants;
31import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
32import com.sun.org.apache.xerces.internal.impl.xs.opti.DefaultXMLDocumentHandler;
33import com.sun.org.apache.xerces.internal.util.AttributesProxy;
34import com.sun.org.apache.xerces.internal.util.AugmentationsImpl;
35import com.sun.org.apache.xerces.internal.util.ErrorHandlerProxy;
36import com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper;
37import com.sun.org.apache.xerces.internal.util.LocatorProxy;
38import com.sun.org.apache.xerces.internal.util.SymbolTable;
39import com.sun.org.apache.xerces.internal.util.XMLResourceIdentifierImpl;
40import com.sun.org.apache.xerces.internal.xni.Augmentations;
41import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
42import com.sun.org.apache.xerces.internal.xni.QName;
43import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
44import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
45import com.sun.org.apache.xerces.internal.xni.XMLLocator;
46import com.sun.org.apache.xerces.internal.xni.XMLString;
47import com.sun.org.apache.xerces.internal.xni.XNIException;
48import com.sun.org.apache.xerces.internal.xni.parser.XMLComponent;
49import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager;
50import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
51import com.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver;
52import com.sun.org.apache.xerces.internal.xni.parser.XMLErrorHandler;
53import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
54import org.w3c.dom.TypeInfo;
55import org.w3c.dom.ls.LSInput;
56import org.w3c.dom.ls.LSResourceResolver;
57import org.xml.sax.Attributes;
58import org.xml.sax.ContentHandler;
59import org.xml.sax.ErrorHandler;
60import org.xml.sax.SAXException;
61import org.xml.sax.SAXParseException;
62import org.xml.sax.helpers.DefaultHandler;
63
64/**
65 * Runs events through a {@link javax.xml.validation.ValidatorHandler}
66 * and performs validation/infoset-augmentation by an external validator.
67 *
68 * <p>
69 * This component sets up the pipeline as follows:
70 *
71 * <!-- this picture may look teribble on your IDE but it is correct. -->
72 * <pre>
73 *             __                                           __
74 *            /  |==> XNI2SAX --> Validator --> SAX2XNI ==>|
75 *           /   |                                         |
76 *       ==>| Tee|                                         | next
77 *           \   |                                         |  component
78 *            \  |============other XNI events============>|
79 *             ~~                                           ~~
80 * </pre>
81 * <p>
82 * only those events that need to go through Validator will go the 1st route,
83 * and other events go the 2nd direct route.
84 *
85 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
86 */
87final class JAXPValidatorComponent
88    extends TeeXMLDocumentFilterImpl implements XMLComponent {
89
90    /** Property identifier: entity manager. */
91    private static final String ENTITY_MANAGER =
92        Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_MANAGER_PROPERTY;
93
94    /** Property identifier: error reporter. */
95    private static final String ERROR_REPORTER =
96        Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
97
98    /** Property identifier: symbol table. */
99    private static final String SYMBOL_TABLE =
100        Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
101
102    // pipeline parts
103    private final ValidatorHandler validator;
104    private final XNI2SAX xni2sax = new XNI2SAX();
105    private final SAX2XNI sax2xni = new SAX2XNI();
106
107    // never be null
108    private final TypeInfoProvider typeInfoProvider;
109
110    /**
111     * Used to store the {@link Augmentations} associated with the
112     * current event, so that we can pick it up again
113     * when the event is forwarded by the {@link ValidatorHandler}.
114     *
115     * UGLY HACK.
116     */
117    private Augmentations fCurrentAug;
118
119    /**
120     * {@link XMLAttributes} version of {@link #fCurrentAug}.
121     */
122    private XMLAttributes fCurrentAttributes;
123
124    // components obtained from a manager / property
125
126    private SymbolTable fSymbolTable;
127    private XMLErrorReporter fErrorReporter;
128    private XMLEntityResolver fEntityResolver;
129
130    /**
131     * @param validatorHandler may not be null.
132     */
133    public JAXPValidatorComponent( ValidatorHandler validatorHandler ) {
134        this.validator = validatorHandler;
135        TypeInfoProvider tip = validatorHandler.getTypeInfoProvider();
136        if(tip==null)   tip = noInfoProvider;
137        this.typeInfoProvider = tip;
138
139        // configure wiring between internal components.
140        xni2sax.setContentHandler(validator);
141        validator.setContentHandler(sax2xni);
142        this.setSide(xni2sax);
143
144        // configure validator with proper EntityResolver/ErrorHandler.
145        validator.setErrorHandler(new ErrorHandlerProxy() {
146            protected XMLErrorHandler getErrorHandler() {
147                XMLErrorHandler handler = fErrorReporter.getErrorHandler();
148                if(handler!=null)   return handler;
149                return new ErrorHandlerWrapper(DraconianErrorHandler.getInstance());
150            }
151        });
152        validator.setResourceResolver(new LSResourceResolver() {
153            public LSInput resolveResource(String type,String ns, String publicId, String systemId, String baseUri) {
154                if(fEntityResolver==null)   return null;
155                try {
156                    XMLInputSource is = fEntityResolver.resolveEntity(
157                        new XMLResourceIdentifierImpl(publicId,systemId,baseUri,null));
158                    if(is==null)    return null;
159
160                    LSInput di = new DOMInputImpl();
161                    di.setBaseURI(is.getBaseSystemId());
162                    di.setByteStream(is.getByteStream());
163                    di.setCharacterStream(is.getCharacterStream());
164                    di.setEncoding(is.getEncoding());
165                    di.setPublicId(is.getPublicId());
166                    di.setSystemId(is.getSystemId());
167
168                    return di;
169                } catch( IOException e ) {
170                    // erors thrown by the callback is not supposed to be
171                    // reported to users.
172                    throw new XNIException(e);
173                }
174            }
175        });
176    }
177
178
179    public void startElement(QName element, XMLAttributes attributes, Augmentations augs) throws XNIException {
180        fCurrentAttributes = attributes;
181        fCurrentAug = augs;
182        xni2sax.startElement(element,attributes,null);
183        fCurrentAttributes = null; // mostly to make it easy to find any bug.
184    }
185
186    public void endElement(QName element, Augmentations augs) throws XNIException {
187        fCurrentAug = augs;
188        xni2sax.endElement(element,null);
189    }
190
191    public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs) throws XNIException {
192        startElement(element,attributes,augs);
193        endElement(element,augs);
194    }
195
196
197    public void characters(XMLString text, Augmentations augs) throws XNIException {
198        // since a validator may change the contents,
199        // let this one go through a validator
200        fCurrentAug = augs;
201        xni2sax.characters(text,null);
202    }
203
204    public void ignorableWhitespace(XMLString text, Augmentations augs) throws XNIException {
205        // since a validator may change the contents,
206        // let this one go through a validator
207        fCurrentAug = augs;
208        xni2sax.ignorableWhitespace(text,null);
209    }
210
211    public void reset(XMLComponentManager componentManager) throws XMLConfigurationException {
212        // obtain references from the manager
213        fSymbolTable = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE);
214        fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER);
215        try {
216            fEntityResolver = (XMLEntityResolver) componentManager.getProperty(ENTITY_MANAGER);
217        }
218        catch (XMLConfigurationException e) {
219            fEntityResolver = null;
220        }
221    }
222
223    /**
224     *
225     * Uses {@link DefaultHandler} as a default implementation of
226     * {@link ContentHandler}.
227     *
228     * <p>
229     * We only forward certain events from a {@link ValidatorHandler}.
230     * Other events should go "the 2nd direct route".
231     */
232    private final class SAX2XNI extends DefaultHandler {
233
234        /**
235         * {@link Augmentations} to send along with events.
236         * We reuse one object for efficiency.
237         */
238        private final Augmentations fAugmentations = new AugmentationsImpl();
239
240        /**
241         * {@link QName} to send along events.
242         * we reuse one QName for efficiency.
243         */
244        private final QName fQName = new QName();
245
246        public void characters(char[] ch, int start, int len) throws SAXException {
247            try {
248                handler().characters(new XMLString(ch,start,len),aug());
249            } catch( XNIException e ) {
250                throw toSAXException(e);
251            }
252        }
253
254        public void ignorableWhitespace(char[] ch, int start, int len) throws SAXException {
255            try {
256                handler().ignorableWhitespace(new XMLString(ch,start,len),aug());
257            } catch( XNIException e ) {
258                throw toSAXException(e);
259            }
260        }
261
262        public void startElement(String uri, String localName, String qname, Attributes atts) throws SAXException {
263            try {
264                updateAttributes(atts);
265                handler().startElement(toQName(uri,localName,qname), fCurrentAttributes, elementAug());
266            } catch( XNIException e ) {
267                throw toSAXException(e);
268            }
269        }
270
271        public void endElement(String uri, String localName, String qname) throws SAXException {
272            try {
273                handler().endElement(toQName(uri,localName,qname),aug());
274            } catch( XNIException e ) {
275                throw toSAXException(e);
276            }
277        }
278
279        private Augmentations elementAug() {
280            Augmentations aug = aug();
281            /** aug.putItem(Constants.TYPEINFO,typeInfoProvider.getElementTypeInfo()); **/
282            return aug;
283        }
284
285
286        /**
287         * Gets the {@link Augmentations} that should be associated with
288         * the current event.
289         */
290        private Augmentations aug() {
291            if( fCurrentAug!=null ) {
292                Augmentations r = fCurrentAug;
293                fCurrentAug = null; // we "consumed" this augmentation.
294                return r;
295            }
296            fAugmentations.removeAllItems();
297            return fAugmentations;
298        }
299
300        /**
301         * Get the handler to which we should send events.
302         */
303        private XMLDocumentHandler handler() {
304            return JAXPValidatorComponent.this.getDocumentHandler();
305        }
306
307        /**
308         * Converts the {@link XNIException} received from a downstream
309         * component to a {@link SAXException}.
310         */
311        private SAXException toSAXException( XNIException xe ) {
312            Exception e = xe.getException();
313            if( e==null )   e = xe;
314            if( e instanceof SAXException )  return (SAXException)e;
315            return new SAXException(e);
316        }
317
318        /**
319         * Creates a proper {@link QName} object from 3 parts.
320         * <p>
321         * This method does the symbolization.
322         */
323        private QName toQName( String uri, String localName, String qname ) {
324            String prefix = null;
325            int idx = qname.indexOf(':');
326            if( idx>0 )
327                prefix = symbolize(qname.substring(0,idx));
328
329            localName = symbolize(localName);
330            qname = symbolize(qname);
331            uri = symbolize(uri);
332
333            // notify handlers
334            fQName.setValues(prefix, localName, qname, uri);
335            return fQName;
336        }
337    }
338
339    /**
340     * Converts {@link XNI} events to {@link ContentHandler} events.
341     *
342     * <p>
343     * Deriving from {@link DefaultXMLDocumentHandler}
344     * to reuse its default {@link com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler}
345     * implementation.
346     *
347     * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
348     */
349    private final class XNI2SAX extends DefaultXMLDocumentHandler {
350
351        private ContentHandler fContentHandler;
352
353        private String fVersion;
354
355        /** Namespace context */
356        protected NamespaceContext fNamespaceContext;
357
358        /**
359         * For efficiency, we reuse one instance.
360         */
361        private final AttributesProxy fAttributesProxy = new AttributesProxy(null);
362
363        public void setContentHandler( ContentHandler handler ) {
364            this.fContentHandler = handler;
365        }
366
367        public ContentHandler getContentHandler() {
368            return fContentHandler;
369        }
370
371
372        public void xmlDecl(String version, String encoding, String standalone, Augmentations augs) throws XNIException {
373            this.fVersion = version;
374        }
375
376        public void startDocument(XMLLocator locator, String encoding, NamespaceContext namespaceContext, Augmentations augs) throws XNIException {
377            fNamespaceContext = namespaceContext;
378            fContentHandler.setDocumentLocator(new LocatorProxy(locator));
379            try {
380                fContentHandler.startDocument();
381            } catch (SAXException e) {
382                throw new XNIException(e);
383            }
384        }
385
386        public void endDocument(Augmentations augs) throws XNIException {
387            try {
388                fContentHandler.endDocument();
389            } catch (SAXException e) {
390                throw new XNIException(e);
391            }
392        }
393
394        public void processingInstruction(String target, XMLString data, Augmentations augs) throws XNIException {
395            try {
396                fContentHandler.processingInstruction(target,data.toString());
397            } catch (SAXException e) {
398                throw new XNIException(e);
399            }
400        }
401
402        public void startElement(QName element, XMLAttributes attributes, Augmentations augs) throws XNIException {
403            try {
404                // start namespace prefix mappings
405                int count = fNamespaceContext.getDeclaredPrefixCount();
406                if (count > 0) {
407                    String prefix = null;
408                    String uri = null;
409                    for (int i = 0; i < count; i++) {
410                        prefix = fNamespaceContext.getDeclaredPrefixAt(i);
411                        uri = fNamespaceContext.getURI(prefix);
412                        fContentHandler.startPrefixMapping(prefix, (uri == null)?"":uri);
413                    }
414                }
415
416                String uri = element.uri != null ? element.uri : "";
417                String localpart = element.localpart;
418                fAttributesProxy.setAttributes(attributes);
419                fContentHandler.startElement(uri, localpart, element.rawname, fAttributesProxy);
420            } catch( SAXException e ) {
421                throw new XNIException(e);
422            }
423        }
424
425        public void endElement(QName element, Augmentations augs) throws XNIException {
426            try {
427                String uri = element.uri != null ? element.uri : "";
428                String localpart = element.localpart;
429                fContentHandler.endElement(uri, localpart, element.rawname);
430
431                // send endPrefixMapping events
432                int count = fNamespaceContext.getDeclaredPrefixCount();
433                if (count > 0) {
434                    for (int i = 0; i < count; i++) {
435                        fContentHandler.endPrefixMapping(fNamespaceContext.getDeclaredPrefixAt(i));
436                    }
437                }
438            } catch( SAXException e ) {
439                throw new XNIException(e);
440            }
441        }
442
443        public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs) throws XNIException {
444            startElement(element,attributes,augs);
445            endElement(element,augs);
446        }
447
448        public void characters(XMLString text, Augmentations augs) throws XNIException {
449            try {
450                fContentHandler.characters(text.ch,text.offset,text.length);
451            } catch (SAXException e) {
452                throw new XNIException(e);
453            }
454        }
455
456        public void ignorableWhitespace(XMLString text, Augmentations augs) throws XNIException {
457            try {
458                fContentHandler.ignorableWhitespace(text.ch,text.offset,text.length);
459            } catch (SAXException e) {
460                throw new XNIException(e);
461            }
462        }
463    }
464
465    private static final class DraconianErrorHandler implements ErrorHandler {
466
467        /**
468         * Singleton instance.
469         */
470        private static final DraconianErrorHandler ERROR_HANDLER_INSTANCE
471            = new DraconianErrorHandler();
472
473        private DraconianErrorHandler() {}
474
475        /** Returns the one and only instance of this error handler. */
476        public static DraconianErrorHandler getInstance() {
477            return ERROR_HANDLER_INSTANCE;
478        }
479
480        /** Warning: Ignore. */
481        public void warning(SAXParseException e) throws SAXException {
482            // noop
483        }
484
485        /** Error: Throws back SAXParseException. */
486        public void error(SAXParseException e) throws SAXException {
487            throw e;
488        }
489
490        /** Fatal Error: Throws back SAXParseException. */
491        public void fatalError(SAXParseException e) throws SAXException {
492            throw e;
493        }
494
495    } // DraconianErrorHandler
496
497
498    /**
499     * Compares the given {@link Attributes} with {@link #fCurrentAttributes}
500     * and update the latter accordingly.
501     */
502    private void updateAttributes( Attributes atts ) {
503        int len = atts.getLength();
504        for( int i=0; i<len; i++ ) {
505            String aqn = atts.getQName(i);
506            int j = fCurrentAttributes.getIndex(aqn);
507            String av = atts.getValue(i);
508            if(j==-1) {
509                // newly added attribute. add to the current attribute list.
510
511                String prefix;
512                int idx = aqn.indexOf(':');
513                if( idx<0 ) {
514                    prefix = null;
515                } else {
516                    prefix = symbolize(aqn.substring(0,idx));
517                }
518
519                j = fCurrentAttributes.addAttribute(
520                    new QName(
521                        prefix,
522                        symbolize(atts.getLocalName(i)),
523                        symbolize(aqn),
524                        symbolize(atts.getURI(i))),
525                    atts.getType(i),av);
526            } else {
527                // the attribute is present.
528                if( !av.equals(fCurrentAttributes.getValue(j)) ) {
529                    // but the value was changed.
530                    fCurrentAttributes.setValue(j,av);
531                }
532            }
533
534            /** Augmentations augs = fCurrentAttributes.getAugmentations(j);
535            augs.putItem( Constants.TYPEINFO,
536                typeInfoProvider.getAttributeTypeInfo(i) );
537            augs.putItem( Constants.ID_ATTRIBUTE,
538                typeInfoProvider.isIdAttribute(i)?Boolean.TRUE:Boolean.FALSE ); **/
539        }
540    }
541
542    private String symbolize( String s ) {
543        return fSymbolTable.addSymbol(s);
544    }
545
546
547    /**
548     * {@link TypeInfoProvider} that returns no info.
549     */
550    private static final TypeInfoProvider noInfoProvider = new TypeInfoProvider() {
551        public TypeInfo getElementTypeInfo() {
552            return null;
553        }
554        public TypeInfo getAttributeTypeInfo(int index) {
555            return null;
556        }
557        public TypeInfo getAttributeTypeInfo(String attributeQName) {
558            return null;
559        }
560        public TypeInfo getAttributeTypeInfo(String attributeUri, String attributeLocalName) {
561            return null;
562        }
563        public boolean isIdAttribute(int index) {
564            return false;
565        }
566        public boolean isSpecified(int index) {
567            return false;
568        }
569    };
570
571    //
572    //
573    // XMLComponent implementation.
574    //
575    //
576
577    // no property/feature supported
578    public String[] getRecognizedFeatures() {
579        return null;
580    }
581
582    public void setFeature(String featureId, boolean state) throws XMLConfigurationException {
583    }
584
585    public String[] getRecognizedProperties() {
586        return new String[]{ENTITY_MANAGER, ERROR_REPORTER, SYMBOL_TABLE};
587    }
588
589    public void setProperty(String propertyId, Object value) throws XMLConfigurationException {
590    }
591
592    public Boolean getFeatureDefault(String featureId) {
593        return null;
594    }
595
596    public Object getPropertyDefault(String propertyId) {
597        return null;
598    }
599
600}
601