| /* |
| * Copyright 2001-2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package org.apache.axis.encoding; |
| |
| import org.apache.axis.MessageContext; |
| import org.apache.axis.Constants; |
| import org.apache.axis.Message; |
| import org.apache.axis.AxisFault; |
| import org.apache.axis.constants.Use; |
| import org.apache.axis.attachments.Attachments; |
| import org.apache.axis.description.TypeDesc; |
| import org.apache.axis.soap.SOAPConstants; |
| import org.apache.axis.utils.DefaultEntityResolver; |
| import org.apache.axis.utils.DefaultErrorHandler; |
| import org.apache.axis.utils.NSStack; |
| import org.apache.axis.utils.XMLUtils; |
| import org.apache.axis.utils.JavaUtils; |
| import org.apache.axis.utils.Messages; |
| import org.apache.axis.utils.cache.MethodCache; |
| import org.apache.axis.schema.SchemaVersion; |
| import org.apache.axis.components.logger.LogFactory; |
| import org.apache.axis.message.IDResolver; |
| import org.apache.axis.message.MessageElement; |
| import org.apache.axis.message.SAX2EventRecorder; |
| import org.apache.axis.message.SOAPEnvelope; |
| import org.apache.axis.message.SOAPHandler; |
| import org.apache.axis.message.EnvelopeBuilder; |
| import org.apache.axis.message.EnvelopeHandler; |
| import org.apache.axis.message.NullAttributes; |
| import org.apache.commons.logging.Log; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.DTDHandler; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.Locator; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.ext.LexicalHandler; |
| import org.xml.sax.helpers.AttributesImpl; |
| |
| import javax.xml.namespace.QName; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.rpc.JAXRPCException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.io.IOException; |
| import java.lang.reflect.Method; |
| |
| /** |
| * This interface describes the AXIS DeserializationContext, note that |
| * an AXIS compliant DeserializationContext must extend the org.xml.sax.helpers.DefaultHandler. |
| */ |
| |
| public class DeserializationContext implements ContentHandler, DTDHandler, |
| javax.xml.rpc.encoding.DeserializationContext, LexicalHandler { |
| protected static Log log = |
| LogFactory.getLog(DeserializationContext.class.getName()); |
| |
| // invariant member variable to track low-level logging requirements |
| // we cache this once per instance lifecycle to avoid repeated lookups |
| // in heavily used code. |
| private final boolean debugEnabled = log.isDebugEnabled(); |
| |
| static final SchemaVersion schemaVersions[] = new SchemaVersion [] { |
| SchemaVersion.SCHEMA_1999, |
| SchemaVersion.SCHEMA_2000, |
| SchemaVersion.SCHEMA_2001, |
| }; |
| |
| private NSStack namespaces = new NSStack(); |
| |
| // Class used for deserialization using class metadata from |
| // downstream deserializers |
| private Class destClass; |
| |
| // for performance reasons, keep the top of the stack separate from |
| // the remainder of the handlers, and therefore readily available. |
| private SOAPHandler topHandler = null; |
| private ArrayList pushedDownHandlers = new ArrayList(); |
| |
| //private SAX2EventRecorder recorder = new SAX2EventRecorder(); |
| private SAX2EventRecorder recorder = null; |
| private SOAPEnvelope envelope; |
| |
| /* A map of IDs -> IDResolvers */ |
| private HashMap idMap; |
| private LocalIDResolver localIDs; |
| |
| private HashMap fixups; |
| |
| static final SOAPHandler nullHandler = new SOAPHandler(); |
| |
| protected MessageContext msgContext; |
| |
| private boolean doneParsing = false; |
| protected InputSource inputSource = null; |
| |
| private MessageElement curElement; |
| |
| protected int startOfMappingsPos = -1; |
| |
| private static final Class[] DESERIALIZER_CLASSES = |
| new Class[] {String.class, Class.class, QName.class}; |
| private static final String DESERIALIZER_METHOD = "getDeserializer"; |
| |
| // This is a hack to associate the first schema namespace we see with |
| // the correct SchemaVersion. It assumes people won't often be mixing |
| // schema versions in a given document, which I think is OK. --Glen |
| protected boolean haveSeenSchemaNS = false; |
| |
| public void deserializing(boolean isDeserializing) { |
| doneParsing = isDeserializing; |
| } |
| |
| /** |
| * Construct Deserializer using MessageContext and EnvelopeBuilder handler |
| * @param ctx is the MessageContext |
| * @param initialHandler is the EnvelopeBuilder handler |
| */ |
| public DeserializationContext(MessageContext ctx, |
| SOAPHandler initialHandler) |
| { |
| msgContext = ctx; |
| |
| // If high fidelity is required, record the whole damn thing. |
| if (ctx == null || ctx.isHighFidelity()) |
| recorder = new SAX2EventRecorder(); |
| |
| if (initialHandler instanceof EnvelopeBuilder) { |
| envelope = ((EnvelopeBuilder)initialHandler).getEnvelope(); |
| envelope.setRecorder(recorder); |
| } |
| |
| pushElementHandler(new EnvelopeHandler(initialHandler)); |
| } |
| |
| /** |
| * Construct Deserializer |
| * @param is is the InputSource |
| * @param ctx is the MessageContext |
| * @param messageType is the MessageType to construct an EnvelopeBuilder |
| */ |
| public DeserializationContext(InputSource is, |
| MessageContext ctx, |
| String messageType) |
| { |
| msgContext = ctx; |
| EnvelopeBuilder builder = new EnvelopeBuilder(messageType, ctx != null ? ctx.getSOAPConstants() : null); |
| // If high fidelity is required, record the whole damn thing. |
| if (ctx == null || ctx.isHighFidelity()) |
| recorder = new SAX2EventRecorder(); |
| |
| envelope = builder.getEnvelope(); |
| envelope.setRecorder(recorder); |
| |
| pushElementHandler(new EnvelopeHandler(builder)); |
| |
| inputSource = is; |
| } |
| |
| private SOAPConstants soapConstants = null; |
| |
| /** |
| * returns the soap constants. |
| */ |
| public SOAPConstants getSOAPConstants(){ |
| if (soapConstants != null) |
| return soapConstants; |
| if (msgContext != null) { |
| soapConstants = msgContext.getSOAPConstants(); |
| return soapConstants; |
| } else { |
| return Constants.DEFAULT_SOAP_VERSION; |
| } |
| } |
| |
| /** |
| * Construct Deserializer |
| * @param is is the InputSource |
| * @param ctx is the MessageContext |
| * @param messageType is the MessageType to construct an EnvelopeBuilder |
| * @param env is the SOAPEnvelope to construct an EnvelopeBuilder |
| */ |
| public DeserializationContext(InputSource is, |
| MessageContext ctx, |
| String messageType, |
| SOAPEnvelope env) |
| { |
| EnvelopeBuilder builder = new EnvelopeBuilder(env, messageType); |
| |
| msgContext = ctx; |
| |
| // If high fidelity is required, record the whole damn thing. |
| if (ctx == null || ctx.isHighFidelity()) |
| recorder = new SAX2EventRecorder(); |
| |
| envelope = builder.getEnvelope(); |
| envelope.setRecorder(recorder); |
| |
| pushElementHandler(new EnvelopeHandler(builder)); |
| |
| inputSource = is; |
| } |
| |
| /** |
| * Create a parser and parse the inputSource |
| */ |
| public void parse() throws SAXException |
| { |
| if (inputSource != null) { |
| SAXParser parser = XMLUtils.getSAXParser(); |
| try { |
| // We only set the DeserializationContext as ContentHandler and DTDHandler, but |
| // we use singletons for the EntityResolver and ErrorHandler. This reduces the risk |
| // that the SAX parser internally keeps a reference to the DeserializationContext |
| // after we release the parser. E.g. Oracle's SAX parser (oracle.xml.parser.v2) keeps a |
| // reference to the EntityResolver, although we reset the EntityResolver in |
| // XMLUtils#releaseSAXParser. That reference is only cleared when the parser is reused. |
| XMLReader reader = parser.getXMLReader(); |
| reader.setContentHandler(this); |
| reader.setEntityResolver(DefaultEntityResolver.INSTANCE); |
| reader.setErrorHandler(DefaultErrorHandler.INSTANCE); |
| reader.setDTDHandler(this); |
| |
| reader.setProperty("http://xml.org/sax/properties/lexical-handler", this); |
| reader.parse(inputSource); |
| |
| try { |
| // cleanup - so that the parser can be reused. |
| reader.setProperty("http://xml.org/sax/properties/lexical-handler", nullLexicalHandler); |
| } catch (Exception e){ |
| // Ignore. |
| } |
| |
| // only release the parser for reuse if there wasn't an |
| // error. While parsers should be reusable, don't trust |
| // parsers that died to clean up appropriately. |
| XMLUtils.releaseSAXParser(parser); |
| } catch (IOException e) { |
| throw new SAXException(e); |
| } |
| inputSource = null; |
| } |
| } |
| |
| /** |
| * Get current MessageElement |
| **/ |
| public MessageElement getCurElement() { |
| return curElement; |
| } |
| |
| /** |
| * Set current MessageElement |
| **/ |
| public void setCurElement(MessageElement el) |
| { |
| curElement = el; |
| if (curElement != null && curElement.getRecorder() != recorder) { |
| recorder = curElement.getRecorder(); |
| } |
| } |
| |
| |
| /** |
| * Get MessageContext |
| */ |
| public MessageContext getMessageContext() |
| { |
| return msgContext; |
| } |
| |
| /** |
| * Returns this context's encoding style. If we've got a message |
| * context then we'll get the style from that; otherwise we'll |
| * return a default. |
| * |
| * @return a <code>String</code> value |
| */ |
| public String getEncodingStyle() |
| { |
| return msgContext == null ? |
| Use.ENCODED.getEncoding() : msgContext.getEncodingStyle(); |
| } |
| |
| /** |
| * Get Envelope |
| */ |
| public SOAPEnvelope getEnvelope() |
| { |
| return envelope; |
| } |
| |
| /** |
| * Get Event Recorder |
| */ |
| public SAX2EventRecorder getRecorder() |
| { |
| return recorder; |
| } |
| |
| /** |
| * Set Event Recorder |
| */ |
| public void setRecorder(SAX2EventRecorder recorder) |
| { |
| this.recorder = recorder; |
| } |
| |
| /** |
| * Get the Namespace Mappings. Returns null if none are present. |
| **/ |
| public ArrayList getCurrentNSMappings() |
| { |
| return namespaces.cloneFrame(); |
| } |
| |
| /** |
| * Get the Namespace for a particular prefix |
| */ |
| public String getNamespaceURI(String prefix) |
| { |
| String result = namespaces.getNamespaceURI(prefix); |
| if (result != null) |
| return result; |
| |
| if (curElement != null) |
| return curElement.getNamespaceURI(prefix); |
| |
| return null; |
| } |
| |
| /** |
| * Construct a QName from a string of the form <prefix>:<localName> |
| * @param qNameStr is the prefixed name from the xml text |
| * @return QName |
| */ |
| public QName getQNameFromString(String qNameStr) |
| { |
| if (qNameStr == null) |
| return null; |
| |
| // OK, this is a QName, so look up the prefix in our current mappings. |
| int i = qNameStr.indexOf(':'); |
| String nsURI; |
| if (i == -1) { |
| nsURI = getNamespaceURI(""); |
| } else { |
| nsURI = getNamespaceURI(qNameStr.substring(0, i)); |
| } |
| |
| return new QName(nsURI, qNameStr.substring(i + 1)); |
| } |
| |
| /** |
| * Create a QName for the type of the element defined by localName and |
| * namespace from the XSI type. |
| * @param namespace of the element |
| * @param localName is the local name of the element |
| * @param attrs are the attributes on the element |
| */ |
| public QName getTypeFromXSITypeAttr(String namespace, String localName, |
| Attributes attrs) { |
| // Check for type |
| String type = Constants.getValue(attrs, Constants.URIS_SCHEMA_XSI, |
| "type"); |
| if (type == null) { |
| log.debug("No xsi:type attribute found"); |
| return null; |
| } else { |
| // Return the type attribute value converted to a QName |
| QName result = getQNameFromString(type); |
| if (log.isDebugEnabled()) { |
| log.debug("xsi:type attribute found; raw=" + type + "; resolved=" + result); |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * Create a QName for the type of the element defined by localName and |
| * namespace with the specified attributes. |
| * @param namespace of the element |
| * @param localName is the local name of the element |
| * @param attrs are the attributes on the element |
| */ |
| public QName getTypeFromAttributes(String namespace, String localName, |
| Attributes attrs) |
| { |
| QName typeQName = getTypeFromXSITypeAttr(namespace, localName, attrs); |
| if ( (typeQName == null) && Constants.isSOAP_ENC(namespace) ) { |
| |
| // If the element is a SOAP-ENC element, the name of the element is the type. |
| // If the default type mapping accepts SOAP 1.2, then use then set |
| // the typeQName to the SOAP-ENC type. |
| // Else if the default type mapping accepts SOAP 1.1, then |
| // convert the SOAP-ENC type to the appropriate XSD Schema Type. |
| if (namespace.equals(Constants.URI_SOAP12_ENC)) { |
| typeQName = new QName(namespace, localName); |
| } else if (localName.equals(Constants.SOAP_ARRAY.getLocalPart())) { |
| typeQName = Constants.SOAP_ARRAY; |
| } else if (localName.equals(Constants.SOAP_STRING.getLocalPart())) { |
| typeQName = Constants.SOAP_STRING; |
| } else if (localName.equals(Constants.SOAP_BOOLEAN.getLocalPart())) { |
| typeQName = Constants.SOAP_BOOLEAN; |
| } else if (localName.equals(Constants.SOAP_DOUBLE.getLocalPart())) { |
| typeQName = Constants.SOAP_DOUBLE; |
| } else if (localName.equals(Constants.SOAP_FLOAT.getLocalPart())) { |
| typeQName = Constants.SOAP_FLOAT; |
| } else if (localName.equals(Constants.SOAP_INT.getLocalPart())) { |
| typeQName = Constants.SOAP_INT; |
| } else if (localName.equals(Constants.SOAP_LONG.getLocalPart())) { |
| typeQName = Constants.SOAP_LONG; |
| } else if (localName.equals(Constants.SOAP_SHORT.getLocalPart())) { |
| typeQName = Constants.SOAP_SHORT; |
| } else if (localName.equals(Constants.SOAP_BYTE.getLocalPart())) { |
| typeQName = Constants.SOAP_BYTE; |
| } |
| } |
| |
| // If we still have no luck, check to see if there's an arrayType |
| // (itemType for SOAP 1.2) attribute, in which case this is almost |
| // certainly an array. |
| |
| if (typeQName == null && attrs != null) { |
| String encURI = getSOAPConstants().getEncodingURI(); |
| String itemType = getSOAPConstants().getAttrItemType(); |
| for (int i = 0; i < attrs.getLength(); i++) { |
| if (encURI.equals(attrs.getURI(i)) && |
| itemType.equals(attrs.getLocalName(i))) { |
| return new QName(encURI, "Array"); |
| } |
| } |
| } |
| |
| return typeQName; |
| } |
| |
| /** |
| * Convenenience method that returns true if the value is nil |
| * (due to the xsi:nil) attribute. |
| * @param attrs are the element attributes. |
| * @return true if xsi:nil is true |
| */ |
| public boolean isNil(Attributes attrs) { |
| return JavaUtils.isTrueExplicitly( |
| Constants.getValue(attrs, Constants.QNAMES_NIL), |
| false); |
| } |
| |
| /** |
| * Get a Deserializer which can turn a given xml type into a given |
| * Java type |
| */ |
| public final Deserializer getDeserializer(Class cls, QName xmlType) { |
| if (xmlType == null) |
| return null; |
| |
| DeserializerFactory dserF = null; |
| Deserializer dser = null; |
| try { |
| dserF = (DeserializerFactory) getTypeMapping(). |
| getDeserializer(cls, xmlType); |
| } catch (JAXRPCException e) { |
| log.error(Messages.getMessage("noFactory00", xmlType.toString())); |
| } |
| if (dserF != null) { |
| try { |
| dser = (Deserializer) dserF.getDeserializerAs(Constants.AXIS_SAX); |
| } catch (JAXRPCException e) { |
| log.error(Messages.getMessage("noDeser00", xmlType.toString())); |
| } |
| } |
| return dser; |
| } |
| |
| /** |
| * Convenience method to get the Deserializer for a specific |
| * java class from its meta data. |
| * @param cls is the Class used to find the deserializer |
| * @return Deserializer |
| */ |
| public Deserializer getDeserializerForClass(Class cls) { |
| if (cls == null) { |
| cls = destClass; |
| } |
| if (cls == null) { |
| return null; |
| } |
| // if (cls.isArray()) { |
| // cls = cls.getComponentType(); |
| // } |
| if (javax.xml.rpc.holders.Holder.class.isAssignableFrom(cls)) { |
| try { |
| cls = cls.getField("value").getType(); |
| } catch (Exception e) { |
| } |
| } |
| |
| Deserializer dser = null; |
| |
| QName type = getTypeMapping().getTypeQName(cls); |
| dser = getDeserializer(cls, type); |
| if (dser != null) |
| return dser; |
| |
| try { |
| Method method = |
| MethodCache.getInstance().getMethod(cls, |
| DESERIALIZER_METHOD, |
| DESERIALIZER_CLASSES); |
| if (method != null) { |
| TypeDesc typedesc = TypeDesc.getTypeDescForClass(cls); |
| if (typedesc != null) { |
| dser = (Deserializer) method.invoke(null, |
| new Object[] {getEncodingStyle(), cls, typedesc.getXmlType()}); |
| } |
| } |
| } catch (Exception e) { |
| log.error(Messages.getMessage("noDeser00", cls.getName())); |
| } |
| return dser; |
| } |
| |
| /** |
| * Allows the destination class to be set so that downstream |
| * deserializers like ArrayDeserializer can pick it up when |
| * deserializing its components using getDeserializerForClass |
| * @param destClass is the Class of the component to be deserialized |
| */ |
| public void setDestinationClass(Class destClass) { |
| this.destClass = destClass; |
| } |
| |
| /** |
| * Allows the destination class to be retrieved so that downstream |
| * deserializers like ArrayDeserializer can pick it up when |
| * deserializing its components using getDeserializerForClass |
| * @return the Class of the component to be deserialized |
| */ |
| public Class getDestinationClass() { |
| return destClass; |
| } |
| |
| /** |
| * Convenience method to get the Deserializer for a specific |
| * xmlType. |
| * @param xmlType is QName for a type to deserialize |
| * @return Deserializer |
| */ |
| public final Deserializer getDeserializerForType(QName xmlType) { |
| return getDeserializer(null, xmlType); |
| } |
| |
| /** |
| * Get the TypeMapping for this DeserializationContext |
| */ |
| public TypeMapping getTypeMapping() |
| { |
| if (msgContext == null || msgContext.getTypeMappingRegistry() == null) { |
| return (TypeMapping) new org.apache.axis.encoding.TypeMappingRegistryImpl().getTypeMapping( |
| null); |
| } |
| TypeMappingRegistry tmr = msgContext.getTypeMappingRegistry(); |
| return (TypeMapping) tmr.getTypeMapping(getEncodingStyle()); |
| } |
| |
| /** |
| * Get the TypeMappingRegistry we're using. |
| * @return TypeMapping or null |
| */ |
| public TypeMappingRegistry getTypeMappingRegistry() { |
| return msgContext.getTypeMappingRegistry(); |
| } |
| |
| /** |
| * Get the MessageElement for the indicated id (where id is the #value of an href) |
| * If the MessageElement has not been processed, the MessageElement will |
| * be returned. If the MessageElement has been processed, the actual object |
| * value is stored with the id and this routine will return null. |
| * @param id is the value of an href attribute |
| * @return MessageElement or null |
| */ |
| public MessageElement getElementByID(String id) |
| { |
| if((idMap != null)) { |
| IDResolver resolver = (IDResolver)idMap.get(id); |
| if(resolver != null) { |
| Object ret = resolver.getReferencedObject(id); |
| if (ret instanceof MessageElement) |
| return (MessageElement)ret; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Gets the MessageElement or actual Object value associated with the href value. |
| * The return of a MessageElement indicates that the referenced element has |
| * not been processed. If it is not a MessageElement, the Object is the |
| * actual deserialized value. |
| * In addition, this method is invoked to get Object values via Attachments. |
| * @param href is the value of an href attribute (or an Attachment id) |
| * @return MessageElement other Object or null |
| */ |
| public Object getObjectByRef(String href) { |
| Object ret= null; |
| if(href != null){ |
| if((idMap != null)){ |
| IDResolver resolver = (IDResolver)idMap.get(href); |
| if(resolver != null) |
| ret = resolver.getReferencedObject(href); |
| } |
| if( null == ret && !href.startsWith("#")){ |
| //Could this be an attachment? |
| Message msg= null; |
| if(null != (msg=msgContext.getCurrentMessage())){ |
| Attachments attch= null; |
| if( null != (attch= msg.getAttachmentsImpl())){ |
| try{ |
| ret= attch.getAttachmentByReference(href); |
| }catch(AxisFault e){ |
| throw new RuntimeException(e.toString() + JavaUtils.stackToString(e)); |
| } |
| } |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Add the object associated with this id (where id is the value of an id= attribute, |
| * i.e. it does not start with #). |
| * This routine is called to associate the deserialized object |
| * with the id specified on the XML element. |
| * @param id (id name without the #) |
| * @param obj is the deserialized object for this id. |
| */ |
| public void addObjectById(String id, Object obj) |
| { |
| // The resolver uses the href syntax as the key. |
| String idStr = '#' + id; |
| if ((idMap == null) || (id == null)) |
| return ; |
| |
| IDResolver resolver = (IDResolver)idMap.get(idStr); |
| if (resolver == null) |
| return ; |
| |
| resolver.addReferencedObject(idStr, obj); |
| return; |
| } |
| |
| /** |
| * During deserialization, an element with an href=#id<int> |
| * may be encountered before the element defining id=id<int> is |
| * read. In these cases, the getObjectByRef method above will |
| * return null. The deserializer is placed in a table keyed |
| * by href (a fixup table). After the element id is processed, |
| * the deserializer is informed of the value so that it can |
| * update its target(s) with the value. |
| * @param href (#id syntax) |
| * @param dser is the deserializer of the element |
| */ |
| public void registerFixup(String href, Deserializer dser) |
| { |
| if (fixups == null) |
| fixups = new HashMap(); |
| |
| Deserializer prev = (Deserializer) fixups.put(href, dser); |
| |
| // There could already be a deserializer in the fixup list |
| // for this href. If so, the easiest way to get all of the |
| // targets updated is to move the previous deserializers |
| // targets to dser. |
| if (prev != null && prev != dser) { |
| dser.moveValueTargets(prev); |
| if (dser.getDefaultType() == null) { |
| dser.setDefaultType(prev.getDefaultType()); |
| } |
| } |
| } |
| |
| /** |
| * Register the MessageElement with this id (where id is id= form without the #) |
| * This routine is called when the MessageElement with an id is read. |
| * If there is a Deserializer in our fixup list (described above), |
| * the 'fixup' deserializer is given to the MessageElement. When the |
| * MessageElement is completed, the 'fixup' deserializer is informed and |
| * it can set its targets. |
| * @param id (id name without the #) |
| * @param elem is the MessageElement |
| */ |
| public void registerElementByID(String id, MessageElement elem) |
| { |
| if (localIDs == null) |
| localIDs = new LocalIDResolver(); |
| |
| String absID = '#' + id; |
| |
| localIDs.addReferencedObject(absID, elem); |
| |
| registerResolverForID(absID, localIDs); |
| |
| if (fixups != null) { |
| Deserializer dser = (Deserializer)fixups.get(absID); |
| if (dser != null) { |
| elem.setFixupDeserializer(dser); |
| } |
| } |
| } |
| |
| /** |
| * Each id can have its own kind of resolver. This registers a |
| * resolver for the id. |
| */ |
| public void registerResolverForID(String id, IDResolver resolver) |
| { |
| if ((id == null) || (resolver == null)) { |
| // ??? Throw nullPointerException? |
| return; |
| } |
| |
| if (idMap == null) |
| idMap = new HashMap(); |
| |
| idMap.put(id, resolver); |
| } |
| |
| /** |
| * Return true if any ids are being tracked by this DeserializationContext |
| * |
| * @return true if any ides are being tracked by this DeserializationContext |
| */ |
| public boolean hasElementsByID() |
| { |
| return idMap == null ? false : idMap.size() > 0; |
| } |
| |
| /** |
| * Get the current position in the record. |
| */ |
| public int getCurrentRecordPos() |
| { |
| if (recorder == null) return -1; |
| return recorder.getLength() - 1; |
| } |
| |
| /** |
| * Get the start of the mapping position |
| */ |
| public int getStartOfMappingsPos() |
| { |
| if (startOfMappingsPos == -1) { |
| return getCurrentRecordPos() + 1; |
| } |
| |
| return startOfMappingsPos; |
| } |
| |
| /** |
| * Push the MessageElement into the recorder |
| */ |
| public void pushNewElement(MessageElement elem) |
| { |
| if (debugEnabled) { |
| log.debug("Pushing element " + elem.getName()); |
| } |
| |
| if (!doneParsing && (recorder != null)) { |
| recorder.newElement(elem); |
| } |
| |
| try { |
| if(curElement != null) |
| elem.setParentElement(curElement); |
| } catch (Exception e) { |
| /* |
| * The only checked exception that may be thrown from setParent |
| * occurs if the parent already has an explicit object value, |
| * which should never occur during deserialization. |
| */ |
| log.fatal(Messages.getMessage("exception00"), e); |
| } |
| curElement = elem; |
| |
| if (elem.getRecorder() != recorder) |
| recorder = elem.getRecorder(); |
| } |
| |
| /**************************************************************** |
| * Management of sub-handlers (deserializers) |
| */ |
| |
| public void pushElementHandler(SOAPHandler handler) |
| { |
| if (debugEnabled) { |
| log.debug(Messages.getMessage("pushHandler00", "" + handler)); |
| } |
| |
| if (topHandler != null) pushedDownHandlers.add(topHandler); |
| topHandler = handler; |
| } |
| |
| /** Replace the handler at the top of the stack. |
| * |
| * This is only used when we have a placeholder Deserializer |
| * for a referenced object which doesn't know its type until we |
| * hit the referent. |
| */ |
| public void replaceElementHandler(SOAPHandler handler) |
| { |
| topHandler = handler; |
| } |
| |
| public SOAPHandler popElementHandler() |
| { |
| SOAPHandler result = topHandler; |
| |
| int size = pushedDownHandlers.size(); |
| if (size > 0) { |
| topHandler = (SOAPHandler) pushedDownHandlers.remove(size-1); |
| } else { |
| topHandler = null; |
| } |
| |
| if (debugEnabled) { |
| if (result == null) { |
| log.debug(Messages.getMessage("popHandler00", "(null)")); |
| } else { |
| log.debug(Messages.getMessage("popHandler00", "" + result)); |
| } |
| } |
| |
| return result; |
| } |
| |
| boolean processingRef = false; |
| public void setProcessingRef(boolean ref) { |
| processingRef = ref; |
| } |
| public boolean isProcessingRef() { |
| return processingRef; |
| } |
| |
| /**************************************************************** |
| * SAX event handlers |
| */ |
| public void startDocument() throws SAXException { |
| // Should never receive this in the midst of a parse. |
| if (!doneParsing && (recorder != null)) |
| recorder.startDocument(); |
| } |
| |
| /** |
| * endDocument is invoked at the end of the document. |
| */ |
| public void endDocument() throws SAXException { |
| if (debugEnabled) { |
| log.debug("Enter: DeserializationContext::endDocument()"); |
| } |
| if (!doneParsing && (recorder != null)) |
| recorder.endDocument(); |
| |
| doneParsing = true; |
| |
| if (debugEnabled) { |
| log.debug("Exit: DeserializationContext::endDocument()"); |
| } |
| } |
| /** |
| * Return if done parsing document. |
| */ |
| public boolean isDoneParsing() {return doneParsing;} |
| |
| /** Record the current set of prefix mappings in the nsMappings table. |
| * |
| * !!! We probably want to have this mapping be associated with the |
| * MessageElements, since they may potentially need access to them |
| * long after the end of the prefix mapping here. (example: |
| * when we need to record a long string of events scanning forward |
| * in the document to find an element with a particular ID.) |
| */ |
| public void startPrefixMapping(String prefix, String uri) |
| throws SAXException |
| { |
| if (debugEnabled) { |
| log.debug("Enter: DeserializationContext::startPrefixMapping(" + prefix + ", " + uri + ")"); |
| } |
| |
| if (!doneParsing && (recorder != null)) { |
| recorder.startPrefixMapping(prefix, uri); |
| } |
| |
| if (startOfMappingsPos == -1) { |
| namespaces.push(); |
| startOfMappingsPos = getCurrentRecordPos(); |
| } |
| |
| if (prefix != null) { |
| namespaces.add(uri, prefix); |
| } else { |
| namespaces.add(uri, ""); |
| } |
| |
| if (!haveSeenSchemaNS && msgContext != null) { |
| // If we haven't yet seen a schema namespace, check if this |
| // is one. If so, set the SchemaVersion appropriately. |
| // Hopefully the schema def is on the outermost element so we |
| // get this over with quickly. |
| for (int i = 0; !haveSeenSchemaNS && i < schemaVersions.length; |
| i++) { |
| SchemaVersion schemaVersion = schemaVersions[i]; |
| if (uri.equals(schemaVersion.getXsdURI()) || |
| uri.equals(schemaVersion.getXsiURI())) { |
| msgContext.setSchemaVersion(schemaVersion); |
| haveSeenSchemaNS = true; |
| } |
| } |
| } |
| |
| if (topHandler != null) { |
| topHandler.startPrefixMapping(prefix, uri); |
| } |
| |
| if (debugEnabled) { |
| log.debug("Exit: DeserializationContext::startPrefixMapping()"); |
| } |
| } |
| |
| public void endPrefixMapping(String prefix) |
| throws SAXException |
| { |
| if (debugEnabled) { |
| log.debug("Enter: DeserializationContext::endPrefixMapping(" + prefix + ")"); |
| } |
| |
| if (!doneParsing && (recorder != null)) { |
| recorder.endPrefixMapping(prefix); |
| } |
| |
| if (topHandler != null) { |
| topHandler.endPrefixMapping(prefix); |
| } |
| |
| if (debugEnabled) { |
| log.debug("Exit: DeserializationContext::endPrefixMapping()"); |
| } |
| } |
| |
| public void setDocumentLocator(Locator locator) |
| { |
| // We don't store the Locator because we don't need it. In addition it is typically |
| // a reference to some internal object of the parser and not keeping that reference |
| // ensures that this object (which may be heavyweight) can be garbage collected |
| // early (see AXIS-2863 for an issue that may be related to this: in that case, |
| // Locator is implemented by oracle.xml.parser.v2.XMLReader). |
| } |
| |
| public void characters(char[] p1, int p2, int p3) throws SAXException { |
| if (!doneParsing && (recorder != null)) { |
| recorder.characters(p1, p2, p3); |
| } |
| if (topHandler != null) { |
| topHandler.characters(p1, p2, p3); |
| } |
| } |
| |
| public void ignorableWhitespace(char[] p1, int p2, int p3) throws SAXException { |
| if (!doneParsing && (recorder != null)) { |
| recorder.ignorableWhitespace(p1, p2, p3); |
| } |
| if (topHandler != null) { |
| topHandler.ignorableWhitespace(p1, p2, p3); |
| } |
| } |
| |
| public void processingInstruction(String p1, String p2) throws SAXException { |
| // must throw an error since SOAP 1.1 doesn't allow |
| // processing instructions anywhere in the message |
| throw new SAXException(Messages.getMessage("noInstructions00")); |
| } |
| |
| public void skippedEntity(String p1) throws SAXException { |
| if (!doneParsing && (recorder != null)) { |
| recorder.skippedEntity(p1); |
| } |
| topHandler.skippedEntity(p1); |
| } |
| |
| /** |
| * startElement is called when an element is read. This is the big work-horse. |
| * |
| * This guy also handles monitoring the recording depth if we're recording |
| * (so we know when to stop). |
| */ |
| public void startElement(String namespace, String localName, |
| String qName, Attributes attributes) |
| throws SAXException |
| { |
| if (debugEnabled) { |
| log.debug("Enter: DeserializationContext::startElement(" + namespace + ", " + localName + ")"); |
| } |
| |
| if (attributes == null || attributes.getLength() == 0) { |
| attributes = NullAttributes.singleton; |
| } else { |
| attributes = new AttributesImpl(attributes); |
| |
| SOAPConstants soapConstants = getSOAPConstants(); |
| if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) { |
| if (attributes.getValue(soapConstants.getAttrHref()) != null && |
| attributes.getValue(Constants.ATTR_ID) != null) { |
| |
| AxisFault fault = new AxisFault(Constants.FAULT_SOAP12_SENDER, |
| null, Messages.getMessage("noIDandHREFonSameElement"), null, null, null); |
| |
| throw new SAXException(fault); |
| |
| } |
| } |
| |
| } |
| |
| SOAPHandler nextHandler = null; |
| |
| String prefix = ""; |
| int idx = qName.indexOf(':'); |
| if (idx > 0) { |
| prefix = qName.substring(0, idx); |
| } |
| |
| if (topHandler != null) { |
| nextHandler = topHandler.onStartChild(namespace, |
| localName, |
| prefix, |
| attributes, |
| this); |
| } |
| |
| if (nextHandler == null) { |
| nextHandler = new SOAPHandler(); |
| } |
| |
| pushElementHandler(nextHandler); |
| |
| nextHandler.startElement(namespace, localName, prefix, |
| attributes, this); |
| |
| if (!doneParsing && (recorder != null)) { |
| recorder.startElement(namespace, localName, qName, |
| attributes); |
| if (!doneParsing) { |
| curElement.setContentsIndex(recorder.getLength()); |
| } |
| } |
| |
| if (startOfMappingsPos != -1) { |
| startOfMappingsPos = -1; |
| } else { |
| // Push an empty frame if there are no mappings |
| namespaces.push(); |
| } |
| |
| if (debugEnabled) { |
| log.debug("Exit: DeserializationContext::startElement()"); |
| } |
| } |
| |
| /** |
| * endElement is called at the end tag of an element |
| */ |
| public void endElement(String namespace, String localName, String qName) |
| throws SAXException |
| { |
| if (debugEnabled) { |
| log.debug("Enter: DeserializationContext::endElement(" + namespace + ", " + localName + ")"); |
| } |
| |
| if (!doneParsing && (recorder != null)) { |
| recorder.endElement(namespace, localName, qName); |
| } |
| |
| try { |
| SOAPHandler handler = popElementHandler(); |
| handler.endElement(namespace, localName, this); |
| |
| if (topHandler != null) { |
| topHandler.onEndChild(namespace, localName, this); |
| } else { |
| // We should be done! |
| } |
| |
| } finally { |
| if (curElement != null) { |
| curElement = (MessageElement)curElement.getParentElement(); |
| } |
| |
| namespaces.pop(); |
| |
| if (debugEnabled) { |
| String name = curElement != null ? |
| curElement.getClass().getName() + ":" + |
| curElement.getName() : null; |
| log.debug("Popped element stack to " + name); |
| log.debug("Exit: DeserializationContext::endElement()"); |
| } |
| } |
| } |
| |
| /** |
| * This class is used to map ID's to an actual value Object or Message |
| */ |
| private static class LocalIDResolver implements IDResolver |
| { |
| HashMap idMap = null; |
| |
| /** |
| * Add object associated with id |
| */ |
| public void addReferencedObject(String id, Object referent) |
| { |
| if (idMap == null) { |
| idMap = new HashMap(); |
| } |
| |
| idMap.put(id, referent); |
| } |
| |
| /** |
| * Get object referenced by href |
| */ |
| public Object getReferencedObject(String href) |
| { |
| if ((idMap == null) || (href == null)) { |
| return null; |
| } |
| return idMap.get(href); |
| } |
| } |
| |
| public void startDTD(java.lang.String name, |
| java.lang.String publicId, |
| java.lang.String systemId) |
| throws SAXException |
| { |
| /* It is possible for a malicious user to send us bad stuff in |
| the <!DOCTYPE ../> tag that will cause a denial of service |
| Example: |
| <?xml version="1.0" ?> |
| <!DOCTYPE foobar [ |
| <!ENTITY x0 "hello"> |
| <!ENTITY x1 "&x0;&x0;"> |
| <!ENTITY x2 "&x1;&x1;"> |
| ... |
| <!ENTITY x99 "&x98;&x98;"> |
| <!ENTITY x100 "&x99;&x99;"> |
| ]> |
| */ |
| throw new SAXException(Messages.getMessage("noInstructions00")); |
| /* if (recorder != null) |
| recorder.startDTD(name, publicId, systemId); |
| */ |
| } |
| |
| public void notationDecl(String name, String publicId, String systemId) throws SAXException { |
| // Do nothing; we never get here |
| } |
| |
| public void unparsedEntityDecl(String name, String publicId, String systemId, |
| String notationName) throws SAXException { |
| // Do nothing; we never get here |
| } |
| |
| public void endDTD() |
| throws SAXException |
| { |
| if (recorder != null) |
| recorder.endDTD(); |
| } |
| |
| public void startEntity(java.lang.String name) |
| throws SAXException |
| { |
| if (recorder != null) |
| recorder.startEntity(name); |
| } |
| |
| public void endEntity(java.lang.String name) |
| throws SAXException |
| { |
| if (recorder != null) |
| recorder.endEntity(name); |
| } |
| |
| public void startCDATA() |
| throws SAXException |
| { |
| if (recorder != null) |
| recorder.startCDATA(); |
| } |
| |
| public void endCDATA() |
| throws SAXException |
| { |
| if (recorder != null) |
| recorder.endCDATA(); |
| } |
| |
| public void comment(char[] ch, |
| int start, |
| int length) |
| throws SAXException |
| { |
| if (recorder != null) |
| recorder.comment(ch, start, length); |
| } |
| |
| /** We only need one instance of this dummy handler to set into the parsers. */ |
| private static final NullLexicalHandler nullLexicalHandler = new NullLexicalHandler(); |
| |
| /** |
| * It is illegal to set the lexical-handler property to null. To facilitate |
| * discarding the heavily loaded instance of DeserializationContextImpl from |
| * the SAXParser instance that is kept in the Stack maintained by XMLUtils |
| * we use this class. |
| */ |
| private static class NullLexicalHandler implements LexicalHandler { |
| public void startDTD(String arg0, String arg1, String arg2) throws SAXException {} |
| public void endDTD() throws SAXException {} |
| public void startEntity(String arg0) throws SAXException {} |
| public void endEntity(String arg0) throws SAXException {} |
| public void startCDATA() throws SAXException {} |
| public void endCDATA() throws SAXException {} |
| public void comment(char[] arg0, int arg1, int arg2) throws SAXException {} |
| } |
| } |
| |