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.ser; import org.apache.axis.Constants; import org.apache.axis.components.logger.LogFactory; import org.apache.axis.encoding.DeserializationContext; import org.apache.axis.encoding.Deserializer; import org.apache.axis.encoding.DeserializerImpl; import org.apache.axis.encoding.DeserializerTarget; import org.apache.axis.message.SOAPHandler; import org.apache.axis.utils.ClassUtils; import org.apache.axis.utils.JavaUtils; import org.apache.axis.utils.Messages; import org.apache.axis.wsdl.symbolTable.SchemaUtils; import org.apache.commons.logging.Log; import org.apache.axis.soap.SOAPConstants; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; /** * An ArrayDeserializer handles deserializing SOAP * 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 ArrayDeserializer extends DeserializerImpl { protected static Log log = LogFactory.getLog(ArrayDeserializer.class.getName()); public QName arrayType = null; public int curIndex = 0; QName defaultItemType; int length; Class arrayClass = null; ArrayList mDimLength = null; // If set, array of multi-dim lengths ArrayList mDimFactor = null; // If set, array of factors for multi-dim [] SOAPConstants soapConstants = SOAPConstants.SOAP11_CONSTANTS; /** * This method is invoked after startElement when the element requires * deserialization (i.e. the element is not an href & 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 attrs 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 { // Deserializing the xml array requires processing the // xsi:type= attribute, the soapenc:arrayType attribute, // and the xsi:type attributes of the individual elements. // // The xsi:type=<qName> attribute is used to determine the java // type of the array to instantiate. Axis expects it // to be set to the generic "soapenc:Array" or to // a specific qName. If the generic "soapenc:Array" // specification is used, Axis determines the array // type by examining the soapenc:arrayType attribute. // // The soapenc:arrayType=<qname><dims> is used to determine // i) the number of dimensions, // ii) the length of each dimension, // iii) the default xsi:type of each of the elements. // // If the arrayType attribute is missing, Axis assumes // a single dimension array with length equal to the number // of nested elements. In such cases, the default xsi:type of // the elements is determined using the array xsi:type. // // The xsi:type attributes of the individual elements of the // array are used to determine the java type of the element. // If the xsi:type attribute is missing for an element, the // default xsi:type value is used. if (log.isDebugEnabled()) { log.debug("Enter: ArrayDeserializer::startElement()"); } soapConstants = context.getSOAPConstants(); // Get the qname for the array type=, set it to null if // the generic type is used. QName typeQName = context.getTypeFromAttributes(namespace, localName, attributes); if (typeQName == null) { typeQName = getDefaultType(); } if (typeQName != null && Constants.equals(Constants.SOAP_ARRAY, typeQName)) { typeQName = null; } // Now get the arrayType value QName arrayTypeValue = context.getQNameFromString( Constants.getValue(attributes, Constants.URIS_SOAP_ENC, soapConstants.getAttrItemType())); // The first part of the arrayType expression is // the default item type qname. // The second part is the dimension information String dimString = null; QName innerQName = null; String innerDimString = ""; if (arrayTypeValue != null) { if (soapConstants != SOAPConstants.SOAP12_CONSTANTS) { // Doing SOAP 1.1 // Array dimension noted like this : [][x] String arrayTypeValueNamespaceURI = arrayTypeValue.getNamespaceURI(); String arrayTypeValueLocalPart = arrayTypeValue.getLocalPart(); int leftBracketIndex = arrayTypeValueLocalPart.lastIndexOf('['); int rightBracketIndex = arrayTypeValueLocalPart.lastIndexOf(']'); if (leftBracketIndex == -1 || rightBracketIndex == -1 || rightBracketIndex < leftBracketIndex) { throw new IllegalArgumentException(Messages.getMessage("badArrayType00", "" + arrayTypeValue)); } dimString = arrayTypeValueLocalPart.substring(leftBracketIndex + 1, rightBracketIndex); arrayTypeValueLocalPart = arrayTypeValueLocalPart.substring(0, leftBracketIndex); // If multi-dim array set to soapenc:Array if (arrayTypeValueLocalPart.endsWith("]")) { defaultItemType = Constants.SOAP_ARRAY; int bracket = arrayTypeValueLocalPart.indexOf("["); innerQName = new QName(arrayTypeValueNamespaceURI, arrayTypeValueLocalPart.substring(0, bracket)); innerDimString = arrayTypeValueLocalPart.substring(bracket); } else { defaultItemType = new QName(arrayTypeValueNamespaceURI, arrayTypeValueLocalPart); } } else { String arraySizeValue = attributes.getValue(soapConstants.getEncodingURI(), Constants.ATTR_ARRAY_SIZE); int leftStarIndex = arraySizeValue.lastIndexOf('*'); // Skip to num if any if (leftStarIndex != -1) { // "*" => "" if (leftStarIndex == 0 && arraySizeValue.length() == 1) { // "* *" => "" } else if (leftStarIndex == (arraySizeValue.length() - 1)) { throw new IllegalArgumentException( Messages.getMessage("badArraySize00", "" + arraySizeValue)); // "* N" => "N" } else { dimString = arraySizeValue.substring(leftStarIndex + 2); innerQName = arrayTypeValue; innerDimString = arraySizeValue.substring(0, leftStarIndex + 1); } } else { dimString = arraySizeValue; } if (innerDimString == null || innerDimString.length() == 0) { defaultItemType = arrayTypeValue; } else { defaultItemType = Constants.SOAP_ARRAY12; } } } // If no type QName and no defaultItemType qname, use xsd:anyType if (defaultItemType == null && typeQName == null) { Class destClass = context.getDestinationClass(); if (destClass != null && destClass.isArray()) { // This will get set OK down below... } else { defaultItemType = Constants.XSD_ANYTYPE; } } // Determine the class type for the array. arrayClass = null; if (typeQName != null) { arrayClass = context.getTypeMapping().getClassForQName(typeQName); } if (typeQName == null || arrayClass == null) { // type= information is not sufficient. // Get an array of the default item type. Class arrayItemClass = null; QName compQName = defaultItemType; // Nested array, use the innermost qname String dims = "[]"; if (innerQName != null) { compQName = innerQName; if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) { // With SOAP 1.2 Array, we append [] for each * found int offset = 0; while ((offset = innerDimString.indexOf('*', offset)) != -1) { dims += "[]"; offset++; } } else { // With SOAP 1.1 Array, we can append directly the complete innerDimString dims += innerDimString; } } // item Class arrayItemClass = context.getTypeMapping().getClassForQName(compQName); if (arrayItemClass != null) { try { // Append the dimension found to the classname computed from the itemClass // to form the array classname // String loadableArrayClassName = JavaUtils .getLoadableClassName(JavaUtils.getTextClassName(arrayItemClass.getName()) + dims); arrayClass = ClassUtils.forName(loadableArrayClassName, true, arrayItemClass.getClassLoader()); } catch (Exception e) { throw new SAXException(Messages.getMessage("noComponent00", "" + defaultItemType)); } } } if (arrayClass == null) { arrayClass = context.getDestinationClass(); } if (arrayClass == null) { throw new SAXException(Messages.getMessage("noComponent00", "" + defaultItemType)); } if (dimString == null || dimString.length() == 0) { // Size determined using length of the members value = new ArrayListExtension(arrayClass); } else { try { StringTokenizer tokenizer; if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) { tokenizer = new StringTokenizer(dimString); } else { tokenizer = new StringTokenizer(dimString, "[],"); } length = Integer.parseInt(tokenizer.nextToken()); if (tokenizer.hasMoreTokens()) { // If the array is passed as a multi-dimensional array // (i.e. int[2][3]) then store all of the // mult-dim lengths. // The valueReady method uses this array to set the // proper mult-dim element. mDimLength = new ArrayList(); mDimLength.add(new Integer(length)); while (tokenizer.hasMoreTokens()) { mDimLength.add(new Integer(Integer.parseInt(tokenizer.nextToken()))); } } // Create an ArrayListExtension class to store the ArrayList // plus converted objects. ArrayList list = new ArrayListExtension(arrayClass, length); // This is expensive as our array may not grown this big. // Prevents problems when XML claims a huge size // that it doesn't actually fill. //for (int i = 0; i < length; i++) { // list.add(null); //} value = list; } catch (NumberFormatException e) { throw new IllegalArgumentException(Messages.getMessage("badInteger00", dimString)); } } // If soapenc:offset specified, set the current index accordingly String offset = Constants.getValue(attributes, Constants.URIS_SOAP_ENC, Constants.ATTR_OFFSET); if (offset != null) { if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) { throw new SAXException(Messages.getMessage("noSparseArray")); } int leftBracketIndex = offset.lastIndexOf('['); int rightBracketIndex = offset.lastIndexOf(']'); if (leftBracketIndex == -1 || rightBracketIndex == -1 || rightBracketIndex < leftBracketIndex) { throw new SAXException(Messages.getMessage("badOffset00", offset)); } curIndex = convertToIndex(offset.substring(leftBracketIndex + 1, rightBracketIndex), "badOffset00"); } if (log.isDebugEnabled()) { log.debug("Exit: ArrayDeserializer::startElement()"); } } /** * onStartChild is called on each child element. * @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 { if (log.isDebugEnabled()) { log.debug("Enter: ArrayDeserializer.onStartChild()"); } // If the position attribute is set, // use it to update the current index if (attributes != null) { String pos = Constants.getValue(attributes, Constants.URIS_SOAP_ENC, Constants.ATTR_POSITION); if (pos != null) { if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) { throw new SAXException(Messages.getMessage("noSparseArray")); } int leftBracketIndex = pos.lastIndexOf('['); int rightBracketIndex = pos.lastIndexOf(']'); if (leftBracketIndex == -1 || rightBracketIndex == -1 || rightBracketIndex < leftBracketIndex) { throw new SAXException(Messages.getMessage("badPosition00", pos)); } curIndex = convertToIndex(pos.substring(leftBracketIndex + 1, rightBracketIndex), "badPosition00"); } // If the xsi:nil attribute, set the value to null // and return since there is nothing to deserialize. if (context.isNil(attributes)) { setChildValue(null, new Integer(curIndex++)); return null; } } // Use the xsi:type setting on the attribute if it exists. QName itemType = context.getTypeFromAttributes(namespace, localName, attributes); // Get the deserializer for the type. Deserializer dSer = null; if (itemType != null && (context.getCurElement().getHref() == null)) { dSer = context.getDeserializerForType(itemType); } if (dSer == null) { // No deserializer can be found directly. Need to look harder QName defaultType = defaultItemType; Class javaType = null; if (arrayClass != null && arrayClass.isArray() && defaultType == null) { javaType = arrayClass.getComponentType(); defaultType = context.getTypeMapping().getTypeQName(javaType); } // We don't have a deserializer, the safest thing to do // is to set up using the DeserializerImpl below. // The DeserializerImpl will take care of href/id and // install the appropriate serializer, etc. The problem // is that takes a lot of time and will occur // all the time if no xsi:types are sent. Most of the // time an item is a simple schema type (i.e. String) // so the following shortcut is used to get a Deserializer // for these cases. if (itemType == null && dSer == null) { if (defaultType != null && SchemaUtils.isSimpleSchemaType(defaultType)) { dSer = context.getDeserializer(javaType, defaultType); } } // If no deserializer is // found, the deserializer is set to DeserializerImpl(). // It is possible that the element has an href, thus we // won't know the type until the definitition is encountered. if (dSer == null) { dSer = new DeserializerImpl(); // Determine a default type for the deserializer if (itemType == null) { dSer.setDefaultType(defaultType); } } } // Register the callback value target, and // keep track of this index so we know when it has been set. dSer.registerValueTarget(new DeserializerTarget(this, new Integer(curIndex))); // The framework handles knowing when the value is complete, as // long as we tell it about each child we're waiting on... addChildDeserializer(dSer); curIndex++; // In case of multi-array, we need to specify the destination class // of the children elements of this element array deserializer. context.setDestinationClass(arrayClass.getComponentType()); if (log.isDebugEnabled()) { log.debug("Exit: ArrayDeserializer.onStartChild()"); } return (SOAPHandler) dSer; } public void onEndChild(String namespace, String localName, DeserializationContext context) throws SAXException { // reverse onStartChild operation. context.setDestinationClass(arrayClass); } public void characters(char[] chars, int i, int i1) throws SAXException { for (int idx = i; i < i1; i++) { if (!Character.isWhitespace(chars[idx])) throw new SAXException(Messages.getMessage("charsInArray")); } } /** * set is called during deserialization to assign * the Object value to the array position indicated by hint. * The hint is always a single Integer. If the array being * deserialized is a multi-dimensional array, the hint is * converted into a series of indices to set the correct * nested position. * The array deserializer always deserializes into * an ArrayList, which is converted and copied into the * actual array after completion (by valueComplete). * It is important to wait until all indices have been * processed before invoking valueComplete. * @param value value of the array element * @param hint index of the array element (Integer) **/ public void setChildValue(Object value, Object hint) throws SAXException { if (log.isDebugEnabled()) { log.debug("Enter: ArrayDeserializer::setValue(" + value + ", " + hint + ")"); } ArrayList list = (ArrayList) this.value; int offset = ((Integer) hint).intValue(); if (this.mDimLength == null) { // Normal Case: Set the element in the list // grow the list if necessary to accomodate the new member while (list.size() <= offset) { list.add(null); } list.set(offset, value); } else { // Multi-Dim Array case: Need to find the nested ArrayList // and set the proper element. // Convert the offset into a series of indices ArrayList mDimIndex = toMultiIndex(offset); // Get/Create the nested ArrayList for (int i = 0; i < mDimLength.size(); i++) { int length = ((Integer) mDimLength.get(i)).intValue(); int index = ((Integer) mDimIndex.get(i)).intValue(); while (list.size() < length) { list.add(null); } // If not the last dimension, get the nested ArrayList // Else set the value if (i < mDimLength.size() - 1) { if (list.get(index) == null) { list.set(index, new ArrayList()); } list = (ArrayList) list.get(index); } else { list.set(index, value); } } } } /** * When valueComplete() is invoked on the array, * first convert the array value into the expected array. * Then call super.valueComplete() to inform referents * that the array value is ready. **/ public void valueComplete() throws SAXException { if (componentsReady()) { try { if (arrayClass != null) { value = JavaUtils.convert(value, arrayClass); } } catch (RuntimeException e) { // We must ignore exceptions from convert for Arrays with null - why? } } super.valueComplete(); } /** * Converts the given string to an index. * Assumes the string consists of a brackets surrounding comma * separated digits. For example "[2]" or [2,3]". * The routine returns a single index. * For example "[2]" returns 2. * For example "[2,3]" depends on the size of the multiple dimensions. * if the dimensions are "[3,5]" then 13 is returned (2*5) + 3. * @param text representing index text * @param exceptKey exception message key * @return index */ private int convertToIndex(String text, String exceptKey) throws SAXException { StringTokenizer tokenizer = new StringTokenizer(text, "[],"); int index = 0; try { if (mDimLength == null) { // Normal Case: Single dimension index = Integer.parseInt(tokenizer.nextToken()); if (tokenizer.hasMoreTokens()) { throw new SAXException(Messages.getMessage(exceptKey, text)); } } else { // Multiple Dimensions: int dim = -1; ArrayList work = new ArrayList(); while (tokenizer.hasMoreTokens()) { // Problem if the number of dimensions specified exceeds // the number of dimensions of arrayType dim++; if (dim >= mDimLength.size()) { throw new SAXException(Messages.getMessage(exceptKey, text)); } // Get the next token and convert to integer int workIndex = Integer.parseInt(tokenizer.nextToken()); // Problem if the index is out of range. if (workIndex < 0 || workIndex >= ((Integer) mDimLength.get(dim)).intValue()) { throw new SAXException(Messages.getMessage(exceptKey, text)); } work.add(new Integer(workIndex)); } index = toSingleIndex(work); // Convert to single index } } catch (SAXException e) { throw e; } catch (Exception e) { throw new SAXException(Messages.getMessage(exceptKey, text)); } return index; } /** * Converts single index to list of multiple indices. * @param single index * @return list of multiple indices or null if not multiple indices. */ private ArrayList toMultiIndex(int single) { if (mDimLength == null) return null; // Calculate the index factors if not already known if (mDimFactor == null) { mDimFactor = new ArrayList(); for (int i = 0; i < mDimLength.size(); i++) { int factor = 1; for (int j = i + 1; j < mDimLength.size(); j++) { factor *= ((Integer) mDimLength.get(j)).intValue(); } mDimFactor.add(new Integer(factor)); } } ArrayList rc = new ArrayList(); for (int i = 0; i < mDimLength.size(); i++) { int factor = ((Integer) mDimFactor.get(i)).intValue(); rc.add(new Integer(single / factor)); single = single % factor; } return rc; } /** * Converts multiple index to single index. * @param indexArray list of multiple indices * @return single index */ private int toSingleIndex(ArrayList indexArray) { if (mDimLength == null || indexArray == null) return -1; // Calculate the index factors if not already known if (mDimFactor == null) { mDimFactor = new ArrayList(); for (int i = 0; i < mDimLength.size(); i++) { int factor = 1; for (int j = i + 1; j < mDimLength.size(); j++) { factor *= ((Integer) mDimLength.get(j)).intValue(); } mDimFactor.add(new Integer(factor)); } } int single = 0; for (int i = 0; i < indexArray.size(); i++) { single += ((Integer) mDimFactor.get(i)).intValue() * ((Integer) indexArray.get(i)).intValue(); } return single; } /** * During processing, the Array Deserializer stores the array in * an ArrayListExtension class. This class contains all of the * normal function of an ArrayList, plus it keeps a list of the * converted array values. This class is essential to support * arrays that are multi-referenced. **/ public class ArrayListExtension extends ArrayList implements JavaUtils.ConvertCache { private HashMap table = null; private Class arrayClass = null; // The array class. /** * Constructors */ ArrayListExtension(Class arrayClass) { super(); this.arrayClass = arrayClass; // Don't use the array class as a hint // if it can't be instantiated if (arrayClass == null || arrayClass.isInterface() || java.lang.reflect.Modifier.isAbstract(arrayClass.getModifiers())) { arrayClass = null; } } ArrayListExtension(Class arrayClass, int length) { // Sanity check the array size, 50K is big enough to start super(length > 50000 ? 50000 : length); this.arrayClass = arrayClass; // Don't use the array class as a hint // if it can't be instantiated if (arrayClass == null || arrayClass.isInterface() || java.lang.reflect.Modifier.isAbstract(arrayClass.getModifiers())) { arrayClass = null; } } /** * Store converted value **/ public void setConvertedValue(Class cls, Object value) { if (table == null) table = new HashMap(); table.put(cls, value); } /** * Get previously converted value **/ public Object getConvertedValue(Class cls) { if (table == null) return null; return table.get(cls); } /** * Get the destination array class described by the xml **/ public Class getDestClass() { return arrayClass; } } }