Java tutorial
/* * 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.Constants; import org.apache.axis.Part; import org.apache.axis.components.logger.LogFactory; import org.apache.axis.message.EnvelopeHandler; import org.apache.axis.message.MessageElement; import org.apache.axis.message.SAX2EventRecorder; import org.apache.axis.message.SAXOutputter; import org.apache.axis.message.SOAPHandler; import org.apache.axis.utils.Messages; import org.apache.axis.soap.SOAPConstants; import org.apache.commons.logging.Log; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.namespace.QName; import java.io.StringWriter; import java.util.HashSet; import java.util.Vector; /** The Deserializer base class. * * @author Glen Daniels (gdaniels@allaire.com) * Re-architected for JAX-RPC Compliance by * @author Rich Scheuerle (sche@us.ibm.com) */ public class DeserializerImpl extends SOAPHandler implements javax.xml.rpc.encoding.Deserializer, Deserializer, Callback { protected static Log log = LogFactory.getLog(DeserializerImpl.class.getName()); protected Object value = null; // 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(); // isEnded is set when the endElement is called protected boolean isEnded = false; protected Vector targets = null; protected QName defaultType = null; protected boolean componentsReadyFlag = false; /** * A set of sub-deserializers whose values must complete before our * value is complete. */ private HashSet activeDeserializers = new HashSet(); protected boolean isHref = false; protected boolean isNil = false; // xsd:nil attribute is set to true protected String id = null; // Set to the id of the element public DeserializerImpl() { } /** * JAX-RPC compliant method which returns mechanism type. */ public String getMechanismType() { return Constants.AXIS_SAX; } /** * Get the deserialized value. * @return Object representing deserialized value or null */ public Object getValue() { return value; } /** * Set the deserialized value. * @param value Object representing deserialized value */ public void setValue(Object value) { this.value = value; } /** * If the deserializer has component values (like ArrayDeserializer) * this method gets the specific component via the hint. * The default implementation returns null. * @return Object representing deserialized value or null */ public Object getValue(Object hint) { return null; } /** * If the deserializer has component values (like ArrayDeserializer) * this method sets the specific component via the hint. * The default implementation does nothing. * @param hint Object representing deserialized value or null */ public void setChildValue(Object value, Object hint) throws SAXException { } public void setValue(Object value, Object hint) throws SAXException { if (hint instanceof Deserializer) { // This one's done activeDeserializers.remove(hint); // If we're past the end of our XML, and this is the last one, // our value has been assembled completely. if (componentsReady()) { // Got everything we need, call valueComplete() valueComplete(); } } } /** * In some circumstances an element may not have * a type attribute, but a default type qname is known from * information in the container. For example, * an element of an array may not have a type= attribute, * so the default qname is the component type of the array. * This method is used to communicate the default type information * to the deserializer. */ public void setDefaultType(QName qName) { defaultType = qName; } public QName getDefaultType() { return defaultType; } /** * For deserializers of non-primitives, the value may not be * known until later (due to multi-referencing). In such * cases the deserializer registers Target object(s). When * the value is known, the set(value) will be invoked for * each Target registered with the Deserializer. The Target * object abstracts the function of setting a target with a * value. See the Target interface for more info. * @param target */ public void registerValueTarget(Target target) { if (targets == null) { targets = new Vector(); } targets.addElement(target); } /** * Get the Value Targets of the Deserializer. * @return Vector of Target objects or null */ public Vector getValueTargets() { return targets; } /** * Remove the Value Targets of the Deserializer. */ public void removeValueTargets() { if (targets != null) { targets = null; } } /** * Move someone else's targets to our own (see DeserializationContext) * * The DeserializationContext only allows one Deserializer to * wait for a unknown multi-ref'ed value. So to ensure * that all of the targets are updated, this method is invoked * to copy the Target objects to the waiting Deserializer. * @param other is the Deserializer to copy targets from. */ public void moveValueTargets(Deserializer other) { if ((other == null) || (other.getValueTargets() == null)) { return; } if (targets == null) { targets = new Vector(); } targets.addAll(other.getValueTargets()); other.removeValueTargets(); } /** * Some deserializers (ArrayDeserializer) require * all of the component values to be known before the * value is complete. * (For the ArrayDeserializer this is important because * the elements are stored in an ArrayList, and all values * must be known before the ArrayList is converted into the * expected array. * * This routine is used to indicate when the components are ready. * The default (true) is useful for most Deserializers. */ public boolean componentsReady() { return (componentsReadyFlag || (!isHref && isEnded && activeDeserializers.isEmpty())); } /** * The valueComplete() method is invoked when the * end tag of the element is read. This results * in the setting of all registered Targets (see * registerValueTarget). * Note that the valueComplete() only processes * the Targets if componentReady() returns true. * So if you override componentReady(), then your * specific Deserializer will need to call valueComplete() * when your components are ready (See ArrayDeserializer) */ public void valueComplete() throws SAXException { if (componentsReady()) { if (targets != null) { for (int i = 0; i < targets.size(); i++) { Target target = (Target) targets.get(i); target.set(value); if (debugEnabled) { log.debug(Messages.getMessage("setValueInTarget00", "" + value, "" + target)); } } // Don't need targets any more, so clear them removeValueTargets(); } } } public void addChildDeserializer(Deserializer dSer) { // Keep track of our active deserializers. This enables us to figure // out whether or not we're really done in the case where we get to // our end tag, but still have open hrefs for members. if (activeDeserializers != null) { activeDeserializers.add(dSer); } // In concert with the above, we make sure each field deserializer // lets us know when it's done so we can take it off our list. dSer.registerValueTarget(new CallbackTarget(this, dSer)); } /** * Subclasses may override these */ /** * This method is invoked when an element start tag is encountered. * DeserializerImpl provides default behavior, which involves the following: * - directly handling the deserialization of a nill value * - handling the registration of the id value. * - handling the registration of a fixup if this element is an href. * - calling onStartElement to do the actual deserialization if not nill or href cases. * @param namespace is the namespace of the element * @param localName is the name of the element * @param prefix is the prefix of the element * @param attributes are the attributes on the element...used to get the type * @param context is the DeserializationContext * * Normally a specific Deserializer (FooDeserializer) should extend DeserializerImpl. * Here is the flow that will occur in such cases: * 1) DeserializerImpl.startElement(...) will be called and do the id/href/nill stuff. * 2) If real deserialization needs to take place DeserializerImpl.onStartElement will be * invoked, which will attempt to install the specific Deserializer (FooDeserializer) * 3) The FooDeserializer.startElement(...) will be called to do the Foo specific stuff. * This results in a call to FooDeserializer.onStartElement(...) if startElement was * not overridden. * 4) The onChildElement(...) method is called for each child element. Nothing occurs * if not overridden. The FooDeserializer.onStartChild(...) method should return * the deserializer for the child element. * 5) When the end tag is reached, the endElement(..) method is invoked. The default * behavior is to handle hrefs/ids, call onEndElement and then call the Deserializer * valueComplete method. * * So the methods that you potentially want to override are: * onStartElement, onStartChild, componentsReady, setValue(object, hint) * You probably should not override startElement or endElement. * If you need specific behaviour at the end of the element consider overriding * onEndElement. * * See the pre-existing Deserializers for more information. */ public void startElement(String namespace, String localName, String prefix, Attributes attributes, DeserializationContext context) throws SAXException { super.startElement(namespace, localName, prefix, attributes, context); // If the nil attribute is present and true, set the value to null // and return since there is nothing to deserialize. if (context.isNil(attributes)) { value = null; isNil = true; return; } SOAPConstants soapConstants = context.getSOAPConstants(); // If this element has an id, then associate the value with the id. // (Prior to this association, the MessageElement of the element is // associated with the id. Failure to replace the MessageElement at this // point will cause an infinite loop during deserialization if the // current element contains child elements that cause an href back to this id.) // Also note that that endElement() method is responsible for the final // association of this id with the completed value. id = attributes.getValue("id"); if (id != null) { context.addObjectById(id, value); if (debugEnabled) { log.debug(Messages.getMessage("deserInitPutValueDebug00", "" + value, id)); } context.registerFixup("#" + id, this); } String href = attributes.getValue(soapConstants.getAttrHref()); if (href != null) { isHref = true; Object ref = context.getObjectByRef(href); if (debugEnabled) { log.debug(Messages.getMessage("gotForID00", new String[] { "" + ref, href, (ref == null ? "*null*" : ref.getClass().toString()) })); } if (ref == null) { // Nothing yet... register for later interest. context.registerFixup(href, this); return; } if (ref instanceof MessageElement) { context.replaceElementHandler(new EnvelopeHandler(this)); SAX2EventRecorder r = context.getRecorder(); context.setRecorder(null); ((MessageElement) ref).publishToHandler((DefaultHandler) context); context.setRecorder(r); } else { if (!href.startsWith("#") && defaultType != null && ref instanceof Part) { //For attachments this is the end of the road-- invoke deserializer Deserializer dser = context.getDeserializerForType(defaultType); if (null != dser) { dser.startElement(namespace, localName, prefix, attributes, context); ref = dser.getValue(); } } // If the ref is not a MessageElement, then it must be an // element that has already been deserialized. Use it directly. value = ref; componentsReadyFlag = true; valueComplete(); } } else { isHref = false; onStartElement(namespace, localName, prefix, attributes, context); } } /** * This method is invoked after startElement when the element requires * deserialization (i.e. the element is not an href and the value is not nil.) * DeserializerImpl provides default behavior, which simply * involves obtaining a correct Deserializer and plugging its handler. * @param namespace is the namespace of the element * @param localName is the name of the element * @param prefix is the prefix of the element * @param attributes are the attributes on the element...used to get the type * @param context is the DeserializationContext */ public void onStartElement(String namespace, String localName, String prefix, Attributes attributes, DeserializationContext context) throws SAXException { // If I'm the base class, try replacing myself with an // appropriate deserializer gleaned from type info. if (this.getClass().equals(DeserializerImpl.class)) { QName type = context.getTypeFromAttributes(namespace, localName, attributes); // If no type is specified, use the defaultType if available. // xsd:string is used if no type is provided. if (type == null) { type = defaultType; if (type == null) { type = Constants.XSD_STRING; } } if (debugEnabled) { log.debug(Messages.getMessage("gotType00", "Deser", "" + type)); } // We know we're deserializing, but we don't have // a specific deserializer. So create one using the // attribute type qname. if (type != null) { Deserializer dser = context.getDeserializerForType(type); if (dser == null) { dser = context.getDeserializerForClass(null); } if (dser != null) { // Move the value targets to the new deserializer dser.moveValueTargets(this); context.replaceElementHandler((SOAPHandler) dser); // And don't forget to give it the start event... boolean isRef = context.isProcessingRef(); context.setProcessingRef(true); dser.startElement(namespace, localName, prefix, attributes, context); context.setProcessingRef(isRef); } else { throw new SAXException(Messages.getMessage("noDeser00", "" + type)); } } } } /** * onStartChild is called on each child element. * The default behavior supplied by DeserializationImpl is to do nothing. * A specific deserializer may perform other tasks. For example a * BeanDeserializer will construct a deserializer for the indicated * property and return it. * @param namespace is the namespace of the child element * @param localName is the local name of the child element * @param prefix is the prefix used on the name of the child element * @param attributes are the attributes of the child element * @param context is the deserialization context. * @return is a Deserializer to use to deserialize a child (must be * a derived class of SOAPHandler) or null if no deserialization should * be performed. */ public SOAPHandler onStartChild(String namespace, String localName, String prefix, Attributes attributes, DeserializationContext context) throws SAXException { return null; } /** * endElement is called when the end element tag is reached. * It handles href/id information for multi-ref processing * and invokes the valueComplete() method of the deserializer * which sets the targets with the deserialized value. * @param namespace is the namespace of the child element * @param localName is the local name of the child element * @param context is the deserialization context */ public final void endElement(String namespace, String localName, DeserializationContext context) throws SAXException { super.endElement(namespace, localName, context); isEnded = true; if (!isHref) { onEndElement(namespace, localName, context); } // Time to call valueComplete to copy the value to // the targets. First a call is made to componentsReady // to ensure that all components are ready. if (componentsReady()) { valueComplete(); } // If this element has an id, then associate the value with the id. // Subsequent hrefs to the id will obtain the value directly. // This is necessary for proper multi-reference deserialization. if (id != null) { context.addObjectById(id, value); if (debugEnabled) { log.debug(Messages.getMessage("deserPutValueDebug00", "" + value, id)); } } } /** * onEndElement is called by endElement. It is not called * if the element has an href. * @param namespace is the namespace of the child element * @param localName is the local name of the child element * @param context is the deserialization context */ public void onEndElement(String namespace, String localName, DeserializationContext context) throws SAXException { // If we only have SAX events, but someone really wanted a // value, try sending them the contents of this element // as a String... // ??? Is this the right thing to do here? if (this.getClass().equals(DeserializerImpl.class) && targets != null && !targets.isEmpty()) { StringWriter writer = new StringWriter(); SerializationContext serContext = new SerializationContext(writer, context.getMessageContext()); serContext.setSendDecl(false); SAXOutputter so = null; so = new SAXOutputter(serContext); context.getCurElement().publishContents(so); if (!isNil) { value = writer.getBuffer().toString(); } } } }