ArraySerializer.java |
/* * The Apache Software License, Version 1.1 * * * Copyright (c) 2001-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Axis" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.jboss.axis.encoding.ser; import org.jboss.axis.AxisEngine; import org.jboss.axis.Constants; import org.jboss.axis.MessageContext; import org.jboss.axis.encoding.SerializationContext; import org.jboss.axis.encoding.Serializer; import org.jboss.axis.schema.SchemaVersion; import org.jboss.axis.soap.SOAPConstants; import org.jboss.axis.utils.JavaUtils; import org.jboss.axis.utils.Messages; import org.jboss.axis.wsdl.fromJava.Types; import org.jboss.logging.Logger; import org.w3c.dom.Element; import org.xml.sax.Attributes; import org.xml.sax.helpers.AttributesImpl; import javax.xml.namespace.QName; import java.io.IOException; import java.lang.reflect.Array; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; /** * An ArraySerializer handles serializing of arrays. * <p/> * Some code borrowed from ApacheSOAP - thanks to Matt Duftler! * * @author Glen Daniels (gdaniels@macromedia.com) * <p/> * Multi-reference stuff: * @author Rich Scheuerle (scheu@us.ibm.com) */ public class ArraySerializer implements Serializer { private static Logger log = Logger.getLogger(ArraySerializer.class.getName()); // Maps the a componentType Class to its type QName // Usefull when you want to override the default private HashMap componentTypeMap = new HashMap(); /** * Serialize an element that is an array. * * @param name is the element name * @param attributes are the attributes...serialize is free to add more. * @param value is the value * @param context is the SerializationContext */ public void serialize(QName name, Attributes attributes, Object value, SerializationContext context) throws IOException { if (value == null) throw new IOException(Messages.getMessage("cantDoNullArray00")); MessageContext msgContext = context.getMessageContext(); SchemaVersion schema = SchemaVersion.SCHEMA_2001; SOAPConstants soap = SOAPConstants.SOAP11_CONSTANTS; if (msgContext != null) { schema = msgContext.getSchemaVersion(); soap = msgContext.getSOAPConstants(); } Class cls = value.getClass(); Collection list = null; if (!cls.isArray()) { if (!(value instanceof Collection)) { throw new IOException(Messages.getMessage("cantSerialize00", cls.getName())); } list = (Collection)value; } // Get the componentType of the array/list Class componentType; if (list == null) { componentType = cls.getComponentType(); } else { componentType = Object.class; } // Explicitly registered component type qnames have priority // In most cases we won't have them unless a subclass sets it explicitly // TDI 17-Jun-2004 QName componentQName = (QName)componentTypeMap.get(componentType); String dims = ""; // Check to see if componentType is also an array. // If so, set the componentType to the most nested non-array // componentType. Increase the dims string by "[]" // each time through the loop. // Note from Rich Scheuerle: // This won't handle Lists of Lists or // arrays of Lists....only arrays of arrays. // Only do this, if the type was not explicitly registered if (componentQName == null) { while (componentType.isArray()) { componentType = componentType.getComponentType(); if (soap == SOAPConstants.SOAP12_CONSTANTS) dims += "* "; else dims += "[]"; } } // Get the QName of the componentType. if (componentQName == null) { componentQName = context.getQNameForClass(componentType); } // If not found, look at the super classes if (componentQName == null) { Class searchCls = componentType; while (searchCls != null && componentQName == null) { searchCls = searchCls.getSuperclass(); componentQName = context.getQNameForClass(searchCls); } if (componentQName != null) { componentType = searchCls; } } if (componentQName == null) { throw new IOException(Messages.getMessage("noType00", componentType.getName())); } String prefix = context.getPrefixForURI(componentQName.getNamespaceURI()); String compType = prefix + ":" + componentQName.getLocalPart(); int len = (list == null) ? Array.getLength(value) : list.size(); String arrayType; if (soap == SOAPConstants.SOAP12_CONSTANTS) arrayType = dims + len; else arrayType = dims + "[" + len + "]"; // Discover whether array can be serialized directly as a two-dimensional // array (i.e. arrayType=int[2,3]) versus an array of arrays. // Benefits: // - Less text passed on the wire. // - Easier to read wire format // - Tests the deserialization of multi-dimensional arrays. // Drawbacks: // - Is not safe! It is possible that the arrays are multiply // referenced. Transforming into a 2-dim array will cause the // multi-referenced information to be lost. Plus there is no // way to determine whether the arrays are multi-referenced. // - .NET currently (Dec 2002) does not support 2D SOAP-encoded arrays // // OLD Comment as to why this was ENABLED: // It is necessary for // interoperability (echo2DStringArray). It is 'safe' for now // because Axis treats arrays as non multi-ref (see the note // in SerializationContextImpl.isPrimitive(...) ) // More complicated processing is necessary for 3-dim arrays, etc. // // Axis 1.1 - December 2002 // Turned this OFF because Microsoft .NET can not deserialize // multi-dimensional SOAP-encoded arrays, and this interopability // is pretty high visibility. Make it a global configuration parameter: // <parameter name="enable2DArrayEncoding" value="true"/> (tomj) // // Check the message context to see if we should turn 2D processing ON // Default is OFF boolean enable2Dim = false; // Vidyanand : added this check if (msgContext != null) { enable2Dim = JavaUtils.isTrueExplicitly(msgContext.getAxisEngine().getOption(AxisEngine.PROP_TWOD_ARRAY_ENCODING)); } int dim2Len = -1; if (enable2Dim && !dims.equals("")) { if (cls.isArray() && len > 0) { boolean okay = true; // Make sure all of the component arrays are the same size for (int i = 0; i < len && okay; i++) { Object elementValue = Array.get(value, i); if (elementValue == null) okay = false; else if (dim2Len < 0) { dim2Len = Array.getLength(elementValue); if (dim2Len <= 0) { okay = false; } } else if (dim2Len != Array.getLength(elementValue)) { okay = false; } } // Update the arrayType to use mult-dim array encoding if (okay) { dims = dims.substring(0, dims.length() - 2); if (soap == SOAPConstants.SOAP12_CONSTANTS) arrayType = dims + len + " " + dim2Len; else arrayType = dims + "[" + len + "," + dim2Len + "]"; } else { dim2Len = -1; } } } // Need to distinguish if this is array processing for an // actual schema array or for a maxOccurs usage. // For the maxOccurs case, the currentXMLType of the context is // the same as the componentQName. boolean maxOccursUsage = (msgContext != null && !msgContext.isEncoded()) && componentQName.equals(context.getCurrentXMLType()); if (!maxOccursUsage) { AttributesImpl attrs; if (attributes == null) { attrs = new AttributesImpl(); } else if (attributes instanceof AttributesImpl) { attrs = (AttributesImpl)attributes; } else { attrs = new AttributesImpl(attributes); } if (attrs.getIndex(soap.getEncodingURI(), soap.getAttrItemType()) == -1) { String encprefix = context.getPrefixForURI(soap.getEncodingURI()); if (soap != SOAPConstants.SOAP12_CONSTANTS) { compType = compType + arrayType; attrs.addAttribute(soap.getEncodingURI(), soap.getAttrItemType(), encprefix + ":arrayType", "CDATA", compType); } else { attrs.addAttribute(soap.getEncodingURI(), soap.getAttrItemType(), encprefix + ":itemType", "CDATA", compType); attrs.addAttribute(soap.getEncodingURI(), "arraySize", encprefix + ":arraySize", "CDATA", arrayType); } } // Force type to be SOAP_ARRAY for all array serialization. // // There are two choices here: // Force the type to type=SOAP_ARRAY // Pros: More interop test successes. // Cons: Since we have specific type information it // is more correct to use it. Plus the specific // type information may be important on the // server side to disambiguate overloaded operations. // Use the specific type information: // Pros: The specific type information is more correct // and may be useful for operation overloading. // Cons: More interop test failures (as of 2/6/2002). // int typeI = attrs.getIndex(schema.getXsiURI(), "type"); if (typeI != -1) { String qname = context.getPrefixForURI(schema.getXsiURI(), "xsi") + ":type"; QName soapArray; if (soap == SOAPConstants.SOAP12_CONSTANTS) { soapArray = Constants.SOAP_ARRAY12; } else { soapArray = Constants.SOAP_ARRAY; } attrs.setAttribute(typeI, schema.getXsiURI(), "type", qname, "CDATA", context.qName2String(soapArray)); } attributes = attrs; } // For the maxOccurs case, each item is named with the QName // we got in the arguments. For normal array case, we write an element with // that QName, and then serialize each item as <item> QName elementName = name; Attributes serializeAttr = attributes; if (!maxOccursUsage) { serializeAttr = null; // since we are putting them here context.startElement(name, attributes); elementName = Constants.QNAME_LITERAL_ITEM; } if (dim2Len < 0) { // Normal case, serialize each array element if (list == null) { for (int index = 0; index < len; index++) { Object aValue = Array.get(value, index); // Serialize the element. context.serialize(elementName, serializeAttr, aValue, componentQName, // prefered type QName true, // Send null values Boolean.FALSE); // Don't send xsi:type if it matches preferred QName } } else { for (Iterator iterator = list.iterator(); iterator.hasNext();) { Object aValue = iterator.next(); // Serialize the element. context.serialize(elementName, serializeAttr, aValue, componentQName, // prefered type QName true, // Send null values Boolean.FALSE); // Don't send xsi:type if it matches preferred QName } } } else { // Serialize as a 2 dimensional array for (int index = 0; index < len; index++) { for (int index2 = 0; index2 < dim2Len; index2++) { Object aValue = Array.get(Array.get(value, index), index2); context.serialize(elementName, null, aValue); } } } if (!maxOccursUsage) context.endElement(); } public String getMechanismType() { return Constants.AXIS_SAX; } public void addComponentTypeMapping(Class pomponentType, QName typeName) { componentTypeMap.put(pomponentType, typeName); } /** * Return XML schema for the specified type, suitable for insertion into * the <types> element of a WSDL document, or underneath an * <element> or <attribute> declaration. * * @param javaType the Java Class we're writing out schema for * @param types the Java2WSDL Types object which holds the context * for the WSDL being generated. * @return a type element containing a schema simpleType/complexType * @see org.jboss.axis.wsdl.fromJava.Types */ public Element writeSchema(Class javaType, Types types) throws Exception { // If an array the component type should be processed first String componentTypeName = null; Class componentType = null; if (javaType.isArray()) { String dimString = "[]"; componentType = javaType.getComponentType(); if (componentType.isArray()) { while (componentType.isArray()) { dimString += "[]"; componentType = componentType.getComponentType(); } } else { types.writeType(componentType, null); } componentTypeName = types.getQNameString(types.getTypeQName(componentType)) + dimString; } // Use Types helper method to actually create the complexType return types.createArrayElement(componentTypeName); } }
ArraySerializer.java |