/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.cxf.aegis.type.basic;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.xml.namespace.QName;
import org.apache.cxf.aegis.Context;
import org.apache.cxf.aegis.DatabindingException;
import org.apache.cxf.aegis.type.Type;
import org.apache.cxf.aegis.type.TypeMapping;
import org.apache.cxf.aegis.type.TypeUtil;
import org.apache.cxf.aegis.util.NamespaceHelper;
import org.apache.cxf.aegis.util.XmlConstants;
import org.apache.cxf.aegis.xml.MessageReader;
import org.apache.cxf.aegis.xml.MessageWriter;
import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.interceptor.Fault;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Namespace;
/**
* Serializes JavaBeans.
*
* @author <a href="mailto:dan@envoisolutions.com">Dan Diephouse</a>
* @author <a href="mailto:jack.xu.hong@gmail.com">Jack Hong</a>
*/
public class BeanType extends Type {
private BeanTypeInfo info;
private boolean isInterface;
private boolean isException;
public BeanType() {
}
public BeanType(BeanTypeInfo info) {
this.info = info;
this.typeClass = info.getTypeClass();
initTypeClass();
}
private void initTypeClass() {
this.isInterface = typeClass.isInterface();
isException = Exception.class.isAssignableFrom(typeClass);
}
/*
* (non-Javadoc)
*
* @see org.codehaus.xfire.aegis.type.Type#readObject(org.codehaus.xfire.aegis.MessageReader,
* org.codehaus.xfire.MessageContext)
*/
@Override
public Object readObject(MessageReader reader, Context context) throws DatabindingException {
BeanTypeInfo inf = getTypeInfo();
try {
Class clazz = getTypeClass();
Object object = null;
InterfaceInvocationHandler delegate = null;
boolean isProxy = false;
if (isInterface) {
String impl = (String)context.get(clazz.getName() + ".implementation");
if (impl == null) {
delegate = new InterfaceInvocationHandler();
object = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {clazz},
delegate);
isProxy = true;
} else {
try {
clazz = ClassLoaderUtils.loadClass(impl, getClass());
object = clazz.newInstance();
} catch (ClassNotFoundException e) {
throw new DatabindingException("Could not find implementation class " + impl
+ " for class " + clazz.getName());
}
}
} else if (isException) {
object = createFromFault(context);
} else {
object = clazz.newInstance();
}
// Read attributes
while (reader.hasMoreAttributeReaders()) {
MessageReader childReader = reader.getNextAttributeReader();
QName name = childReader.getName();
Type type = inf.getType(name);
if (type != null) {
Object writeObj = type.readObject(childReader, context);
if (isProxy) {
delegate.writeProperty(name.getLocalPart(), writeObj);
} else {
writeProperty(name, object, writeObj, clazz, inf);
}
}
}
// Read child elements
while (reader.hasMoreElementReaders()) {
MessageReader childReader = reader.getNextElementReader();
QName name = childReader.getName();
BeanType parent = getBeanTypeWithProperty(name);
Type defaultType = null;
if (parent != null) {
info = parent.getTypeInfo();
defaultType = info.getType(name);
}
Type type = TypeUtil.getReadType(childReader.getXMLStreamReader(), context, defaultType);
// If the xsi:type lookup didn't work or there was none, use the
// normal Type.
if (type == null) {
parent = getBeanTypeWithProperty(name);
if (parent != null) {
inf = parent.getTypeInfo();
type = inf.getType(name);
} else {
type = null;
}
}
if (type != null) {
if (!childReader.isXsiNil()) {
Object writeObj = type.readObject(childReader, context);
if (isProxy) {
delegate.writeProperty(name.getLocalPart(), writeObj);
} else {
writeProperty(name, object, writeObj, clazz, inf);
}
} else {
if (!inf.isNillable(name)) {
throw new DatabindingException(name.getLocalPart()
+ " is nil, but not nillable.");
}
childReader.readToEnd();
}
} else {
childReader.readToEnd();
}
}
return object;
} catch (IllegalAccessException e) {
throw new DatabindingException("Illegal access. " + e.getMessage(), e);
} catch (InstantiationException e) {
throw new DatabindingException("Couldn't instantiate class. " + e.getMessage(), e);
} catch (SecurityException e) {
throw new DatabindingException("Illegal access. " + e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new DatabindingException("Illegal argument. " + e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new DatabindingException("Could not create class: " + e.getMessage(), e);
}
}
/**
* If the class is an exception, this will try and instantiate it with
* information from the XFireFault (if it exists).
*/
protected Object createFromFault(Context context)
throws SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class clazz = getTypeClass();
Constructor ctr;
Object o;
Fault fault = context.getFault();
try {
ctr = clazz.getConstructor(new Class[] {String.class, Throwable.class});
o = ctr.newInstance(new Object[] {fault.getMessage(), fault});
} catch (NoSuchMethodException e) {
try {
ctr = clazz.getConstructor(new Class[] {String.class, Exception.class});
o = ctr.newInstance(new Object[] {fault.getMessage(), fault});
} catch (NoSuchMethodException e1) {
try {
ctr = clazz.getConstructor(new Class[] {String.class});
o = ctr.newInstance(new Object[] {fault.getMessage()});
} catch (NoSuchMethodException e2) {
return clazz.newInstance();
}
}
}
return o;
}
/**
* Write the specified property to a field.
*/
protected void writeProperty(QName name, Object object, Object property, Class impl, BeanTypeInfo inf)
throws DatabindingException {
try {
PropertyDescriptor desc = inf.getPropertyDescriptorFromMappedName(name);
Method m = desc.getWriteMethod();
if (m == null) {
if (getTypeClass().isInterface()) {
m = getWriteMethodFromImplClass(impl, desc);
}
if (m == null) {
throw new DatabindingException("No write method for property " + name + " in "
+ object.getClass());
}
}
Class propertyType = desc.getPropertyType();
if ((property == null && !propertyType.isPrimitive()) || (property != null)) {
m.invoke(object, new Object[] {property});
}
} catch (Exception e) {
if (e instanceof DatabindingException) {
throw (DatabindingException)e;
}
throw new DatabindingException("Couldn't set property " + name + " on " + object + ". "
+ e.getMessage(), e);
}
}
/**
* This is a hack to get the write method from the implementation class for
* an interface.
*/
private Method getWriteMethodFromImplClass(Class<?> impl, PropertyDescriptor pd) throws Exception {
String name = pd.getName();
name = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
return impl.getMethod(name, new Class[] {pd.getPropertyType()});
}
/**
* To avoid double-writing xsi:type attributes, ObjectType uses this special entrypoint.
* @param object
* @param writer
* @param context
* @param wroteXsiType
*/
void writeObjectFromObjectType(Object object, MessageWriter writer,
Context context, boolean wroteXsiType) {
writeObjectInternal(object, writer, context, wroteXsiType);
}
/**
* {@inheritDoc}
*/
@Override
public void writeObject(Object object, MessageWriter writer, Context context)
throws DatabindingException {
writeObjectInternal(object, writer, context, false);
}
private void writeObjectInternal(Object object, MessageWriter writer, Context context,
boolean wroteXsiType)
throws DatabindingException {
if (object == null) {
return;
}
BeanTypeInfo inf = getTypeInfo();
if (!wroteXsiType
&& object.getClass() == getTypeClass()
&& context.isWriteXsiTypes()) {
writer.writeXsiType(getSchemaType());
}
/*
* TODO: Replace this method with one split into two pieces so that we
* can front-load the attributes and traverse down the list of super
* classes.
*/
for (Iterator itr = inf.getAttributes(); itr.hasNext();) {
QName name = (QName)itr.next();
Object value = readProperty(object, name);
if (value != null) {
Type type = getType(inf, name);
if (type == null) {
throw new DatabindingException("Couldn't find type for " + value.getClass()
+ " for property " + name);
}
MessageWriter cwriter = writer.getAttributeWriter(name);
type.writeObject(value, cwriter, context);
cwriter.close();
}
}
for (Iterator itr = inf.getElements(); itr.hasNext();) {
QName name = (QName)itr.next();
if (inf.isExtension()
&& inf.getPropertyDescriptorFromMappedName(name).getReadMethod().getDeclaringClass() != inf
.getTypeClass()) {
continue;
}
Object value = readProperty(object, name);
Type defaultType = getType(inf, name);
Type type = TypeUtil.getWriteType(context, value, defaultType);
MessageWriter cwriter;
// Write the value if it is not null.
if (value != null) {
cwriter = getWriter(writer, name, type);
if (type == null) {
throw new DatabindingException("Couldn't find type for " + value.getClass()
+ " for property " + name);
}
type.writeObject(value, cwriter, context);
cwriter.close();
} else if (inf.isNillable(name)) {
cwriter = getWriter(writer, name, type);
// Write the xsi:nil if it is null.
cwriter.writeXsiNil();
cwriter.close();
}
}
if (inf.isExtension()) {
Type t = getSuperType();
if (t != null) {
t.writeObject(object, writer, context);
}
}
}
private MessageWriter getWriter(MessageWriter writer, QName name, Type type) {
MessageWriter cwriter;
if (type.isAbstract()) {
cwriter = writer.getElementWriter(name);
} else {
cwriter = writer.getElementWriter(name);
}
return cwriter;
}
protected Object readProperty(Object object, QName name) {
try {
PropertyDescriptor desc = getTypeInfo().getPropertyDescriptorFromMappedName(name);
Method m = desc.getReadMethod();
if (m == null) {
throw new DatabindingException("No read method for property " + name + " in class "
+ object.getClass().getName());
}
return m.invoke(object, new Object[0]);
} catch (Exception e) {
throw new DatabindingException("Couldn't get property " + name + " from bean " + object, e);
}
}
/**
* @see org.apache.cxf.aegis.type.Type#writeSchema(org.jdom.Element)
*/
@Override
public void writeSchema(Element root) {
BeanTypeInfo inf = getTypeInfo();
Element complex = new Element("complexType", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
complex.setAttribute(new Attribute("name", getSchemaType().getLocalPart()));
root.addContent(complex);
Type sooperType = getSuperType();
/*
* See Java Virtual Machine specification:
* http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#75734
*/
if (((inf.getTypeClass().getModifiers() & Modifier.ABSTRACT) != 0)
&& !inf.getTypeClass().isInterface()) {
complex.setAttribute(new Attribute("abstract", "true"));
}
if (inf.isExtension() && sooperType != null) {
Element complexContent = new Element("complexContent", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
complex.addContent(complexContent);
complex = complexContent;
}
/*
* Decide if we're going to extend another type. If we are going to
* defer, then make sure that we extend the type for our superclass.
*/
boolean isExtension = inf.isExtension();
Element dummy = complex;
if (isExtension && sooperType != null) {
Element extension = new Element("extension", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
complex.addContent(extension);
QName baseType = sooperType.getSchemaType();
extension.setAttribute(new Attribute("base", getNameWithPrefix2(root, baseType
.getNamespaceURI(), baseType.getLocalPart())));
dummy = extension;
}
Element seq = null;
// Write out schema for elements
for (Iterator itr = inf.getElements(); itr.hasNext();) {
QName name = (QName)itr.next();
if (isExtension) {
PropertyDescriptor pd = inf.getPropertyDescriptorFromMappedName(name);
assert pd.getReadMethod() != null && pd.getWriteMethod() != null;
if (pd.getReadMethod().getDeclaringClass() != inf.getTypeClass()) {
continue;
}
}
if (seq == null) {
seq = new Element("sequence", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
dummy.addContent(seq);
}
Element element = new Element("element", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
seq.addContent(element);
Type type = getType(inf, name);
String nameNS = name.getNamespaceURI();
String nameWithPrefix = getNameWithPrefix(root, nameNS, name.getLocalPart());
String prefix = NamespaceHelper.getUniquePrefix(root, type.getSchemaType().getNamespaceURI());
writeTypeReference(name, nameWithPrefix, element, type, prefix, root);
}
/**
* if future proof then add <xsd:any/> element
*/
if (inf.isExtensibleElements()) {
if (seq == null) {
seq = new Element("sequence", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
dummy.addContent(seq);
}
seq.addContent(createAnyElement());
}
// Write out schema for attributes
for (Iterator itr = inf.getAttributes(); itr.hasNext();) {
QName name = (QName)itr.next();
Element element = new Element("attribute", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
dummy.addContent(element);
Type type = getType(inf, name);
String nameNS = name.getNamespaceURI();
String nameWithPrefix = getNameWithPrefix(root, nameNS, name.getLocalPart());
String prefix = NamespaceHelper.getUniquePrefix(root, type.getSchemaType().getNamespaceURI());
element.setAttribute(new Attribute("name", nameWithPrefix));
element.setAttribute(TypeUtil.createTypeAttribute(prefix, type, root));
}
/**
* If extensible attributes then add <xsd:anyAttribute/>
*/
if (inf.isExtensibleAttributes()) {
dummy.addContent(createAnyAttribute());
}
}
static Attribute createTypeAttribute(String prefix, Type type, Element root) {
String ns = type.getSchemaType().getNamespaceURI();
if (!ns.equals(root.getAttributeValue("targetNamespace"))
&& !ns.equals(XmlConstants.XSD)) {
//find import statement
List<Element> l = CastUtils.cast(root.getChildren("import",
Namespace.getNamespace(XmlConstants.XSD)));
boolean found = false;
for (Element e : l) {
if (ns.equals(e.getAttributeValue("namespace"))) {
found = true;
}
}
if (!found) {
Element element = new Element("import", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
root.addContent(0, element);
element.setAttribute("namespace", ns);
}
}
return new Attribute("type", prefix + ':' + type.getSchemaType().getLocalPart());
}
private String getNameWithPrefix(Element root, String nameNS, String localName) {
if (!nameNS.equals(getSchemaType().getNamespaceURI())) {
Element rootElement = (Element)root.getParent();
String prefix = null;
if (rootElement != null) { // can happen with doc/lit/bare
prefix = NamespaceHelper.getUniquePrefix(rootElement, nameNS);
}
if (prefix == null || prefix.length() == 0) {
prefix = NamespaceHelper.getUniquePrefix(root, nameNS);
}
return prefix + ":" + localName;
}
return localName;
}
private String getNameWithPrefix2(Element root, String nameNS, String localName) {
String prefix = NamespaceHelper.getUniquePrefix(root, nameNS);
if (prefix == null || prefix.length() == 0) {
prefix = NamespaceHelper.getUniquePrefix(root, nameNS);
}
return prefix + ":" + localName;
}
private Type getType(BeanTypeInfo inf, QName name) {
Type type = inf.getType(name);
if (type == null) {
throw new NullPointerException("Couldn't find type for" + name + " in class "
+ getTypeClass().getName());
}
return type;
}
private void writeTypeReference(QName name, String nameWithPrefix,
Element element, Type type, String prefix,
Element root) {
if (type.isAbstract()) {
element.setAttribute(new Attribute("name", nameWithPrefix));
element.setAttribute(TypeUtil.createTypeAttribute(prefix, type, root));
int minOccurs = getTypeInfo().getMinOccurs(name);
if (minOccurs == 0) {
element.setAttribute(new Attribute("minOccurs", Integer.valueOf(minOccurs).toString()));
}
if (getTypeInfo().isNillable(name)) {
element.setAttribute(new Attribute("nillable", "true"));
}
} else {
element.setAttribute(new Attribute("ref", prefix + ':' + type.getSchemaType().getLocalPart()));
}
}
@Override
public void setTypeClass(Class typeClass) {
super.setTypeClass(typeClass);
initTypeClass();
}
/**
* We need to write a complex type schema for Beans, so return true.
*
* @see org.apache.cxf.aegis.type.Type#isComplex()
*/
@Override
public boolean isComplex() {
return true;
}
@Override
public Set<Type> getDependencies() {
Set<Type> deps = new HashSet<Type>();
BeanTypeInfo inf = getTypeInfo();
for (Iterator itr = inf.getAttributes(); itr.hasNext();) {
QName name = (QName)itr.next();
deps.add(inf.getType(name));
}
for (Iterator itr = inf.getElements(); itr.hasNext();) {
QName name = (QName)itr.next();
if (inf.isExtension()
&& inf.getPropertyDescriptorFromMappedName(name).getReadMethod().getDeclaringClass() != inf
.getTypeClass()) {
continue;
}
deps.add(inf.getType(name));
}
/*
* Automagically add chain of superclasses *if* this is an an extension.
*/
if (inf.isExtension()) {
Type sooperType = getSuperType();
if (sooperType != null) {
deps.add(sooperType);
}
}
return deps;
}
private BeanType getBeanTypeWithProperty(QName name) {
BeanType sooper = this;
Type type = null;
while (type == null && sooper != null) {
type = sooper.getTypeInfo().getType(name);
if (type == null) {
sooper = sooper.getSuperType();
}
}
return sooper;
}
private BeanType getSuperType() {
BeanTypeInfo inf = getTypeInfo();
Class c = inf.getTypeClass().getSuperclass();
/*
* Don't dig any deeper than Object or Exception
*/
if (c != null && c != Object.class && c != Exception.class && c != RuntimeException.class) {
TypeMapping tm = inf.getTypeMapping();
BeanType superType = (BeanType)tm.getType(c);
if (superType == null) {
superType = (BeanType)getTypeMapping().getTypeCreator().createType(c);
Class cParent = c.getSuperclass();
if (cParent != null && cParent != Object.class) {
superType.getTypeInfo().setExtension(true);
}
tm.register(superType);
}
return superType;
} else {
return null;
}
}
public BeanTypeInfo getTypeInfo() {
if (info == null) {
info = createTypeInfo();
}
info.initialize();
return info;
}
public BeanTypeInfo createTypeInfo() {
BeanTypeInfo inf = new BeanTypeInfo(getTypeClass(), getSchemaType().getNamespaceURI());
inf.setTypeMapping(getTypeMapping());
return inf;
}
/**
* Create an element to represent any future elements that might get added
* to the schema <xsd:any minOccurs="0" maxOccurs="unbounded"/>
*
* @return
*/
private Element createAnyElement() {
Element result = new Element("any", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
result.setAttribute(new Attribute("minOccurs", "0"));
result.setAttribute(new Attribute("maxOccurs", "unbounded"));
return result;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName());
sb.append(": [class=");
Class c = getTypeClass();
sb.append((c == null) ? "<null>" : c.getName());
sb.append(",\nQName=");
QName q = getSchemaType();
sb.append((q == null) ? "<null>" : q.toString());
sb.append(",\ninfo=");
sb.append(getTypeInfo().toString());
sb.append("]");
return sb.toString();
}
/**
* Create an element to represent any future attributes that might get added
* to the schema <xsd:anyAttribute/>
*
* @return
*/
private Element createAnyAttribute() {
return new Element("anyAttribute", XmlConstants.XSD_PREFIX, XmlConstants.XSD);
}
}