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 java.io.IOException; import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Stack; import javax.xml.namespace.QName; import javax.xml.rpc.JAXRPCException; import javax.xml.rpc.holders.QNameHolder; import org.apache.axis.AxisEngine; import org.apache.axis.AxisProperties; import org.apache.axis.Constants; import org.apache.axis.Message; import org.apache.axis.MessageContext; import org.apache.axis.attachments.Attachments; import org.apache.axis.client.Call; import org.apache.axis.components.encoding.XMLEncoder; import org.apache.axis.components.encoding.XMLEncoderFactory; import org.apache.axis.components.logger.LogFactory; import org.apache.axis.constants.Use; import org.apache.axis.description.OperationDesc; import org.apache.axis.description.TypeDesc; import org.apache.axis.encoding.ser.ArraySerializer; import org.apache.axis.encoding.ser.BaseSerializerFactory; import org.apache.axis.encoding.ser.SimpleListSerializerFactory; import org.apache.axis.handlers.soap.SOAPService; import org.apache.axis.schema.SchemaVersion; import org.apache.axis.soap.SOAPConstants; import org.apache.axis.types.HexBinary; import org.apache.axis.utils.IDKey; import org.apache.axis.utils.JavaUtils; import org.apache.axis.utils.Mapping; import org.apache.axis.utils.Messages; import org.apache.axis.utils.NSStack; import org.apache.axis.utils.XMLUtils; import org.apache.axis.utils.cache.MethodCache; import org.apache.axis.wsdl.symbolTable.SchemaUtils; import org.apache.axis.wsdl.symbolTable.SymbolTable; import org.apache.axis.wsdl.symbolTable.Utils; import org.apache.commons.logging.Log; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.CharacterData; import org.w3c.dom.Comment; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.xml.sax.Attributes; import org.xml.sax.helpers.AttributesImpl; /** Manage a serialization, including keeping track of namespace mappings * and element stacks. * * @author Glen Daniels (gdaniels@apache.org) * @author Rich Scheuerle <scheu@us.ibm.com> */ public class SerializationContext implements javax.xml.rpc.encoding.SerializationContext { protected static Log log = LogFactory.getLog(SerializationContext.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(); private NSStack nsStack = null; private boolean writingStartTag = false; private boolean onlyXML = true; private int indent = 0; private Stack elementStack = new Stack(); private Writer writer; private int lastPrefixIndex = 1; private MessageContext msgContext; private QName currentXMLType; /** The item QName if we're serializing a literal array... */ private QName itemQName; /** The item type if we're serializing a literal array... */ private QName itemType; /** The SOAP context we're using */ private SOAPConstants soapConstants = SOAPConstants.SOAP11_CONSTANTS; private static QName multirefQName = new QName("", "multiRef"); private static Class[] SERIALIZER_CLASSES = new Class[] { String.class, Class.class, QName.class }; private static final String SERIALIZER_METHOD = "getSerializer"; /** * Should I write out objects as multi-refs? * * !!! For now, this is an all-or-nothing flag. Either ALL objects will * be written in-place as hrefs with the full serialization at the end * of the body, or we'll write everything inline (potentially repeating * serializations of identical objects). */ private boolean doMultiRefs = false; /** * Should I disable the pretty xml completely. */ private boolean disablePrettyXML = false; /** * Should I disable the namespace prefix optimization. */ private boolean enableNamespacePrefixOptimization = false; /** * current setting for pretty */ private boolean pretty = false; /** * Should I send an XML declaration? */ private boolean sendXMLDecl = true; /** * Should I send xsi:type attributes? By default, yes. */ private boolean sendXSIType = true; /** * Send an element with an xsi:nil="true" attribute for null * variables (if Boolean.TRUE), or nothing (if Boolean.FALSE). */ private Boolean sendNull = Boolean.TRUE; /** * A place to hold objects we cache for multi-ref serialization, and * remember the IDs we assigned them. */ private HashMap multiRefValues = null; private int multiRefIndex = -1; private boolean noNamespaceMappings = true; private QName writeXMLType; private XMLEncoder encoder = null; /** The flag whether the XML decl should be written */ protected boolean startOfDocument = true; /** The encoding to serialize */ private String encoding = XMLEncoderFactory.DEFAULT_ENCODING; class MultiRefItem { String id; QName xmlType; Boolean sendType; Object value; MultiRefItem(String id, QName xmlType, Boolean sendType, Object value) { this.id = id; this.xmlType = xmlType; this.sendType = sendType; this.value = value; } } /** * These three variables are necessary to process * multi-level object graphs for multi-ref serialization. * While writing out nested multi-ref objects (via outputMultiRef), we * will fill the secondLevelObjects vector * with any new objects encountered. * The outputMultiRefsFlag indicates whether we are currently within the * outputMultiRef() method (so that serialization() knows to update the * secondLevelObjects vector). * The forceSer variable is the trigger to force actual serialization of the indicated object. */ private HashSet secondLevelObjects = null; private Object forceSer = null; private boolean outputMultiRefsFlag = false; /** * Which schema version are we using? */ SchemaVersion schemaVersion = SchemaVersion.SCHEMA_2001; /** * A list of particular namespace -> prefix mappings we should prefer. * See getPrefixForURI() below. */ HashMap preferredPrefixes = new HashMap(); /** * Construct SerializationContext with associated writer * @param writer java.io.Writer */ public SerializationContext(Writer writer) { this.writer = writer; initialize(); } private void initialize() { // These are the preferred prefixes we'll use instead of the "ns1" // style defaults. MAKE SURE soapConstants IS SET CORRECTLY FIRST! preferredPrefixes.put(soapConstants.getEncodingURI(), Constants.NS_PREFIX_SOAP_ENC); preferredPrefixes.put(Constants.NS_URI_XML, Constants.NS_PREFIX_XML); preferredPrefixes.put(schemaVersion.getXsdURI(), Constants.NS_PREFIX_SCHEMA_XSD); preferredPrefixes.put(schemaVersion.getXsiURI(), Constants.NS_PREFIX_SCHEMA_XSI); preferredPrefixes.put(soapConstants.getEnvelopeURI(), Constants.NS_PREFIX_SOAP_ENV); nsStack = new NSStack(enableNamespacePrefixOptimization); } /** * Construct SerializationContext with associated writer and MessageContext * @param writer java.io.Writer * @param msgContext is the MessageContext */ public SerializationContext(Writer writer, MessageContext msgContext) { this.writer = writer; this.msgContext = msgContext; if (msgContext != null) { soapConstants = msgContext.getSOAPConstants(); // Use whatever schema is associated with this MC schemaVersion = msgContext.getSchemaVersion(); Boolean shouldSendDecl = (Boolean) msgContext.getProperty(AxisEngine.PROP_XML_DECL); if (shouldSendDecl != null) sendXMLDecl = shouldSendDecl.booleanValue(); Boolean shouldSendMultiRefs = (Boolean) msgContext.getProperty(AxisEngine.PROP_DOMULTIREFS); if (shouldSendMultiRefs != null) doMultiRefs = shouldSendMultiRefs.booleanValue(); Boolean shouldDisablePrettyXML = (Boolean) msgContext.getProperty(AxisEngine.PROP_DISABLE_PRETTY_XML); if (shouldDisablePrettyXML != null) disablePrettyXML = shouldDisablePrettyXML.booleanValue(); Boolean shouldDisableNamespacePrefixOptimization = (Boolean) msgContext .getProperty(AxisEngine.PROP_ENABLE_NAMESPACE_PREFIX_OPTIMIZATION); if (shouldDisableNamespacePrefixOptimization != null) { enableNamespacePrefixOptimization = shouldDisableNamespacePrefixOptimization.booleanValue(); } else { enableNamespacePrefixOptimization = JavaUtils.isTrue( AxisProperties.getProperty(AxisEngine.PROP_ENABLE_NAMESPACE_PREFIX_OPTIMIZATION, "true")); } boolean sendTypesDefault = sendXSIType; // A Literal use operation overrides the above settings. Don't // send xsi:type, and don't do multiref in that case. OperationDesc operation = msgContext.getOperation(); if (operation != null) { if (operation.getUse() != Use.ENCODED) { doMultiRefs = false; sendTypesDefault = false; } } else { // A Literal use service also overrides the above settings. SOAPService service = msgContext.getService(); if (service != null) { if (service.getUse() != Use.ENCODED) { doMultiRefs = false; sendTypesDefault = false; } } } // The SEND_TYPE_ATTR and PROP_SEND_XSI options indicate // whether the elements should have xsi:type attributes. // Only turn this off is the user tells us to if (!msgContext.isPropertyTrue(Call.SEND_TYPE_ATTR, sendTypesDefault)) sendXSIType = false; // Don't need this since the above isPropertyTrue should walk up to the engine's // properties...? // // Boolean opt = (Boolean)optionSource.getOption(AxisEngine.PROP_SEND_XSI); // if (opt != null) { // sendXSIType = opt.booleanValue(); // } } else { enableNamespacePrefixOptimization = JavaUtils.isTrue( AxisProperties.getProperty(AxisEngine.PROP_ENABLE_NAMESPACE_PREFIX_OPTIMIZATION, "true")); disablePrettyXML = JavaUtils .isTrue(AxisProperties.getProperty(AxisEngine.PROP_DISABLE_PRETTY_XML, "true")); } // Set up preferred prefixes based on current schema, soap ver, etc. initialize(); } /** * Get whether the serialization should be pretty printed. * @return true/false */ public boolean getPretty() { return pretty; } /** * Indicate whether the serialization should be pretty printed. * @param pretty true/false */ public void setPretty(boolean pretty) { if (!disablePrettyXML) { this.pretty = pretty; } } /** * Are we doing multirefs? * @return true or false */ public boolean getDoMultiRefs() { return doMultiRefs; } /** * Set whether we are doing multirefs */ public void setDoMultiRefs(boolean shouldDo) { doMultiRefs = shouldDo; } /** * Set whether or not we should write XML declarations. * @param sendDecl true/false */ public void setSendDecl(boolean sendDecl) { sendXMLDecl = sendDecl; } /** * Get whether or not to write xsi:type attributes. * @return true/false */ public boolean shouldSendXSIType() { return sendXSIType; } /** * Get the TypeMapping we're using. * @return TypeMapping or null */ public TypeMapping getTypeMapping() { // Always allow the default mappings if (msgContext == null) return DefaultTypeMappingImpl.getSingletonDelegate(); String encodingStyle = msgContext.getEncodingStyle(); if (encodingStyle == null) encodingStyle = soapConstants.getEncodingURI(); return (TypeMapping) msgContext.getTypeMappingRegistry().getTypeMapping(encodingStyle); } /** * Get the TypeMappingRegistry we're using. * @return TypeMapping or null */ public TypeMappingRegistry getTypeMappingRegistry() { if (msgContext == null) return null; return msgContext.getTypeMappingRegistry(); } /** * Get a prefix for a namespace URI. This method will ALWAYS * return a valid prefix - if the given URI is already mapped in this * serialization, we return the previous prefix. If it is not mapped, * we will add a new mapping and return a generated prefix of the form * "ns<num>". * @param uri is the namespace uri * @return prefix */ public String getPrefixForURI(String uri) { return getPrefixForURI(uri, null, false); } /** * Get a prefix for the given namespace URI. If one has already been * defined in this serialization, use that. Otherwise, map the passed * default prefix to the URI, and return that. If a null default prefix * is passed, use one of the form "ns<num>" */ public String getPrefixForURI(String uri, String defaultPrefix) { return getPrefixForURI(uri, defaultPrefix, false); } /** * Get a prefix for the given namespace URI. If one has already been * defined in this serialization, use that. Otherwise, map the passed * default prefix to the URI, and return that. If a null default prefix * is passed, use one of the form "ns<num>" */ public String getPrefixForURI(String uri, String defaultPrefix, boolean attribute) { if ((uri == null) || (uri.length() == 0)) return null; // If we're looking for an attribute prefix, we shouldn't use the // "" prefix, but always register/find one. String prefix = nsStack.getPrefix(uri, attribute); if (prefix == null) { prefix = (String) preferredPrefixes.get(uri); if (prefix == null) { if (defaultPrefix == null) { prefix = "ns" + lastPrefixIndex++; while (nsStack.getNamespaceURI(prefix) != null) { prefix = "ns" + lastPrefixIndex++; } } else { prefix = defaultPrefix; } } registerPrefixForURI(prefix, uri); } return prefix; } /** * Register prefix for the indicated uri * @param prefix * @param uri is the namespace uri */ public void registerPrefixForURI(String prefix, String uri) { if (debugEnabled) { log.debug(Messages.getMessage("register00", prefix, uri)); } if ((uri != null) && (prefix != null)) { if (noNamespaceMappings) { nsStack.push(); noNamespaceMappings = false; } String activePrefix = nsStack.getPrefix(uri, true); if (activePrefix == null || !activePrefix.equals(prefix)) { nsStack.add(uri, prefix); } } } /** * Return the current message */ public Message getCurrentMessage() { if (msgContext == null) return null; return msgContext.getCurrentMessage(); } /** * Get the MessageContext we're operating with */ 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.DEFAULT.getEncoding() : msgContext.getEncodingStyle(); } /** * Returns whether this context should be encoded or not. * * @return a <code>boolean</code> value */ public boolean isEncoded() { return Constants.isSOAP_ENC(getEncodingStyle()); } /** * Convert QName to a string of the form <prefix>:<localpart> * @param qName * @return prefixed qname representation for serialization. */ public String qName2String(QName qName, boolean writeNS) { String prefix = null; String namespaceURI = qName.getNamespaceURI(); String localPart = qName.getLocalPart(); if (localPart != null && localPart.length() > 0) { int index = localPart.indexOf(':'); if (index != -1) { prefix = localPart.substring(0, index); if (prefix.length() > 0 && !prefix.equals("urn")) { registerPrefixForURI(prefix, namespaceURI); localPart = localPart.substring(index + 1); } else { prefix = null; } } localPart = Utils.getLastLocalPart(localPart); } if (namespaceURI.length() == 0) { if (writeNS) { // If this is unqualified (i.e. prefix ""), set the default // namespace to "" String defaultNS = nsStack.getNamespaceURI(""); if (defaultNS != null && defaultNS.length() > 0) { registerPrefixForURI("", ""); } } } else { prefix = getPrefixForURI(namespaceURI); } if ((prefix == null) || (prefix.length() == 0)) return localPart; return prefix + ':' + localPart; } public String qName2String(QName qName) { return qName2String(qName, false); } /** * Convert attribute QName to a string of the form <prefix>:<localpart> * There are slightly different rules for attributes: * - There is no default namespace * - any attribute in a namespace must have a prefix * * @param qName QName * @return prefixed qname representation for serialization. */ public String attributeQName2String(QName qName) { String prefix = null; String uri = qName.getNamespaceURI(); if (uri.length() > 0) { prefix = getPrefixForURI(uri, null, true); } if ((prefix == null) || (prefix.length() == 0)) return qName.getLocalPart(); return prefix + ':' + qName.getLocalPart(); } /** * Get the QName associated with the specified class. * @param cls Class of an object requiring serialization. * @return appropriate QName associated with the class. */ public QName getQNameForClass(Class cls) { return getTypeMapping().getTypeQName(cls); } /** * Indicates whether the object should be interpretted as a primitive * for the purposes of multi-ref processing. A primitive value * is serialized directly instead of using id/href pairs. Thus * primitive serialization/deserialization is slightly faster. * @param value to be serialized * @return true/false */ public boolean isPrimitive(Object value) { if (value == null) return true; Class javaType = value.getClass(); if (javaType.isPrimitive()) return true; if (javaType == String.class) return true; if (Calendar.class.isAssignableFrom(javaType)) return true; if (Date.class.isAssignableFrom(javaType)) return true; if (HexBinary.class.isAssignableFrom(javaType)) return true; if (Element.class.isAssignableFrom(javaType)) return true; if (javaType == byte[].class) return true; // There has been discussion as to whether arrays themselves should // be regarded as multi-ref. // Here are the three options: // 1) Arrays are full-fledged Objects and therefore should always be // multi-ref'd (Pro: This is like java. Con: Some runtimes don't // support this yet, and it requires more stuff to be passed over the wire.) // 2) Arrays are not full-fledged Objects and therefore should // always be passed as single ref (note the elements of the array // may be multi-ref'd.) (Pro: This seems reasonable, if a user // wants multi-referencing put the array in a container. Also // is more interop compatible. Con: Not like java serialization.) // 3) Arrays of primitives should be single ref, and arrays of // non-primitives should be multi-ref. (Pro: Takes care of the // looping case. Con: Seems like an obtuse rule.) // // Changing the code from (1) to (2) to see if interop fairs better. if (javaType.isArray()) return true; // Note that java.lang wrapper classes (i.e. java.lang.Integer) are // not primitives unless the corresponding type is an xsd type. // (If the wrapper maps to a soap encoded primitive, it can be nillable // and multi-ref'd). QName qName = getQNameForClass(javaType); if (qName != null && Constants.isSchemaXSD(qName.getNamespaceURI())) { if (SchemaUtils.isSimpleSchemaType(qName)) { return true; } } return false; } /** * Serialize the indicated value as an element with the name * indicated by elemQName. * The attributes are additional attribute to be serialized on the element. * The value is the object being serialized. (It may be serialized * directly or serialized as an mult-ref'd item) * The value is an Object, which may be a wrapped primitive, the * javaType is the actual unwrapped object type. * xsi:type is set by using the javaType to * find an appopriate xmlType from the TypeMappingRegistry. * Null values and the xsi:type flag will be sent or not depending * on previous configuration of this SerializationContext. * @param elemQName is the QName of the element * @param attributes are additional attributes * @param value is the object to serialize */ public void serialize(QName elemQName, Attributes attributes, Object value) throws IOException { serialize(elemQName, attributes, value, null, null, null, null); } /** * Serialize the indicated value as an element with the name * indicated by elemQName. * The attributes are additional attribute to be serialized on the element. * The value is the object being serialized. (It may be serialized * directly or serialized as an mult-ref'd item) * The value is an Object, which may be a wrapped primitive, the * javaType is the actual unwrapped object type. * The xmlType is the QName of the type that is used to set * xsi:type. If not specified, xsi:type is set by using the javaType to * find an appopriate xmlType from the TypeMappingRegistry. * Null values and the xsi:type flag will be sent or not depending * on previous configuration of this SerializationContext. * @param elemQName is the QName of the element * @param attributes are additional attributes * @param value is the object to serialize * @param xmlType is the qname of the type or null. * @deprecated use serialize(QName, Attributes, Object, QName, Class) instead */ public void serialize(QName elemQName, Attributes attributes, Object value, QName xmlType) throws IOException { serialize(elemQName, attributes, value, xmlType, null, null, null); } /** * Serialize the indicated value as an element with the name * indicated by elemQName. * The attributes are additional attribute to be serialized on the element. * The value is the object being serialized. (It may be serialized * directly or serialized as an mult-ref'd item) * The value is an Object, which may be a wrapped primitive, the * javaType is the actual unwrapped object type. * The xmlType is the QName of the type that is used to set * xsi:type. If not specified, xsi:type is set by using the javaType to * find an appopriate xmlType from the TypeMappingRegistry. * Null values and the xsi:type flag will be sent or not depending * on previous configuration of this SerializationContext. * @param elemQName is the QName of the element * @param attributes are additional attributes * @param value is the object to serialize * @param xmlType is the qname of the type or null. * @param javaType is the java type of the value */ public void serialize(QName elemQName, Attributes attributes, Object value, QName xmlType, Class javaType) throws IOException { serialize(elemQName, attributes, value, xmlType, javaType, null, null); } /** * Serialize the indicated value as an element with the name * indicated by elemQName. * The attributes are additional attribute to be serialized on the element. * The value is the object being serialized. (It may be serialized * directly or serialized as an mult-ref'd item) * The value is an Object, which may be a wrapped primitive. * The xmlType (if specified) is the QName of the type that is used to set * xsi:type. * The sendNull flag indicates whether null values should be sent over the * wire (default is to send such values with xsi:nil="true"). * The sendType flag indicates whether the xsi:type flag should be sent * (default is true). * @param elemQName is the QName of the element * @param attributes are additional attributes * @param value is the object to serialize * @param xmlType is the qname of the type or null. * @param sendNull determines whether to send null values. * @param sendType determines whether to set xsi:type attribute. * * @deprecated use serialize(QName, Attributes, Object, QName, * Boolean, Boolean) instead. */ public void serialize(QName elemQName, Attributes attributes, Object value, QName xmlType, boolean sendNull, Boolean sendType) throws IOException { serialize(elemQName, attributes, value, xmlType, null, (sendNull) ? Boolean.TRUE : Boolean.FALSE, sendType); } /** * Serialize the indicated value as an element with the name * indicated by elemQName. * The attributes are additional attribute to be serialized on the element. * The value is the object being serialized. (It may be serialized * directly or serialized as an mult-ref'd item) * The value is an Object, which may be a wrapped primitive. * The xmlType (if specified) is the QName of the type that is used to set * xsi:type. * The sendNull flag indicates whether to end an element with an xsi:nil="true" attribute for null * variables (if Boolean.TRUE), or nothing (if Boolean.FALSE). * The sendType flag indicates whether the xsi:type flag should be sent * (default is true). * @param elemQName is the QName of the element * @param attributes are additional attributes * @param value is the object to serialize * @param xmlType is the qname of the type or null. * @param sendNull determines whether to send null values. * @param sendType determines whether to set xsi:type attribute. */ public void serialize(QName elemQName, Attributes attributes, Object value, QName xmlType, Boolean sendNull, Boolean sendType) throws IOException { serialize(elemQName, attributes, value, xmlType, null, sendNull, sendType); } /** * Serialize the indicated value as an element with the name * indicated by elemQName. * The attributes are additional attribute to be serialized on the element. * The value is the object being serialized. (It may be serialized * directly or serialized as an mult-ref'd item) * The value is an Object, which may be a wrapped primitive. * The xmlType (if specified) is the QName of the type that is used to set * xsi:type. * The sendNull flag indicates whether to end an element with an xsi:nil="true" attribute for null * variables (if Boolean.TRUE), or nothing (if Boolean.FALSE). * The sendType flag indicates whether the xsi:type flag should be sent * (default is true). * @param elemQName is the QName of the element * @param attributes are additional attributes * @param value is the object to serialize * @param xmlType is the qname of the type or null. * @param javaType is the java type of the value * @param sendNull determines whether to send null values. * @param sendType determines whether to set xsi:type attribute. */ public void serialize(QName elemQName, Attributes attributes, Object value, QName xmlType, Class javaClass, Boolean sendNull, Boolean sendType) throws IOException { boolean sendXSITypeCache = sendXSIType; if (sendType != null) { sendXSIType = sendType.booleanValue(); } boolean shouldSendType = shouldSendXSIType(); try { Boolean sendNullCache = this.sendNull; if (sendNull != null) { this.sendNull = sendNull; } else { sendNull = this.sendNull; } if (value == null) { // If the value is null, the element is // passed with xsi:nil="true" to indicate that no object is present. if (this.sendNull.booleanValue()) { AttributesImpl attrs = new AttributesImpl(); if (attributes != null && 0 < attributes.getLength()) attrs.setAttributes(attributes); if (shouldSendType) attrs = (AttributesImpl) setTypeAttribute(attrs, xmlType); String nil = schemaVersion.getNilQName().getLocalPart(); attrs.addAttribute(schemaVersion.getXsiURI(), nil, "xsi:" + nil, "CDATA", "true"); startElement(elemQName, attrs); endElement(); } this.sendNull = sendNullCache; return; } Message msg = getCurrentMessage(); if (null != msg) { //Get attachments. returns null if no attachment support. Attachments attachments = getCurrentMessage().getAttachmentsImpl(); if (null != attachments && attachments.isAttachment(value)) { //Attachment support and this is an object that should be treated as an attachment. //Allow an the attachment to do its own serialization. serializeActual(elemQName, attributes, value, xmlType, javaClass, sendType); //No need to add to mulitRefs. Attachment data stream handled by // the message; this.sendNull = sendNullCache; return; } } // If multi-reference is enabled and this object value is not a primitive // and we are not forcing serialization of the object, then generate // an element href (and store the object for subsequent outputMultiRef // processing). // NOTE : you'll notice that everywhere we register objects in the // multiRefValues and secondLevelObjects collections, we key them // using getIdentityKey(value) instead of the Object reference itself. // THIS IS IMPORTANT, and please make sure you understand what's // going on if you change any of this code. It's this way to make // sure that individual Objects are serialized separately even if the // hashCode() and equals() methods have been overloaded to make two // Objects appear equal. if (doMultiRefs && isEncoded() && (value != forceSer) && !isPrimitive(value)) { if (multiRefIndex == -1) multiRefValues = new HashMap(); String id; // Look for a multi-ref descriptor for this Object. MultiRefItem mri = (MultiRefItem) multiRefValues.get(getIdentityKey(value)); if (mri == null) { // Didn't find one, so create one, give it a new ID, and store // it for next time. multiRefIndex++; id = "id" + multiRefIndex; mri = new MultiRefItem(id, xmlType, sendType, value); multiRefValues.put(getIdentityKey(value), mri); /** * If we're SOAP 1.2, we can "inline" the serializations, * so put it out now, with it's ID. */ if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) { AttributesImpl attrs = new AttributesImpl(); if (attributes != null && 0 < attributes.getLength()) attrs.setAttributes(attributes); attrs.addAttribute("", Constants.ATTR_ID, "id", "CDATA", id); serializeActual(elemQName, attrs, value, xmlType, javaClass, sendType); this.sendNull = sendNullCache; return; } /** If we're in the middle of writing out * the multi-refs, we've already cloned the list of objects * and so even though we add a new one to multiRefValues, * it won't get serialized this time around. * * To deal with this, we maintain a list of "second level" * Objects - ones that need serializing as a result of * serializing the first level. When outputMultiRefs() is * nearly finished, it checks to see if secondLevelObjects * is empty, and if not, it goes back and loops over those * Objects. This can happen N times depending on how deep * the Object graph goes. */ if (outputMultiRefsFlag) { if (secondLevelObjects == null) secondLevelObjects = new HashSet(); secondLevelObjects.add(getIdentityKey(value)); } } else { // Found one, remember it's ID id = mri.id; } // Serialize an HREF to our object AttributesImpl attrs = new AttributesImpl(); if (attributes != null && 0 < attributes.getLength()) attrs.setAttributes(attributes); attrs.addAttribute("", soapConstants.getAttrHref(), soapConstants.getAttrHref(), "CDATA", '#' + id); startElement(elemQName, attrs); endElement(); this.sendNull = sendNullCache; return; } // The forceSer variable is set by outputMultiRefs to force // serialization of this object via the serialize(...) call // below. However, if the forced object contains a self-reference, we // get into an infinite loop..which is why it is set back to null // before the actual serialization. if (value == forceSer) forceSer = null; // Actually serialize the value. (i.e. not an href like above) serializeActual(elemQName, attributes, value, xmlType, javaClass, sendType); } finally { sendXSIType = sendXSITypeCache; } } /** * Get an IDKey that represents the unique identity of the object. * This is used as a unique key into a HashMap which will * not give false hits on other Objects where hashCode() and equals() * have been overriden to match. * * @param value the Object to hash * @return a unique IDKey for the identity */ private IDKey getIdentityKey(Object value) { return new IDKey(value); } /** * The serialize method uses hrefs to reference all non-primitive * values. These values are stored and serialized by calling * outputMultiRefs after the serialize method completes. */ public void outputMultiRefs() throws IOException { if (!doMultiRefs || (multiRefValues == null) || soapConstants == SOAPConstants.SOAP12_CONSTANTS) return; outputMultiRefsFlag = true; AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute("", "", "", "", ""); String encodingURI = soapConstants.getEncodingURI(); // explicitly state that this attribute is not a root String prefix = getPrefixForURI(encodingURI); String root = prefix + ":root"; attrs.addAttribute(encodingURI, Constants.ATTR_ROOT, root, "CDATA", "0"); // Make sure we put the encodingStyle on each multiref element we // output. String encodingStyle; if (msgContext != null) { encodingStyle = msgContext.getEncodingStyle(); } else { encodingStyle = soapConstants.getEncodingURI(); } String encStyle = getPrefixForURI(soapConstants.getEnvelopeURI()) + ':' + Constants.ATTR_ENCODING_STYLE; attrs.addAttribute(soapConstants.getEnvelopeURI(), Constants.ATTR_ENCODING_STYLE, encStyle, "CDATA", encodingStyle); // Make a copy of the keySet because it could be updated // during processing HashSet keys = new HashSet(); keys.addAll(multiRefValues.keySet()); Iterator i = keys.iterator(); while (i.hasNext()) { while (i.hasNext()) { AttributesImpl attrs2 = new AttributesImpl(attrs); Object val = i.next(); MultiRefItem mri = (MultiRefItem) multiRefValues.get(val); attrs2.setAttribute(0, "", Constants.ATTR_ID, "id", "CDATA", mri.id); forceSer = mri.value; // Now serialize the value. // The sendType parameter is defaulted for interop purposes. // Some of the remote services do not know how to // ascertain the type in these circumstances (though Axis does). serialize(multirefQName, attrs2, mri.value, mri.xmlType, null, this.sendNull, Boolean.TRUE); // mri.sendType } // Done processing the iterated values. During the serialization // of the values, we may have run into new nested values. These // were placed in the secondLevelObjects map, which we will now // process by changing the iterator to locate these values. if (secondLevelObjects != null) { i = secondLevelObjects.iterator(); secondLevelObjects = null; } } // Reset maps and flags forceSer = null; outputMultiRefsFlag = false; multiRefValues = null; multiRefIndex = -1; secondLevelObjects = null; } public void writeXMLDeclaration() throws IOException { writer.write("<?xml version=\"1.0\" encoding=\""); writer.write(encoding); writer.write("\"?>"); startOfDocument = false; } /** * Writes (using the Writer) the start tag for element QName along with the * indicated attributes and namespace mappings. * @param qName is the name of the element * @param attributes are the attributes to write */ public void startElement(QName qName, Attributes attributes) throws IOException { java.util.ArrayList vecQNames = null; if (debugEnabled) { log.debug(Messages.getMessage("startElem00", "[" + qName.getNamespaceURI() + "]:" + qName.getLocalPart())); } if (startOfDocument && sendXMLDecl) { writeXMLDeclaration(); } if (writingStartTag) { writer.write('>'); if (pretty) writer.write('\n'); indent++; } if (pretty) for (int i = 0; i < indent; i++) writer.write(' '); String elementQName = qName2String(qName, true); writer.write('<'); writer.write(elementQName); if (writeXMLType != null) { attributes = setTypeAttribute(attributes, writeXMLType); writeXMLType = null; } if (attributes != null) { for (int i = 0; i < attributes.getLength(); i++) { String qname = attributes.getQName(i); writer.write(' '); String prefix = ""; String uri = attributes.getURI(i); if (uri != null && uri.length() > 0) { if (qname.length() == 0) { // If qname isn't set, generate one prefix = getPrefixForURI(uri); } else { // If it is, make sure the prefix looks reasonable. int idx = qname.indexOf(':'); if (idx > -1) { prefix = qname.substring(0, idx); prefix = getPrefixForURI(uri, prefix, true); } } if (prefix.length() > 0) { qname = prefix + ':' + attributes.getLocalName(i); } else { qname = attributes.getLocalName(i); } } else { qname = attributes.getQName(i); if (qname.length() == 0) qname = attributes.getLocalName(i); } if (qname.startsWith("xmlns")) { if (vecQNames == null) vecQNames = new ArrayList(); vecQNames.add(qname); } writer.write(qname); writer.write("=\""); getEncoder().writeEncoded(writer, attributes.getValue(i)); writer.write('"'); } } if (noNamespaceMappings) { nsStack.push(); } else { for (Mapping map = nsStack.topOfFrame(); map != null; map = nsStack.next()) { if (!(map.getNamespaceURI().equals(Constants.NS_URI_XMLNS) && map.getPrefix().equals("xmlns")) && !(map.getNamespaceURI().equals(Constants.NS_URI_XML) && map.getPrefix().equals("xml"))) { StringBuffer sb = new StringBuffer("xmlns"); if (map.getPrefix().length() > 0) { sb.append(':'); sb.append(map.getPrefix()); } if ((vecQNames == null) || (vecQNames.indexOf(sb.toString()) == -1)) { writer.write(' '); sb.append("=\""); sb.append(map.getNamespaceURI()); sb.append('"'); writer.write(sb.toString()); } } } noNamespaceMappings = true; } writingStartTag = true; elementStack.push(elementQName); onlyXML = true; } /** * Writes the end element tag for the open element. **/ public void endElement() throws IOException { String elementQName = (String) elementStack.pop(); if (debugEnabled) { log.debug(Messages.getMessage("endElem00", "" + elementQName)); } nsStack.pop(); if (writingStartTag) { writer.write("/>"); if (pretty) writer.write('\n'); writingStartTag = false; return; } if (onlyXML) { indent--; if (pretty) for (int i = 0; i < indent; i++) writer.write(' '); } writer.write("</"); writer.write(elementQName); writer.write('>'); if (pretty) if (indent > 0) writer.write('\n'); onlyXML = true; } /** * Convenience operation to write out (to Writer) the characters * in p1 starting at index p2 for length p3. * @param p1 character array to write * @param p2 starting index in array * @param p3 length to write */ public void writeChars(char[] p1, int p2, int p3) throws IOException { if (startOfDocument && sendXMLDecl) { writeXMLDeclaration(); } if (writingStartTag) { writer.write('>'); writingStartTag = false; } writeSafeString(String.valueOf(p1, p2, p3)); onlyXML = false; } /** * Convenience operation to write out (to Writer) the String * @param string is the String to write. */ public void writeString(String string) throws IOException { if (startOfDocument && sendXMLDecl) { writeXMLDeclaration(); } if (writingStartTag) { writer.write('>'); writingStartTag = false; } writer.write(string); onlyXML = false; } /** * Convenience operation to write out (to Writer) the String * properly encoded with xml entities (like &) * @param string is the String to write. */ public void writeSafeString(String string) throws IOException { if (startOfDocument && sendXMLDecl) { writeXMLDeclaration(); } if (writingStartTag) { writer.write('>'); writingStartTag = false; } getEncoder().writeEncoded(writer, string); onlyXML = false; } /** * Output a DOM representation to a SerializationContext * @param el is a DOM Element */ public void writeDOMElement(Element el) throws IOException { if (startOfDocument && sendXMLDecl) { writeXMLDeclaration(); } // If el is a Text element, write the text and exit if (el instanceof org.apache.axis.message.Text) { writeSafeString(((Text) el).getData()); return; } AttributesImpl attributes = null; NamedNodeMap attrMap = el.getAttributes(); if (attrMap.getLength() > 0) { attributes = new AttributesImpl(); for (int i = 0; i < attrMap.getLength(); i++) { Attr attr = (Attr) attrMap.item(i); String tmp = attr.getNamespaceURI(); if (tmp != null && tmp.equals(Constants.NS_URI_XMLNS)) { String prefix = attr.getLocalName(); if (prefix != null) { if (prefix.equals("xmlns")) prefix = ""; String nsURI = attr.getValue(); registerPrefixForURI(prefix, nsURI); } continue; } attributes.addAttribute(attr.getNamespaceURI(), attr.getLocalName(), attr.getName(), "CDATA", attr.getValue()); } } String namespaceURI = el.getNamespaceURI(); String localPart = el.getLocalName(); if (namespaceURI == null || namespaceURI.length() == 0) localPart = el.getNodeName(); QName qName = new QName(namespaceURI, localPart); startElement(qName, attributes); NodeList children = el.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child instanceof Element) { writeDOMElement((Element) child); } else if (child instanceof CDATASection) { writeString("<![CDATA["); writeString(((Text) child).getData()); writeString("]]>"); } else if (child instanceof Comment) { writeString("<!--"); writeString(((CharacterData) child).getData()); writeString("-->"); } else if (child instanceof Text) { writeSafeString(((Text) child).getData()); } } endElement(); } /** * Convenience method to get the Serializer for a specific * java type * @param javaType is Class for a type to serialize * @return Serializer */ public final Serializer getSerializerForJavaType(Class javaType) { SerializerFactory serF = null; Serializer ser = null; try { serF = (SerializerFactory) getTypeMapping().getSerializer(javaType); if (serF != null) { ser = (Serializer) serF.getSerializerAs(Constants.AXIS_SAX); } } catch (JAXRPCException e) { } return ser; } /** * Obtains the type attribute that should be serialized and returns the new list of Attributes * @param attributes of the qname * @param type is the qname of the type * @return new list of Attributes */ public Attributes setTypeAttribute(Attributes attributes, QName type) { SchemaVersion schema = SchemaVersion.SCHEMA_2001; if (msgContext != null) { schema = msgContext.getSchemaVersion(); } if (type == null || type.getLocalPart().indexOf(SymbolTable.ANON_TOKEN) >= 0 || ((attributes != null) && (attributes.getIndex(schema.getXsiURI(), "type") != -1))) return attributes; AttributesImpl attrs = new AttributesImpl(); if (attributes != null && 0 < attributes.getLength()) attrs.setAttributes(attributes); String prefix = getPrefixForURI(schema.getXsiURI(), "xsi"); attrs.addAttribute(schema.getXsiURI(), "type", prefix + ":type", "CDATA", attributeQName2String(type)); return attrs; } /** * Invoked to do the actual serialization of the qName (called by serialize above). * additional attributes that will be serialized with the qName. * @param elemQName is the QName of the element * @param attributes are additional attributes * @param value is the object to serialize * @param xmlType (optional) is the desired type QName. * @param sendType indicates whether the xsi:type attribute should be set. */ private void serializeActual(QName elemQName, Attributes attributes, Object value, QName xmlType, Class javaClass, Boolean sendType) throws IOException { boolean shouldSendType = (sendType == null) ? shouldSendXSIType() : sendType.booleanValue(); if (value != null) { TypeMapping tm = getTypeMapping(); if (tm == null) { throw new IOException(Messages.getMessage("noSerializer00", value.getClass().getName(), "" + this)); } // Set currentXMLType to the one desired one. // Note for maxOccurs usage this xmlType is the // type of the component not the type of the array. currentXMLType = xmlType; // if we're looking for xsd:anyType, accept anything... if (Constants.equals(Constants.XSD_ANYTYPE, xmlType)) { xmlType = null; shouldSendType = true; } // Try getting a serializer for the prefered xmlType QNameHolder actualXMLType = new QNameHolder(); Class javaType = getActualJavaClass(xmlType, javaClass, value); Serializer ser = getSerializer(javaType, xmlType, actualXMLType); if (ser != null) { // Send the xmlType if indicated or if // the actual xmlType is different than the // prefered xmlType if (shouldSendType || (xmlType != null && (!xmlType.equals(actualXMLType.value)))) { if (!isEncoded()) { if (Constants.isSOAP_ENC(actualXMLType.value.getNamespaceURI())) { // Don't write SOAP_ENC types (i.e. Array) if we're not using encoding } else if (javaType.isPrimitive() && javaClass != null && JavaUtils.getWrapperClass(javaType) == javaClass) { // Don't write xsi:type when serializing primitive wrapper value as primitive type. } else { if (!(javaType.isArray() && xmlType != null && Constants.isSchemaXSD(xmlType.getNamespaceURI()))) { writeXMLType = actualXMLType.value; } } } else { writeXMLType = actualXMLType.value; } } // ----------------- // NOTE: I have seen doc/lit tests that use // the type name as the element name in multi-ref cases // (for example <soapenc:Array ... >) // In such cases the xsi:type is not passed along. // ----------------- // The multiref QName is our own fake name. // It may be beneficial to set the name to the // type name, but I didn't see any improvements // in the interop tests. //if (name.equals(multirefQName) && type != null) // name = type; ser.serialize(elemQName, attributes, value, this); return; } throw new IOException(Messages.getMessage("noSerializer00", value.getClass().getName(), "" + tm)); } // !!! Write out a generic null, or get type info from somewhere else? } /** * Returns the java class for serialization. * If the xmlType is xsd:anyType or javaType is array or javaType is java.lang.Object * the java class for serialization is the class of obj. * If the obj is not array and the obj's class does not match with the javaType, * the java class for serialization is the javaType. * Otherwise, the java class for serialization is the obj's class. * * @param xmlType the qname of xml type * @param javaType the java class from serializer * @param obj the object to serialize * @return the java class for serialization */ private Class getActualJavaClass(QName xmlType, Class javaType, Object obj) { Class cls = obj.getClass(); if ((xmlType != null && Constants.isSchemaXSD(xmlType.getNamespaceURI()) && "anyType".equals(xmlType.getLocalPart())) || (javaType != null && (javaType.isArray() || javaType == Object.class))) { return cls; } if (javaType != null && !javaType.isAssignableFrom(cls) && !cls.isArray()) { return javaType; } return cls; } private Serializer getSerializerFromClass(Class javaType, QName qname) { Serializer serializer = null; try { Method method = MethodCache.getInstance().getMethod(javaType, SERIALIZER_METHOD, SERIALIZER_CLASSES); if (method != null) { serializer = (Serializer) method.invoke(null, new Object[] { getEncodingStyle(), javaType, qname }); } } catch (NoSuchMethodException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } return serializer; } /** * Get the currently prefered xmlType * @return QName of xmlType or null */ public QName getCurrentXMLType() { return currentXMLType; } /** * Walk the interfaces of a class looking for a serializer for that * interface. Include any parent interfaces in the search also. * */ private SerializerFactory getSerializerFactoryFromInterface(Class javaType, QName xmlType, TypeMapping tm) { SerializerFactory serFactory = null; Class[] interfaces = javaType.getInterfaces(); if (interfaces != null) { for (int i = 0; i < interfaces.length; i++) { Class iface = interfaces[i]; serFactory = (SerializerFactory) tm.getSerializer(iface, xmlType); if (serFactory == null) serFactory = getSerializerFactoryFromInterface(iface, xmlType, tm); if (serFactory != null) break; } } return serFactory; } /** * getSerializer * Attempts to get a serializer for the indicated javaType and xmlType. * @param javaType is the type of the object * @param xmlType is the preferred qname type. * @param actualXMLType is set to a QNameHolder or null. * If a QNameHolder, the actual xmlType is returned. * @return found class/serializer or null **/ private Serializer getSerializer(Class javaType, QName xmlType, QNameHolder actualXMLType) { SerializerFactory serFactory = null; TypeMapping tm = getTypeMapping(); if (actualXMLType != null) { actualXMLType.value = null; } while (javaType != null) { // check type mapping serFactory = (SerializerFactory) tm.getSerializer(javaType, xmlType); if (serFactory != null) { break; } // check the class for serializer Serializer serializer = getSerializerFromClass(javaType, xmlType); if (serializer != null) { if (actualXMLType != null) { TypeDesc typedesc = TypeDesc.getTypeDescForClass(javaType); if (typedesc != null) { actualXMLType.value = typedesc.getXmlType(); } } return serializer; } // Walk my interfaces... serFactory = getSerializerFactoryFromInterface(javaType, xmlType, tm); if (serFactory != null) { break; } // Finally, head to my superclass javaType = javaType.getSuperclass(); } // Using the serialization factory, create a serializer Serializer ser = null; if (serFactory != null) { ser = (Serializer) serFactory.getSerializerAs(Constants.AXIS_SAX); if (actualXMLType != null) { // Get the actual qname xmlType from the factory. // If not found via the factory, fall back to a less // performant solution. if (serFactory instanceof BaseSerializerFactory) { actualXMLType.value = ((BaseSerializerFactory) serFactory).getXMLType(); } boolean encoded = isEncoded(); if (actualXMLType.value == null || (!encoded && (actualXMLType.value.equals(Constants.SOAP_ARRAY) || actualXMLType.value.equals(Constants.SOAP_ARRAY12)))) { actualXMLType.value = tm.getXMLType(javaType, xmlType, encoded); } } } return ser; } public String getValueAsString(Object value, QName xmlType, Class javaClass) throws IOException { Class cls = value.getClass(); cls = getActualJavaClass(xmlType, javaClass, value); Serializer ser = getSerializer(cls, xmlType, null); // The java type is an array, but we need a simple type. if (ser instanceof ArraySerializer) { SimpleListSerializerFactory factory = new SimpleListSerializerFactory(cls, xmlType); ser = (Serializer) factory.getSerializerAs(getEncodingStyle()); } if (!(ser instanceof SimpleValueSerializer)) { throw new IOException(Messages.getMessage("needSimpleValueSer", ser.getClass().getName())); } SimpleValueSerializer simpleSer = (SimpleValueSerializer) ser; return simpleSer.getValueAsString(value, this); } public void setWriteXMLType(QName type) { writeXMLType = type; } public XMLEncoder getEncoder() { if (encoder == null) { encoder = XMLUtils.getXMLEncoder(encoding); } return encoder; } /** * get the encoding for the serialization * @return */ public String getEncoding() { return encoding; } /** * set the encoding for the serialization */ public void setEncoding(String encoding) { this.encoding = encoding; } public QName getItemQName() { return itemQName; } public void setItemQName(QName itemQName) { this.itemQName = itemQName; } public QName getItemType() { return itemType; } public void setItemType(QName itemType) { this.itemType = itemType; } }