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 
  22 package com.sun.org.apache.xerces.internal.jaxp;
  23 
  24 import java.io.IOException;
  25 
  26 import javax.xml.validation.TypeInfoProvider;
  27 import javax.xml.validation.ValidatorHandler;
  28 
  29 import com.sun.org.apache.xerces.internal.dom.DOMInputImpl;
  30 import com.sun.org.apache.xerces.internal.impl.Constants;
  31 import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
  32 import com.sun.org.apache.xerces.internal.impl.xs.opti.DefaultXMLDocumentHandler;
  33 import com.sun.org.apache.xerces.internal.util.AttributesProxy;
  34 import com.sun.org.apache.xerces.internal.util.AugmentationsImpl;
  35 import com.sun.org.apache.xerces.internal.util.ErrorHandlerProxy;
  36 import com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper;
  37 import com.sun.org.apache.xerces.internal.util.LocatorProxy;
  38 import com.sun.org.apache.xerces.internal.util.SymbolTable;
  39 import com.sun.org.apache.xerces.internal.util.XMLResourceIdentifierImpl;
  40 import com.sun.org.apache.xerces.internal.xni.Augmentations;
  41 import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
  42 import com.sun.org.apache.xerces.internal.xni.QName;
  43 import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
  44 import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
  45 import com.sun.org.apache.xerces.internal.xni.XMLLocator;
  46 import com.sun.org.apache.xerces.internal.xni.XMLString;
  47 import com.sun.org.apache.xerces.internal.xni.XNIException;
  48 import com.sun.org.apache.xerces.internal.xni.parser.XMLComponent;
  49 import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager;
  50 import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
  51 import com.sun.org.apache.xerces.internal.xni.parser.XMLEntityResolver;
  52 import com.sun.org.apache.xerces.internal.xni.parser.XMLErrorHandler;
  53 import com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource;
  54 import org.w3c.dom.TypeInfo;
  55 import org.w3c.dom.ls.LSInput;
  56 import org.w3c.dom.ls.LSResourceResolver;
  57 import org.xml.sax.Attributes;
  58 import org.xml.sax.ContentHandler;
  59 import org.xml.sax.ErrorHandler;
  60 import org.xml.sax.SAXException;
  61 import org.xml.sax.SAXParseException;
  62 import 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
  86  */
  87 final 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
 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 }