| /* |
| * Copyright 2001-2005 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.ser; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Array; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import javax.xml.namespace.QName; |
| import org.w3c.dom.Element; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.helpers.AttributesImpl; |
| import org.apache.axis.AxisEngine; |
| import org.apache.axis.Constants; |
| import org.apache.axis.MessageContext; |
| import org.apache.axis.components.logger.LogFactory; |
| import org.apache.axis.constants.Use; |
| import org.apache.axis.encoding.SerializationContext; |
| import org.apache.axis.encoding.Serializer; |
| import org.apache.axis.encoding.SerializerFactory; |
| import org.apache.axis.encoding.TypeMapping; |
| import org.apache.axis.schema.SchemaVersion; |
| import org.apache.axis.soap.SOAPConstants; |
| import org.apache.axis.utils.JavaUtils; |
| import org.apache.axis.utils.Messages; |
| import org.apache.axis.wsdl.fromJava.Types; |
| import org.apache.commons.logging.Log; |
| |
| /** |
| * An ArraySerializer handles serializing of arrays. |
| * |
| * Some code borrowed from ApacheSOAP - thanks to Matt Duftler! |
| * |
| * @author Glen Daniels (gdaniels@apache.org) |
| * |
| * Multi-reference stuff: |
| * @author Rich Scheuerle (scheu@us.ibm.com) |
| */ |
| public class ArraySerializer implements Serializer |
| { |
| QName xmlType = null; |
| Class javaType = null; |
| QName componentType = null; |
| QName componentQName = null; |
| |
| /** |
| * Constructor |
| * |
| */ |
| public ArraySerializer(Class javaType, QName xmlType) { |
| this.javaType = javaType; |
| this.xmlType = xmlType; |
| } |
| |
| /** |
| * Constructor |
| * Special constructor that takes the component type of the array. |
| */ |
| public ArraySerializer(Class javaType, QName xmlType, QName componentType) { |
| this(javaType, xmlType); |
| this.componentType = componentType; |
| } |
| |
| /** |
| * Constructor |
| * Special constructor that takes the component type and QName of the array. |
| */ |
| public ArraySerializer(Class javaType, QName xmlType, QName componentType, QName componentQName) { |
| this(javaType, xmlType, componentType); |
| this.componentQName = componentQName; |
| } |
| |
| protected static Log log = |
| LogFactory.getLog(ArraySerializer.class.getName()); |
| |
| /** |
| * 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; |
| boolean encoded = context.isEncoded(); |
| |
| 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 componentClass; |
| if (list == null) { |
| componentClass = cls.getComponentType(); |
| } else { |
| componentClass = Object.class; |
| } |
| |
| // Get the QName of the componentType |
| // if it wasn't passed in from the constructor |
| QName componentTypeQName = this.componentType; |
| |
| // 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. |
| String dims = ""; |
| |
| if (componentTypeQName != null) { |
| // if we have a Type QName at this point, |
| // this is because ArraySerializer has been instanciated with it |
| TypeMapping tm = context.getTypeMapping(); |
| SerializerFactory factory = (SerializerFactory) tm.getSerializer( |
| componentClass, componentTypeQName); |
| while (componentClass.isArray() |
| && factory instanceof ArraySerializerFactory) { |
| ArraySerializerFactory asf = (ArraySerializerFactory) factory; |
| componentClass = componentClass.getComponentType(); |
| QName componentType = null; |
| if (asf.getComponentType() != null) { |
| componentType = asf.getComponentType(); |
| if(encoded) { |
| componentTypeQName = componentType; |
| } |
| } |
| // update factory with the new values |
| factory = (SerializerFactory) tm.getSerializer(componentClass, |
| componentType); |
| if (soap == SOAPConstants.SOAP12_CONSTANTS) |
| dims += "* "; |
| else |
| dims += "[]"; |
| } |
| } else { |
| // compatibility mode |
| while (componentClass.isArray()) { |
| componentClass = componentClass.getComponentType(); |
| if (soap == SOAPConstants.SOAP12_CONSTANTS) |
| dims += "* "; |
| else |
| dims += "[]"; |
| } |
| } |
| |
| // Try the current XML type from the context |
| if (componentTypeQName == null) { |
| componentTypeQName = context.getCurrentXMLType(); |
| if (componentTypeQName != null) { |
| if ((componentTypeQName.equals(xmlType) || |
| componentTypeQName.equals(Constants.XSD_ANYTYPE) || |
| componentTypeQName.equals(soap.getArrayType()))) { |
| componentTypeQName = null; |
| } |
| } |
| } |
| |
| if (componentTypeQName == null) { |
| componentTypeQName = context.getItemType(); |
| } |
| |
| // Then check the type mapping for the class |
| if (componentTypeQName == null) { |
| componentTypeQName = context.getQNameForClass(componentClass); |
| } |
| |
| // If still not found, look at the super classes |
| if (componentTypeQName == null) { |
| Class searchCls = componentClass; |
| while(searchCls != null && componentTypeQName == null) { |
| searchCls = searchCls.getSuperclass(); |
| componentTypeQName = context.getQNameForClass(searchCls); |
| } |
| if (componentTypeQName != null) { |
| componentClass = searchCls; |
| } |
| } |
| |
| // Still can't find it? Throw an error. |
| if (componentTypeQName == null) { |
| throw new IOException( |
| Messages.getMessage("noType00", componentClass.getName())); |
| } |
| |
| int len = (list == null) ? Array.getLength(value) : list.size(); |
| String arrayType = ""; |
| int dim2Len = -1; |
| if (encoded) { |
| 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 SerializationContext.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.getProperty( |
| AxisEngine.PROP_TWOD_ARRAY_ENCODING)); |
| } |
| |
| 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 componentTypeQName. |
| QName itemQName = context.getItemQName(); |
| boolean maxOccursUsage = !encoded && itemQName == null && |
| componentTypeQName.equals(context.getCurrentXMLType()); |
| |
| if (encoded) { |
| AttributesImpl attrs; |
| if (attributes == null) { |
| attrs = new AttributesImpl(); |
| } else if (attributes instanceof AttributesImpl) { |
| attrs = (AttributesImpl)attributes; |
| } else { |
| attrs = new AttributesImpl(attributes); |
| } |
| |
| String compType = context.attributeQName2String(componentTypeQName); |
| |
| 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). |
| // |
| String qname = |
| context.getPrefixForURI(schema.getXsiURI(), |
| "xsi") + ":type"; |
| QName soapArray; |
| if (soap == SOAPConstants.SOAP12_CONSTANTS) { |
| soapArray = Constants.SOAP_ARRAY12; |
| } else { |
| soapArray = Constants.SOAP_ARRAY; |
| } |
| |
| int typeI = attrs.getIndex(schema.getXsiURI(), |
| "type"); |
| if (typeI != -1) { |
| attrs.setAttribute(typeI, |
| schema.getXsiURI(), |
| "type", |
| qname, |
| "CDATA", |
| context.qName2String(soapArray)); |
| } else { |
| attrs.addAttribute(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); |
| if (itemQName != null) |
| elementName = itemQName; |
| else if(componentQName != null) |
| elementName = componentQName; |
| } |
| |
| |
| 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 == null ? |
| serializeAttr : new AttributesImpl(serializeAttr)), |
| aValue, |
| componentTypeQName); // prefered type QName |
| } |
| } else { |
| for (Iterator iterator = list.iterator(); iterator.hasNext();) { |
| Object aValue = iterator.next(); |
| |
| // Serialize the element. |
| context.serialize(elementName, |
| (serializeAttr == null ? |
| serializeAttr : new AttributesImpl(serializeAttr)), |
| aValue, |
| componentTypeQName); // prefered type 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, componentTypeQName); |
| } |
| } |
| } |
| |
| if (!maxOccursUsage) |
| context.endElement(); |
| } |
| |
| public String getMechanismType() { return Constants.AXIS_SAX; } |
| |
| private static boolean isArray(Class clazz) |
| { |
| return clazz.isArray() || java.util.Collection.class.isAssignableFrom(clazz); |
| } |
| |
| private static Class getComponentType(Class clazz) |
| { |
| if (clazz.isArray()) |
| { |
| return clazz.getComponentType(); |
| } |
| else if (java.util.Collection.class.isAssignableFrom(clazz)) |
| { |
| return Object.class; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| |
| /** |
| * 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.apache.axis.wsdl.fromJava.Types |
| */ |
| public Element writeSchema(Class javaType, Types types) throws Exception { |
| boolean encoded = true; |
| MessageContext mc = MessageContext.getCurrentContext(); |
| if (mc != null) { |
| encoded = mc.isEncoded(); |
| } else { |
| encoded = types.getServiceDesc().getUse() == Use.ENCODED; |
| } |
| |
| if (!encoded) { |
| Class cType = Object.class; |
| if (javaType.isArray()) { |
| cType = javaType.getComponentType(); |
| } |
| |
| String typeName = types.writeType(cType); |
| return types.createLiteralArrayElement(typeName, null); |
| } |
| |
| // If an array the component type should be processed first |
| String componentTypeName = null; |
| Class componentType = null; |
| if (isArray(javaType)) { |
| String dimString = "[]"; |
| componentType = getComponentType(javaType); |
| while (isArray(componentType)) { |
| dimString += "[]"; |
| componentType = getComponentType(componentType); |
| } |
| types.writeType(componentType,null); |
| |
| componentTypeName = |
| types.getQNameString(types.getTypeQName(componentType)) + |
| dimString; |
| } |
| |
| // Use Types helper method to actually create the complexType |
| return types.createArrayElement(componentTypeName); |
| } |
| } |