blob: 9f990f1d802b556bd347b9aedbfac4917d6408a2 [file] [log] [blame]
/*
* Copyright 2004,2005 The Apache Software Foundation.
* Copyright 2006 International Business Machines Corp.
*
* 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.axis2.jaxws.util;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This utility contains code to determine if an Object is
* "element enabled" or "type enabled".
*
* Here is an example for illustration:
* <element name='e1'>
* <complexType>...</complexType>
* </element>
*
* <element name='e2' type='t2' />
* <complexType name= 't2'>..
*
* <element name='e3' type='e3' /> <!-- note element and type have same name -->
* <complexType name= 'e3'>..
*
* JAXB will generate the following objects: E1, T2, E3
* E1 will have an @XMLRootElement annotation. It is "element" and "type" enabled.
* e2 does not have a generated object. So it will be represented as a JAXBElement
* that contains an object T2. The JAXBElement is "element" enabled.
* T2 represents a complexType. It is only "type" enabled.
* E3 represents the e3 complexType (it does not represent the e3 element). Thus E3
* is "type enabled".
*
* When JAXB unmarshals an object, it will return an "element" enabled object (either
* a generatated object with @XMLRootElement or a JAXBElement).
* Conversely, you must always marshal "element" enabled objects.
* @see org.apache.axis2.jaxws.marshaller.impl.alt.PDElement
*
* At the signature level, the values passed as arguments in an SEI operation represent
* type enabled objects. Each of the object must be converted to an element enabled object
* to marshal (or conversely converted to a type enabled object when unmarshalling)
*
* -----------------------------------------
* There are other simular utility methods in this class. Including utilities to get
* a xml name -> bean property map.
*
* -----------------------------------------
*
* @TODO A secondary reason usage of XMLRootElementUtil is to isolate all of the
* @XMLRootElement and related annotation queries. Annotation queries are expensive.
* A follow-on version of this class will cache the results so that we can improve performance.
*
*/
public class XMLRootElementUtil {
private static final Log log = LogFactory.getLog(XMLRootElementUtil.class);
/**
* Constructor is intentionally private. This class only provides static utility methods
*/
private XMLRootElementUtil() {
}
/**
* Return true if this class is element enabled
* @param clazz
* @return true if this class has a corresponding xml root element
*/
public static boolean isElementEnabled(Class clazz){
if (clazz.equals(JAXBElement.class)) {
return true;
}
// If the clazz is a primitive, then it does not have a corresponding root element.
if (clazz.isPrimitive() || ClassUtils.getWrapperClass(clazz) != null) {
return false;
}
// Presence of an annotation means that it can be rendered as a root element
XmlRootElement root = (XmlRootElement) clazz.getAnnotation(XmlRootElement.class);
return root !=null;
}
/**
* Returns ture if this class is type enabled
* @param clazz
* @return
*/
public static boolean isTypeEnabled(Class clazz) {
// Primitives, Primitive wrappers, BigDecimal, etc. are all type enabled
// So are all classes with @XmlRootElement or @XmlType.
// For now I am only going to assume that the class is type enabled unless it is JAXBElement
return (!clazz.equals(JAXBElement.class));
}
/**
* Return type enabled object
* @param obj type or element enabled object
* @return type enabled object
*/
public static Object getTypeEnabledObject(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof JAXBElement) {
return ((JAXBElement) obj).getValue();
}
return obj;
}
/**
* Return an object that can be marshalled/unmarshalled as an element
* If the specified object is already element enabled, it is returned.
* @param namespace
* @param localPart
* @param cls either the class or super class of obj
* @param type element or type enabled object
* @return
*/
public static Object getElementEnabledObject(String namespace, String localPart, Class cls, Object obj) {
if (obj != null && isElementEnabled(obj.getClass())) {
return obj;
}
QName qName = new QName(namespace, localPart);
JAXBElement element = new JAXBElement(qName, cls, obj);
return element;
}
/**
* @param clazz
* @return namespace of root element qname or null if this is not object does not represent a root element
*/
public static QName getXmlRootElementQName(Object obj){
// A JAXBElement stores its name
if (obj instanceof JAXBElement) {
return ((JAXBElement) obj).getName();
}
Class clazz = (obj instanceof java.lang.Class) ? (Class) obj : obj.getClass();
// If the clazz is a primitive, then it does not have a corresponding root element.
if (clazz.isPrimitive() ||
ClassUtils.getWrapperClass(clazz) != null) {
return null;
}
// See if the object represents a root element
XmlRootElement root = (XmlRootElement) clazz.getAnnotation(XmlRootElement.class);
if (root == null) {
return null;
}
String namespace = root.namespace();
String localPart = root.name();
// The namespace may need to be defaulted
if (namespace == null || namespace.length() == 0 || namespace.equals("##default")) {
Package pkg = clazz.getPackage();
XmlSchema schema = (XmlSchema) pkg.getAnnotation(XmlSchema.class);
if (schema != null) {
namespace = schema.namespace();
} else {
return null;
}
}
return new QName(namespace, localPart);
}
/**
* The JAXBClass has a set of bean properties each represented by a PropertyDescriptor
* Each of the fields of the class has an associated xml name.
* The method returns a map where the key is the xml name and value is the PropertyDescriptor
* @param jaxbClass
* @return map
*/
public static Map<String, PropertyDescriptor> createPropertyDescriptorMap(Class jaxbClass) throws NoSuchFieldException, IntrospectionException {
if (log.isDebugEnabled()) {
log.debug("Get the PropertyDescriptor[] for " + jaxbClass);
}
// TODO This is a very performance intensive search we should cache the calculated map keyed by the jaxbClass
PropertyDescriptor[] pds = Introspector.getBeanInfo(jaxbClass).getPropertyDescriptors();
// Make this a weak map in case we want to cache the results
Map<String, PropertyDescriptor> map = new WeakHashMap<String, PropertyDescriptor>();
// Unfortunately the element names are stored on the fields.
// Get all of the fields in the class and super classes
List<Field> fields = new ArrayList<Field>();
Class cls = jaxbClass;
while(cls != null) {
Field[] fieldArray = cls.getDeclaredFields();
for (Field field:fieldArray) {
fields.add(field);
}
cls = cls.getSuperclass();
}
// Now match up the fields with the property descriptors...Sigh why didn't JAXB put the @XMLElement annotations on the
// property methods!
for(PropertyDescriptor pd:pds){
// Skip over the class property..it is never represented as an xml element
if (pd.getName().equals("class")) {
continue;
}
// For the current property, find a matching field...so that we can get the xml name
boolean found = false;
if (log.isDebugEnabled()) {
log.debug(" Start: Find xmlname for property:" + pd.getName());
}
for(Field field:fields){
String fieldName = field.getName();
// Use the name of the field and property to find the match
if (fieldName.equalsIgnoreCase(pd.getDisplayName()) ||
fieldName.equalsIgnoreCase(pd.getName()) ) {
// Get the xmlElement name for this field
String xmlName =getXmlElementName(field.getDeclaringClass(), field);
found = true;
if (log.isDebugEnabled()) {
log.debug(" Found field " + field.getName() + " which has xmlname=" + xmlName);
}
if (map.get(xmlName) != null) {
if (log.isDebugEnabled()) {
log.debug(" ALERT: property " + map.get(xmlName).getName() + " already has this same xmlName..this may cause problems.");
}
}
map.put(xmlName, pd);
break;
}
// Unfortunately, sometimes the field name is preceeded by an underscore
if (fieldName.startsWith("_")) {
fieldName = fieldName.substring(1);
if (fieldName.equalsIgnoreCase(pd.getDisplayName()) ||
fieldName.equalsIgnoreCase(pd.getName())) {
// Get the xmlElement name for this field
String xmlName =getXmlElementName(field.getDeclaringClass(), field);
found = true;
if (log.isDebugEnabled()) {
log.debug(" Found field " + field.getName() + " which has xmlname=" + xmlName);
}
if (map.get(xmlName) != null) {
if (log.isDebugEnabled()) {
log.debug(" ALERT: property " + map.get(xmlName).getName() + " already has this same xmlName..this may cause problems.");
}
}
map.put(xmlName, pd);
break;
}
}
}
// We didn't find a field. Default the xmlname to the property name
if (!found) {
String xmlName = pd.getName();
if (log.isDebugEnabled()) {
log.debug(" A matching field was not found. Defaulting xmlname to " + xmlName);
}
if (map.get(xmlName) != null) {
if (log.isDebugEnabled()) {
log.debug(" ALERT: property " + map.get(xmlName).getName() + " already has this same xmlName..this may cause problems.");
}
}
map.put(xmlName, pd);
}
if (log.isDebugEnabled()) {
log.debug(" End: Find xmlname for property:" + pd.getName());
}
}
return map;
}
/**
* Get the name of the field by looking at the XmlElement annotation.
* @param jaxbClass
* @param fieldName
* @return
* @throws NoSuchFieldException
*/
private static String getXmlElementName(Class jaxbClass, Field field)throws NoSuchFieldException{
XmlElement xmlElement =field.getAnnotation(XmlElement.class);
// If XmlElement does not exist, default to using the field name
if (xmlElement == null ||
xmlElement.name().equals("##default")) {
return field.getName();
}
return xmlElement.name();
}
}