| /* |
| * Copyright 2001-2002,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.description.ElementDesc; |
| import org.apache.axis.description.FieldDesc; |
| import org.apache.axis.description.TypeDesc; |
| import org.apache.axis.encoding.ConstructorTarget; |
| import org.apache.axis.encoding.DeserializationContext; |
| import org.apache.axis.encoding.Deserializer; |
| import org.apache.axis.encoding.DeserializerImpl; |
| import org.apache.axis.encoding.Target; |
| import org.apache.axis.encoding.TypeMapping; |
| import org.apache.axis.message.MessageElement; |
| import org.apache.axis.message.SOAPHandler; |
| import org.apache.axis.soap.SOAPConstants; |
| import org.apache.axis.utils.BeanPropertyDescriptor; |
| import org.apache.axis.utils.Messages; |
| import org.apache.commons.logging.Log; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| |
| import javax.xml.namespace.QName; |
| import java.io.CharArrayWriter; |
| import java.io.Serializable; |
| import java.lang.reflect.Constructor; |
| import java.util.Map; |
| |
| /** |
| * General purpose deserializer for an arbitrary java bean. |
| * |
| * @author Sam Ruby <rubys@us.ibm.com> |
| * @author Rich Scheuerle <scheu@us.ibm.com> |
| * @author Tom Jordahl <tomj@macromedia.com> |
| */ |
| public class BeanDeserializer extends DeserializerImpl implements Serializable |
| { |
| protected static Log log = |
| LogFactory.getLog(BeanDeserializer.class.getName()); |
| |
| private final CharArrayWriter val = new CharArrayWriter(); |
| |
| QName xmlType; |
| Class javaType; |
| protected Map propertyMap = null; |
| protected QName prevQName; |
| |
| /** |
| * Constructor if no default constructor |
| */ |
| protected Constructor constructorToUse = null; |
| |
| /** |
| * Constructor Target object to use (if constructorToUse != null) |
| */ |
| protected Target constructorTarget = null; |
| |
| /** Type metadata about this class for XML deserialization */ |
| protected TypeDesc typeDesc = null; |
| |
| // This counter is updated to deal with deserialize collection properties |
| protected int collectionIndex = -1; |
| |
| protected SimpleDeserializer cacheStringDSer = null; |
| protected QName cacheXMLType = null; |
| |
| // Construct BeanSerializer for the indicated class/qname |
| public BeanDeserializer(Class javaType, QName xmlType) { |
| this(javaType, xmlType, TypeDesc.getTypeDescForClass(javaType)); |
| } |
| |
| // Construct BeanDeserializer for the indicated class/qname and meta Data |
| public BeanDeserializer(Class javaType, QName xmlType, TypeDesc typeDesc ) { |
| this(javaType, xmlType, typeDesc, |
| BeanDeserializerFactory.getProperties(javaType, typeDesc)); |
| } |
| |
| // Construct BeanDeserializer for the indicated class/qname and meta Data |
| public BeanDeserializer(Class javaType, QName xmlType, TypeDesc typeDesc, |
| Map propertyMap ) { |
| this.xmlType = xmlType; |
| this.javaType = javaType; |
| this.typeDesc = typeDesc; |
| this.propertyMap = propertyMap; |
| |
| // create a value |
| try { |
| value=javaType.newInstance(); |
| } catch (Exception e) { |
| // Don't process the exception at this point. |
| // This is defered until the call to startElement |
| // which will throw the exception. |
| } |
| } |
| |
| /** |
| * startElement |
| * |
| * The ONLY reason that this method is overridden is so that |
| * the object value can be set or a reasonable exception is thrown |
| * indicating that the object cannot be created. This is done |
| * at this point so that it occurs BEFORE href/id processing. |
| * @param namespace is the namespace of the element |
| * @param localName is the name of the element |
| * @param prefix is the prefix of the element |
| * @param attributes are the attributes on the element...used to get the |
| * type |
| * @param context is the DeserializationContext |
| */ |
| public void startElement(String namespace, String localName, |
| String prefix, Attributes attributes, |
| DeserializationContext context) |
| throws SAXException |
| { |
| // Create the bean object if it was not already |
| // created in the constructor. |
| if (value == null) { |
| try { |
| value=javaType.newInstance(); |
| } catch (Exception e) { |
| // Use first found constructor. |
| // Note : the right way is to use XML mapping information |
| // for example JSR 109's constructor-parameter-order |
| Constructor[] constructors = javaType.getConstructors(); |
| if (constructors.length > 0) { |
| constructorToUse = constructors[0]; |
| } |
| |
| // Failed to create an object if no constructor |
| if (constructorToUse == null) { |
| throw new SAXException(Messages.getMessage("cantCreateBean00", |
| javaType.getName(), |
| e.toString())); |
| } |
| } |
| } |
| // Invoke super.startElement to do the href/id processing. |
| super.startElement(namespace, localName, |
| prefix, attributes, context); |
| } |
| |
| /** |
| * Deserializer interface called on each child element encountered in |
| * the XML stream. |
| * @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 |
| { |
| handleMixedContent(); |
| |
| BeanPropertyDescriptor propDesc = null; |
| FieldDesc fieldDesc = null; |
| |
| SOAPConstants soapConstants = context.getSOAPConstants(); |
| String encodingStyle = context.getEncodingStyle(); |
| boolean isEncoded = Constants.isSOAP_ENC(encodingStyle); |
| |
| QName elemQName = new QName(namespace, localName); |
| // The collectionIndex needs to be reset for Beans with multiple arrays |
| if ((prevQName == null) || (!prevQName.equals(elemQName))) { |
| collectionIndex = -1; |
| } |
| |
| boolean isArray = false; |
| QName itemQName = null; |
| if (typeDesc != null) { |
| // Lookup the name appropriately (assuming an unqualified |
| // name for SOAP encoding, using the namespace otherwise) |
| String fieldName = typeDesc.getFieldNameForElement(elemQName, |
| isEncoded); |
| propDesc = (BeanPropertyDescriptor)propertyMap.get(fieldName); |
| fieldDesc = typeDesc.getFieldByName(fieldName); |
| |
| if (fieldDesc != null) { |
| ElementDesc element = (ElementDesc)fieldDesc; |
| isArray = element.isMaxOccursUnbounded(); |
| itemQName = element.getItemQName(); |
| } |
| } |
| |
| if (propDesc == null) { |
| // look for a field by this name. |
| propDesc = (BeanPropertyDescriptor) propertyMap.get(localName); |
| } |
| |
| // try and see if this is an xsd:any namespace="##any" element before |
| // reporting a problem |
| if (propDesc == null || |
| (((prevQName != null) && prevQName.equals(elemQName) && |
| !(propDesc.isIndexed()||isArray) |
| && getAnyPropertyDesc() != null ))) { |
| // try to put unknown elements into a SOAPElement property, if |
| // appropriate |
| prevQName = elemQName; |
| propDesc = getAnyPropertyDesc(); |
| if (propDesc != null) { |
| try { |
| MessageElement [] curElements = (MessageElement[])propDesc.get(value); |
| int length = 0; |
| if (curElements != null) { |
| length = curElements.length; |
| } |
| MessageElement [] newElements = new MessageElement[length + 1]; |
| if (curElements != null) { |
| System.arraycopy(curElements, 0, |
| newElements, 0, length); |
| } |
| MessageElement thisEl = context.getCurElement(); |
| |
| newElements[length] = thisEl; |
| propDesc.set(value, newElements); |
| // if this is the first pass through the MessageContexts |
| // make sure that the correct any element is set, |
| // that is the child of the current MessageElement, however |
| // on the first pass this child has not been set yet, so |
| // defer it to the child SOAPHandler |
| if (!localName.equals(thisEl.getName())) { |
| return new SOAPHandler(newElements, length); |
| } |
| return new SOAPHandler(); |
| } catch (Exception e) { |
| throw new SAXException(e); |
| } |
| } |
| } |
| |
| |
| if (propDesc == null) { |
| // No such field |
| throw new SAXException( |
| Messages.getMessage("badElem00", javaType.getName(), |
| localName)); |
| } |
| |
| prevQName = elemQName; |
| // Get the child's xsi:type if available |
| QName childXMLType = context.getTypeFromAttributes(namespace, |
| localName, |
| attributes); |
| |
| String href = attributes.getValue(soapConstants.getAttrHref()); |
| Class fieldType = propDesc.getType(); |
| |
| // If no xsi:type or href, check the meta-data for the field |
| if (childXMLType == null && fieldDesc != null && href == null) { |
| childXMLType = fieldDesc.getXmlType(); |
| if (itemQName != null) { |
| // This is actually a wrapped literal array and should be |
| // deserialized with the ArrayDeserializer |
| childXMLType = Constants.SOAP_ARRAY; |
| fieldType = propDesc.getActualType(); |
| } else { |
| childXMLType = fieldDesc.getXmlType(); |
| } |
| } |
| |
| // Get Deserializer for child, default to using DeserializerImpl |
| Deserializer dSer = getDeserializer(childXMLType, |
| fieldType, |
| href, |
| context); |
| |
| // It is an error if the dSer is not found - the only case where we |
| // wouldn't have a deserializer at this point is when we're trying |
| // to deserialize something we have no clue about (no good xsi:type, |
| // no good metadata). |
| if (dSer == null) { |
| dSer = context.getDeserializerForClass(propDesc.getType()); |
| } |
| |
| // Fastpath nil checks... |
| if (context.isNil(attributes)) { |
| if (propDesc != null && (propDesc.isIndexed()||isArray)) { |
| if (!((dSer != null) && (dSer instanceof ArrayDeserializer))) { |
| collectionIndex++; |
| dSer.registerValueTarget(new BeanPropertyTarget(value, |
| propDesc, collectionIndex)); |
| addChildDeserializer(dSer); |
| return (SOAPHandler)dSer; |
| } |
| } |
| return null; |
| } |
| |
| if (dSer == null) { |
| throw new SAXException(Messages.getMessage("noDeser00", |
| childXMLType.toString())); |
| } |
| |
| if (constructorToUse != null) { |
| if (constructorTarget == null) { |
| constructorTarget = new ConstructorTarget(constructorToUse, this); |
| } |
| dSer.registerValueTarget(constructorTarget); |
| } else if (propDesc.isWriteable()) { |
| // If this is an indexed property, and the deserializer we found |
| // was NOT the ArrayDeserializer, this is a non-SOAP array: |
| // <bean> |
| // <field>value1</field> |
| // <field>value2</field> |
| // ... |
| // In this case, we want to use the collectionIndex and make sure |
| // the deserialized value for the child element goes into the |
| // right place in the collection. |
| |
| // Register value target |
| if ((itemQName != null || propDesc.isIndexed() || isArray) && !(dSer instanceof ArrayDeserializer)) { |
| collectionIndex++; |
| dSer.registerValueTarget(new BeanPropertyTarget(value, |
| propDesc, collectionIndex)); |
| } else { |
| // If we're here, the element maps to a single field value, |
| // whether that be a "basic" type or an array, so use the |
| // normal (non-indexed) BeanPropertyTarget form. |
| collectionIndex = -1; |
| dSer.registerValueTarget(new BeanPropertyTarget(value, |
| propDesc)); |
| } |
| } |
| |
| // Let the framework know that we need this deserializer to complete |
| // for the bean to complete. |
| addChildDeserializer(dSer); |
| |
| return (SOAPHandler)dSer; |
| } |
| |
| /** |
| * Get a BeanPropertyDescriptor which indicates where we should |
| * put extensibility elements (i.e. XML which falls under the |
| * auspices of an <xsd:any> declaration in the schema) |
| * |
| * @return an appropriate BeanPropertyDescriptor, or null |
| */ |
| public BeanPropertyDescriptor getAnyPropertyDesc() { |
| if (typeDesc == null) |
| return null; |
| |
| return typeDesc.getAnyDesc(); |
| } |
| |
| /** |
| * Set the bean properties that correspond to element attributes. |
| * |
| * This method is invoked after startElement when the element requires |
| * deserialization (i.e. the element is not an href and the value is not |
| * nil.) |
| * @param namespace is the namespace of the element |
| * @param localName is the name of the element |
| * @param prefix is the prefix of the element |
| * @param attributes are the attributes on the element...used to get the |
| * type |
| * @param context is the DeserializationContext |
| */ |
| public void onStartElement(String namespace, String localName, |
| String prefix, Attributes attributes, |
| DeserializationContext context) |
| throws SAXException { |
| |
| // The value should have been created or assigned already. |
| // This code may no longer be needed. |
| if (value == null && constructorToUse == null) { |
| // create a value |
| try { |
| value=javaType.newInstance(); |
| } catch (Exception e) { |
| throw new SAXException(Messages.getMessage("cantCreateBean00", |
| javaType.getName(), |
| e.toString())); |
| } |
| } |
| |
| // If no type description meta data, there are no attributes, |
| // so we are done. |
| if (typeDesc == null) |
| return; |
| |
| // loop through the attributes and set bean properties that |
| // correspond to attributes |
| for (int i=0; i < attributes.getLength(); i++) { |
| QName attrQName = new QName(attributes.getURI(i), |
| attributes.getLocalName(i)); |
| String fieldName = typeDesc.getFieldNameForAttribute(attrQName); |
| if (fieldName == null) |
| continue; |
| |
| FieldDesc fieldDesc = typeDesc.getFieldByName(fieldName); |
| |
| // look for the attribute property |
| BeanPropertyDescriptor bpd = |
| (BeanPropertyDescriptor) propertyMap.get(fieldName); |
| if (bpd != null) { |
| if (constructorToUse == null) { |
| // check only if default constructor |
| if (!bpd.isWriteable() || bpd.isIndexed()) continue ; |
| } |
| |
| // Get the Deserializer for the attribute |
| Deserializer dSer = getDeserializer(fieldDesc.getXmlType(), |
| bpd.getType(), |
| null, |
| context); |
| if (dSer == null) { |
| dSer = context.getDeserializerForClass(bpd.getType()); |
| } |
| if (dSer == null) |
| throw new SAXException( |
| Messages.getMessage("unregistered00", |
| bpd.getType().toString())); |
| |
| if (! (dSer instanceof SimpleDeserializer)) |
| throw new SAXException( |
| Messages.getMessage("AttrNotSimpleType00", |
| bpd.getName(), |
| bpd.getType().toString())); |
| |
| // Success! Create an object from the string and set |
| // it in the bean |
| try { |
| dSer.onStartElement(namespace, localName, prefix, |
| attributes, context); |
| Object val = ((SimpleDeserializer)dSer). |
| makeValue(attributes.getValue(i)); |
| if (constructorToUse == null) { |
| bpd.set(value, val); |
| } else { |
| // add value for our constructor |
| if (constructorTarget == null) { |
| constructorTarget = new ConstructorTarget(constructorToUse, this); |
| } |
| constructorTarget.set(val); |
| } |
| } catch (Exception e) { |
| throw new SAXException(e); |
| } |
| |
| } // if |
| } // attribute loop |
| } |
| |
| /** |
| * Get the Deserializer for the attribute or child element. |
| * @param xmlType QName of the attribute/child element or null if not known. |
| * @param javaType Class of the corresponding property |
| * @param href String is the value of the href attribute, which is used |
| * to determine whether the child element is complete or an |
| * href to another element. |
| * @param context DeserializationContext |
| * @return Deserializer or null if not found. |
| */ |
| protected Deserializer getDeserializer(QName xmlType, |
| Class javaType, |
| String href, |
| DeserializationContext context) { |
| if (javaType.isArray()) { |
| context.setDestinationClass(javaType); |
| } |
| // See if we have a cached deserializer |
| if (cacheStringDSer != null) { |
| if (String.class.equals(javaType) && |
| href == null && |
| (cacheXMLType == null && xmlType == null || |
| cacheXMLType != null && cacheXMLType.equals(xmlType))) { |
| cacheStringDSer.reset(); |
| return cacheStringDSer; |
| } |
| } |
| |
| Deserializer dSer = null; |
| |
| if (xmlType != null && href == null) { |
| // Use the xmlType to get the deserializer. |
| dSer = context.getDeserializerForType(xmlType); |
| } else { |
| // If the xmlType is not set, get a default xmlType |
| TypeMapping tm = context.getTypeMapping(); |
| QName defaultXMLType = tm.getTypeQName(javaType); |
| // If there is not href, then get the deserializer |
| // using the javaType and default XMLType, |
| // If there is an href, the create the generic |
| // DeserializerImpl and set its default type (the |
| // default type is used if the href'd element does |
| // not have an xsi:type. |
| if (href == null) { |
| dSer = context.getDeserializer(javaType, defaultXMLType); |
| } else { |
| dSer = new DeserializerImpl(); |
| context.setDestinationClass(javaType); |
| dSer.setDefaultType(defaultXMLType); |
| } |
| } |
| if (javaType.equals(String.class) && |
| dSer instanceof SimpleDeserializer) { |
| cacheStringDSer = (SimpleDeserializer) dSer; |
| cacheXMLType = xmlType; |
| } |
| return dSer; |
| } |
| |
| public void characters(char[] chars, int start, int end) throws SAXException { |
| val.write(chars, start, end); |
| } |
| |
| public void onEndElement(String namespace, String localName, |
| DeserializationContext context) throws SAXException { |
| handleMixedContent(); |
| } |
| |
| protected void handleMixedContent() throws SAXException { |
| BeanPropertyDescriptor propDesc = getAnyPropertyDesc(); |
| if (propDesc == null || val.size() == 0) { |
| return; |
| } |
| String textValue = val.toString().trim(); |
| val.reset(); |
| if (textValue.length() == 0) { |
| return; |
| } |
| try { |
| MessageElement[] curElements = (MessageElement[]) propDesc.get(value); |
| int length = 0; |
| if (curElements != null) { |
| length = curElements.length; |
| } |
| MessageElement[] newElements = new MessageElement[length + 1]; |
| if (curElements != null) { |
| System.arraycopy(curElements, 0, |
| newElements, 0, length); |
| } |
| MessageElement thisEl = new MessageElement(new org.apache.axis.message.Text(textValue)); |
| newElements[length] = thisEl; |
| propDesc.set(value, newElements); |
| } catch (Exception e) { |
| throw new SAXException(e); |
| } |
| } |
| } |