org.apache.axis.encoding.ser.BeanDeserializer Maven / Gradle / Ivy
/*
* 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
* @author Rich Scheuerle
* @author Tom Jordahl
*/
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:
//
// value1
// value2
// ...
// 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());
// The java type is an array, but the context didn't
// know that we are an attribute. Better stick with
// simple types..
if (dSer instanceof ArrayDeserializer)
{
SimpleListDeserializerFactory factory =
new SimpleListDeserializerFactory(bpd.getType(),
fieldDesc.getXmlType());
dSer = (Deserializer)
factory.getDeserializerAs(dSer.getMechanismType());
}
}
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);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy