| BeanDeserializer.java |
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2002 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.Constants;
import org.jboss.axis.description.ElementDesc;
import org.jboss.axis.description.FieldDesc;
import org.jboss.axis.description.TypeDesc;
import org.jboss.axis.encoding.DeserializationContext;
import org.jboss.axis.encoding.Deserializer;
import org.jboss.axis.encoding.DeserializerImpl;
import org.jboss.axis.encoding.TypeMapping;
import org.jboss.axis.message.SOAPElementAxisImpl;
import org.jboss.axis.message.SOAPHandler;
import org.jboss.axis.soap.SOAPConstants;
import org.jboss.axis.utils.BeanPropertyDescriptor;
import org.jboss.axis.utils.JavaUtils;
import org.jboss.axis.utils.Messages;
import org.jboss.logging.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import javax.xml.namespace.QName;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Iterator;
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
{
private static Logger log = Logger.getLogger(BeanDeserializer.class.getName());
QName xmlType;
Class javaType;
protected Map propertyMap;
protected QName prevQName;
/**
* 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;
protected String lastFieldName = null;
// This is the constructor we find in startElement if the bean
// does not have a default constructor
protected DeferedBeanConstruction deferedConstruction;
// 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.
}
}
/**
* Set the bean properties that correspond to element attributes.
* <p/>
* 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
{
// reset just in case
lastFieldName = null;
// Create the bean object if it was not already
// created in the constructor.
if (value == null)
{
try
{
value = javaType.newInstance();
}
catch (Exception e)
{
// Get the property classes
ArrayList propTypes = new ArrayList();
Iterator it = propertyMap.values().iterator();
while (it.hasNext())
{
BeanPropertyDescriptor bpd = (BeanPropertyDescriptor)it.next();
propTypes.add(bpd.getType());
}
// Get the constructors
Constructor[] ctors = javaType.getConstructors();
// Reset the defered construction, this is necessary if the deserializer
// is beeing reused, which I am not sure about
deferedConstruction = null;
// Find the constructor that matches the bean property types
for (int i = 0; deferedConstruction == null && i < ctors.length; i++)
{
Constructor ctor = ctors[i];
Class[] ctorArgs = ctor.getParameterTypes();
if (ctorArgs.length > 0)
{
boolean allFound = true;
for (int j = 0; j < ctorArgs.length; j++)
{
allFound &= propTypes.contains(ctorArgs[j]);
}
if (allFound)
{
deferedConstruction = new DeferedBeanConstruction(ctor);
}
}
}
// if value still null, then we'll never be able to construct object
if (value == null && deferedConstruction == null)
{
// Failed to create an object.
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;
// Get the SOAP envelope URI
SOAPConstants soapConstants = context.getMessageContext().getSOAPConstants();
String soapenvURI = soapConstants.getEnvelopeURI();
// loop through the attributes and set bean properties that
// correspond to attributes
for (int i = 0; i < attributes.getLength(); i++)
{
// If the attribute belongs to the SOAP namespace, we ignore the attr namespace
// it might still be property of the bean
QName attrQName = null;
if (soapenvURI.equals(attributes.getURI(i)))
attrQName = new QName(attributes.getLocalName(i));
else
attrQName = new QName(attributes.getURI(i), attributes.getLocalName(i));
String fieldName = typeDesc.getFieldNameForAttribute(attrQName);
if (fieldName == null)
continue;
// look for the attribute property
BeanPropertyDescriptor bpd = (BeanPropertyDescriptor)propertyMap.get(fieldName);
if (bpd != null)
{
if (!bpd.isIndexed())
{
// Get the Deserializer for the attribute
Deserializer dSer = getDeserializer(null, bpd.getType(), null, context);
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 (value != null)
{
bpd.set(value, val);
}
else if (deferedConstruction != null)
{
value = deferedConstruction.newBeanInstance(val);
}
}
catch (Exception e)
{
throw new SAXException(e);
}
}
} // if
} // attribute loop
}
/**
* 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
{
log.debug("onStartChild: " + new QName(namespace, localName));
BeanPropertyDescriptor propDesc = null;
FieldDesc fieldDesc = null;
SOAPConstants soapConstants = context.getMessageContext().getSOAPConstants();
String encodingStyle = context.getMessageContext().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;
}
prevQName = elemQName;
// Fastpath nil checks...
// Commented out. If the bean property is an array (of simple types) that must groe to its propper size
// we need to process the nil
// TDI 20-June-2004
// com/sun/ts/tests/interop/webservices/jaxrpc/wsi/rpc/literal/marshalltest#MarshallJavaArrayTest
// if (context.isNil(attributes))
// return null;
if (typeDesc != null)
{
// Lookup the name appropriately (assuming an unqualified
// name for SOAP encoding, using the namespace otherwise)
String fieldName = typeDesc.getFieldNameForElement(elemQName);
propDesc = (BeanPropertyDescriptor)propertyMap.get(fieldName);
fieldDesc = typeDesc.getFieldByName(fieldName);
// Hold on to the field name so that getDeserializer() can use it
lastFieldName = fieldName;
}
else
{
lastFieldName = null;
}
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)
{
// try to put unknown elements into a SOAPElement property, if
// appropriate
propDesc = getAnyPropertyDesc();
if (propDesc != null)
{
try
{
SOAPElementAxisImpl[] curElements = (SOAPElementAxisImpl[])propDesc.get(value);
int length = 0;
if (curElements != null)
{
length = curElements.length;
}
SOAPElementAxisImpl[] newElements = new SOAPElementAxisImpl[length + 1];
if (curElements != null)
{
System.arraycopy(curElements, 0,
newElements, 0, length);
}
SOAPElementAxisImpl 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));
}
// Get the child's xsi:type if available
QName childXMLType = context.getTypeFromXSITypeAttr(namespace, localName, attributes);
String href = attributes.getValue(soapConstants.getAttrHref());
// If no xsi:type or href, check the meta-data for the field
if (childXMLType == null && fieldDesc != null && href == null)
{
childXMLType = fieldDesc.getXmlType();
}
// The bean property might be the super class of the actual type
Class propType = propDesc.getType();
TypeMapping tm = context.getTypeMapping();
Class childType = tm.getClassForQName(childXMLType);
if (childType != null && propType.isAssignableFrom(childType))
propType = childType;
// Get Deserializer for child, default to using DeserializerImpl
Deserializer dSer = getDeserializer(childXMLType, propType, 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)
{
// FIXME : Currently this doesn't throw an error solely to enable the
// "terra" testcase to pass. We should, IMO, fix the test (either
// to support <xsd:list> or to throw an error when we find such a thing
// in the WSDL at WSDL2Java time). Once that's done, this should be
// uncommented and the next two lines deleted.
//
// throw new SAXException(Messages.getMessage("noDeser00",
// childXMLType.toString()));
dSer = new DeserializerImpl();
return (SOAPHandler)dSer;
}
// Register value target
if (propDesc.isWriteable() || deferedConstruction != null)
{
// 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.
if (propDesc.isIndexed() && !(dSer instanceof ArrayDeserializer))
{
collectionIndex++;
dSer.registerValueTarget(getBeanPropertyTarget(propDesc));
}
// If this is literal style and the property type is an Array
// deserialize each item as if it was the property.
// This should probably be handled by a LiteralArrayDeserializer.
// TDI 20-June-2004
// com/sun/ts/tests/interop/webservices/jaxrpc/wsi/rpc/literal/marshalltest#MarshallJavaArrayTest
else if (!isEncoded && JavaUtils.isArrayClass(propDesc.getType()) &&
!(dSer instanceof Base64Deserializer) && !(dSer instanceof HexDeserializer))
{
collectionIndex++;
dSer.registerValueTarget(getBeanPropertyTarget(propDesc));
}
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(getBeanPropertyTarget(propDesc));
}
}
// Let the framework know that we need this deserializer to complete
// for the bean to complete.
addChildDeserializer(dSer);
return (SOAPHandler)dSer;
}
/** Check that we have a valid bean instance
*/
public void onEndElement(String namespace, String localName, DeserializationContext context) throws SAXException
{
super.onEndElement(namespace, localName, context);
if (value == null && deferedConstruction != null)
throw new SAXException("Could not construct bean using: " + deferedConstruction);
}
/**
* Get the target, its either direct on the value or defered via an constructor
*/
private BeanPropertyTarget getBeanPropertyTarget(BeanPropertyDescriptor propDesc)
{
BeanPropertyTarget target = null;
if (value != null)
target = new BeanPropertyTarget(value, propDesc, collectionIndex);
else if (deferedConstruction != null)
target = new BeanPropertyTarget(this, deferedConstruction, propDesc);
else
throw new IllegalStateException("Cannot get a valid property target");
return target;
}
/**
* 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();
}
/**
* 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)
{
String encodingStyle = context.getMessageContext().getEncodingStyle();
boolean isEncoded = Constants.isSOAP_ENC(encodingStyle);
// 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;
TypeMapping tm = context.getTypeMapping();
if (xmlType != null && href == null)
{
// Use the xmlType to get the deserializer.
dSer = context.getDeserializerForType(xmlType);
}
if (dSer == null)
{
// If the xmlType is not set, get a default xmlType
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();
dSer.setDefaultType(defaultXMLType);
}
}
if (javaType.equals(String.class) &&
dSer instanceof SimpleDeserializer)
{
cacheStringDSer = (SimpleDeserializer)dSer;
cacheXMLType = xmlType;
}
// If this is literal style and the javaType is an Array
// deserialize each item as if it was the property.
// This should probably be handled by a LiteralArrayDeserializer.
// TDI 20-June-2004
// com/sun/ts/tests/interop/webservices/jaxrpc/wsi/rpc/literal/marshalltest#MarshallJavaArrayTest
if (dSer == null && !isEncoded && JavaUtils.isArrayClass(javaType) && javaType != Byte[].class && javaType != byte[].class)
{
FieldDesc fieldDesc = (lastFieldName != null) ? typeDesc.getFieldByName(lastFieldName) : null;
Class compType = javaType.getComponentType();
QName itemXmlType = null;
if (fieldDesc != null && fieldDesc instanceof ElementDesc)
{
itemXmlType = ((ElementDesc)fieldDesc).getItemXmlType();
}
// If there is no itemXmlType defined, or no fieldDesc is available use the typemapping
// on the component type
if (itemXmlType == null)
{
itemXmlType = tm.getTypeQName(compType);
}
log.debug("Using itemXmlType = " + itemXmlType);
dSer = context.getDeserializer(compType, itemXmlType);
}
return dSer;
}
/**
* This will assign text content to the bean property that is an element
* with the asContent flag set to true. There can only be one, or is there a way to know which?
* TDI 22-June-2004
*/
public void characters(char[] p1, int p2, int p3) throws SAXException
{
super.characters(p1, p2, p3);
if (typeDesc != null)
{
Iterator it = propertyMap.values().iterator();
while (it.hasNext())
{
BeanPropertyDescriptor bpDesc = (BeanPropertyDescriptor)it.next();
if (bpDesc.isWriteable())
{
String name = bpDesc.getName();
FieldDesc fieldDesc = typeDesc.getFieldByName(name);
if (fieldDesc instanceof ElementDesc && ((ElementDesc)fieldDesc).isAsContent())
{
String strContent = new String(p1, p2, p3);
try
{
log.debug("Setting content property: " + name + "=" + strContent);
bpDesc.set(value, strContent);
}
catch (Exception e)
{
log.warn("Cannot set content property", e);
}
}
}
}
}
}
}
| BeanDeserializer.java |