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