/* * 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.tuscany.sca.databinding.jaxb; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBIntrospector; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlEnum; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSchema; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; import javax.xml.namespace.QName; import org.apache.tuscany.sca.common.java.collection.LRUCache; import org.apache.tuscany.sca.core.ExtensionPointRegistry; import org.apache.tuscany.sca.core.UtilityExtensionPoint; import org.apache.tuscany.sca.databinding.SimpleTypeMapper; import org.apache.tuscany.sca.databinding.TransformationContext; import org.apache.tuscany.sca.databinding.TransformationException; import org.apache.tuscany.sca.databinding.impl.SimpleTypeMapperImpl; import org.apache.tuscany.sca.interfacedef.DataType; import org.apache.tuscany.sca.interfacedef.Interface; import org.apache.tuscany.sca.interfacedef.Operation; import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; import org.apache.tuscany.sca.interfacedef.java.JavaInterface; import org.apache.tuscany.sca.interfacedef.util.WrapperInfo; import org.apache.tuscany.sca.interfacedef.util.XMLType; /** * * @version $Rev$ $Date$ */ public final class JAXBContextHelper { private final JAXBContextCache cache; private final static SimpleTypeMapper SIMPLE_TYPE_MAPPER = new SimpleTypeMapperImpl(); public JAXBContextHelper(ExtensionPointRegistry registry) { cache = new JAXBContextCache(registry); } public static JAXBContextHelper getInstance(ExtensionPointRegistry registry) { UtilityExtensionPoint utilityExtensionPoint = registry.getExtensionPoint(UtilityExtensionPoint.class); return utilityExtensionPoint.getUtility(JAXBContextHelper.class); } /** * Create a JAXBContext for a given class * @param cls * @return * @throws JAXBException */ public JAXBContext createJAXBContext(Class cls) throws JAXBException { return cache.getJAXBContext(cls); } public JAXBContext createJAXBContext(TransformationContext tContext, boolean source) throws JAXBException { if (tContext == null) throw new TransformationException("JAXB context is not set for the transformation."); // TODO: [rfeng] Need to figure out what's the best granularity to create the JAXBContext // per interface, operation or parameter Operation op = source ? tContext.getSourceOperation() : tContext.getTargetOperation(); if (op != null) { synchronized (op) { JAXBContext context = op.getInputType().getMetaData(JAXBContext.class); if (context == null) { context = createJAXBContext(getDataTypes(op, true)); op.getInputType().setMetaData(JAXBContext.class, context); } return context; } } // For property transformation, the operation can be null DataType dataType = source ? tContext.getSourceDataType() : tContext.getTargetDataType(); return createJAXBContext(dataType); } private static Class[] getSeeAlso(Class interfaze) { if (interfaze == null) { return null; } XmlSeeAlso seeAlso = interfaze.getAnnotation(XmlSeeAlso.class); if (seeAlso == null) { return null; } else { return seeAlso.value(); } } public JAXBContext createJAXBContext(DataType dataType) throws JAXBException { return createJAXBContext(findClasses(dataType)); } public Unmarshaller getUnmarshaller(JAXBContext context) throws JAXBException { return cache.getUnmarshaller(context); } public void releaseJAXBUnmarshaller(JAXBContext context, Unmarshaller unmarshaller) { cache.releaseJAXBUnmarshaller(context, unmarshaller); } public Marshaller getMarshaller(JAXBContext context) throws JAXBException { return cache.getMarshaller(context); } public void releaseJAXBMarshaller(JAXBContext context, Marshaller marshaller) { cache.releaseJAXBMarshaller(context, marshaller); } @SuppressWarnings("unchecked") public static Object createJAXBElement(JAXBContext context, DataType dataType, Object value) { Class type = dataType == null ? value.getClass() : dataType.getPhysical(); type = getValueType(type); QName name = JAXBDataBinding.ROOT_ELEMENT; if (context != null) { Object logical = dataType == null ? null : dataType.getLogical(); if (logical instanceof XMLType) { XMLType xmlType = (XMLType)logical; if (xmlType.isElement()) { name = xmlType.getElementName(); } else { /** * Set the declared type to Object.class so that xsi:type * will be produced */ type = Object.class; } } else { type = Object.class; } } JAXBIntrospector introspector = context.createJAXBIntrospector(); Object element = null; if (value != null && introspector.isElement(value)) { // NOTE: [rfeng] We cannot wrap an element in a JAXBElement element = value; } if (element == null) { // For local elements, we still have to produce xsi:type element = new JAXBElement(name, Object.class, value); } return element; } @SuppressWarnings("unchecked") public static Object createReturnValue(JAXBContext context, DataType dataType, Object value) { Class cls = getJavaType(dataType); if (cls == JAXBElement.class) { return createJAXBElement(context, dataType, value); } else { if (value instanceof JAXBElement) { Object returnValue = ((JAXBElement)value).getValue(); if (returnValue == null) { // TUSCANY-3530 // something went wrong in the transformation that // generated the JAXBElement. Have seen this when trying // to convert a value to a simple type with an incompatible // value. throw new TransformationException("Null returned when trying to convert value to: " + cls.getName()); } return returnValue; } else { return value; } } } /** * Create a JAXContext for an array of classes * @param classes * @return * @throws JAXBException */ public JAXBContext createJAXBContext(Class[] classes) throws JAXBException { return cache.getJAXBContext(classes); } public JAXBContext createJAXBContext(Set> classes) throws JAXBException { return cache.getJAXBContext(classes); } /** * Create a JAXBContext for a given java interface * @param intf * @return * @throws JAXBException */ public JAXBContext createJAXBContext(Interface intf, boolean useWrapper) throws JAXBException { synchronized (cache) { LRUCache map = cache.getCache(); Integer key = new Integer(System.identityHashCode(intf)); JAXBContext context = map.get(key); if (context != null) { return context; } List dataTypes = getDataTypes(intf, useWrapper); context = createJAXBContext(dataTypes); map.put(key, context); return context; } } public JAXBContext createJAXBContext(List dataTypes) throws JAXBException { JAXBContext context; Set> classes = new HashSet>(); Set visited = new HashSet(); for (DataType d : dataTypes) { findClasses(d, classes, visited); } context = createJAXBContext(classes); return context; } private static Set> findClasses(DataType d) { Set> classes = new HashSet>(); Set visited = new HashSet(); findClasses(d, classes, visited); return classes; } private static void findClasses(DataType d, Set> classes, Set visited) { if (d == null) { return; } String db = d.getDataBinding(); if (JAXBDataBinding.NAME.equals(db) || (db != null && db.startsWith("java:")) || db == null) { if (!d.getPhysical().isInterface() && !JAXBElement.class.isAssignableFrom(d.getPhysical())) { classes.add(d.getPhysical()); } else { classes.addAll(findJAXBClassesByInterface(d.getPhysical())); } } if (d.getPhysical() != d.getGenericType()) { findClasses(d.getGenericType(), classes, visited); } } /** * Find referenced classes in the generic type * @param type * @param classSet * @param visited */ private static void findClasses(Type type, Set> classSet, Set visited) { if (visited.contains(type) || type == null) { return; } visited.add(type); if (type instanceof Class) { Class cls = (Class)type; if (!cls.isInterface()) { classSet.add(cls); } else { classSet.addAll(findJAXBClassesByInterface(cls)); } return; } else if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)type; findClasses(pType.getRawType(), classSet, visited); for (Type t : pType.getActualTypeArguments()) { findClasses(t, classSet, visited); } } else if (type instanceof TypeVariable) { TypeVariable tv = (TypeVariable)type; for (Type t : tv.getBounds()) { findClasses(t, classSet, visited); } } else if (type instanceof GenericArrayType) { GenericArrayType gType = (GenericArrayType)type; findClasses(gType.getGenericComponentType(), classSet, visited); } else if (type instanceof WildcardType) { WildcardType wType = (WildcardType)type; for (Type t : wType.getLowerBounds()) { findClasses(t, classSet, visited); } for (Type t : wType.getUpperBounds()) { findClasses(t, classSet, visited); } } } /** * Introspect the @XmlJavaTypeAdapter and @XmlSeeAlso for an interface * @param cls * @return */ private static Set> findJAXBClassesByInterface(Class cls) { if (!cls.isInterface()) { return Collections.emptySet(); } Set> jaxbClasses = new HashSet>(); Class valueType = getValueType(cls); if (valueType != null) { jaxbClasses.add(valueType); } Class[] others = getSeeAlso(cls); if (others != null) { jaxbClasses.addAll(Arrays.asList(others)); } Package pkg = cls.getPackage(); if (pkg != null) { XmlJavaTypeAdapters adapters = pkg.getAnnotation(XmlJavaTypeAdapters.class); if (adapters != null) { for (XmlJavaTypeAdapter a : adapters.value()) { jaxbClasses.add(getValueType(a)); } } } return jaxbClasses; } public static Class getValueType(Class cls) { if (cls == null) { return null; } if (cls.isInterface()) { XmlJavaTypeAdapter adapter = cls.getAnnotation(XmlJavaTypeAdapter.class); return getValueType(adapter); } else { return cls; } } private static Class erase(Type type) { if (type instanceof Class) { return (Class)type; } if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType)type; return (Class)pt.getRawType(); } if (type instanceof TypeVariable) { TypeVariable tv = (TypeVariable)type; Type[] bounds = tv.getBounds(); return (0 < bounds.length) ? erase(bounds[0]) : Object.class; } if (type instanceof WildcardType) { WildcardType wt = (WildcardType)type; Type[] bounds = wt.getUpperBounds(); return (0 < bounds.length) ? erase(bounds[0]) : Object.class; } if (type instanceof GenericArrayType) { GenericArrayType gat = (GenericArrayType)type; return Array.newInstance(erase(gat.getGenericComponentType()), 0).getClass(); } throw new IllegalArgumentException("Unknown Type kind: " + type.getClass()); } public static Class getValueType(XmlJavaTypeAdapter adapter) { if (adapter != null) { Class adapterClass = adapter.value(); if (adapterClass != null) { Type superClass = adapterClass.getGenericSuperclass(); while (superClass instanceof ParameterizedType && XmlAdapter.class != ((ParameterizedType)superClass) .getRawType()) { superClass = erase(superClass).getGenericSuperclass(); } return erase(((ParameterizedType)superClass).getActualTypeArguments()[0]); } } return null; } public JAXBContext createJAXBContext(Interface intf) throws JAXBException { return createJAXBContext(intf, true); } /** * @param intf * @param useWrapper Use wrapper classes? * @return */ private static List getDataTypes(Interface intf, boolean useWrapper) { List dataTypes = new ArrayList(); for (Operation op : intf.getOperations()) { getDataTypes(dataTypes, op, useWrapper); } return dataTypes; } private static List getDataTypes(Operation op, boolean useWrapper) { List dataTypes = new ArrayList(); getDataTypes(dataTypes, op, useWrapper); // Adding classes referenced by @XmlSeeAlso in the java interface Interface interface1 = op.getInterface(); if (interface1 instanceof JavaInterface) { JavaInterface javaInterface = (JavaInterface)interface1; Class[] seeAlso = getSeeAlso(javaInterface.getJavaClass()); if (seeAlso != null) { for (Class cls : seeAlso) { dataTypes.add(new DataTypeImpl(JAXBDataBinding.NAME, cls, XMLType.UNKNOWN)); } } seeAlso = getSeeAlso(javaInterface.getCallbackClass()); if (seeAlso != null) { for (Class cls : seeAlso) { dataTypes.add(new DataTypeImpl(JAXBDataBinding.NAME, cls, XMLType.UNKNOWN)); } } } return dataTypes; } private static void getDataTypes(List dataTypes, Operation op, boolean useWrapper) { WrapperInfo inputWrapper = op.getInputWrapper(); WrapperInfo outputWrapper = op.getOutputWrapper(); if (useWrapper && (inputWrapper != null)) { DataType dt1 = inputWrapper.getWrapperType(); if (dt1 != null) { dataTypes.add(dt1); } } if (useWrapper && (outputWrapper != null)) { DataType dt2 = outputWrapper.getWrapperType(); if (dt2 != null) { dataTypes.add(dt2); } } // FIXME: [rfeng] We may need to find the referenced classes in the child types // else { for (DataType dt1 : op.getInputType().getLogical()) { dataTypes.add(dt1); } for (DataType dt2 : op.getOutputType().getLogical()) { dataTypes.add(dt2); } } for (DataType dt3 : op.getFaultTypes()) { DataType dt4 = dt3.getLogical(); if (dt4 != null) { dataTypes.add(dt4); } } } public static Class getJavaType(DataType dataType) { if (dataType == null) { return null; } Class type = dataType.getPhysical(); if (JAXBElement.class.isAssignableFrom(type)) { Type generic = dataType.getGenericType(); type = Object.class; } if (type == Object.class && dataType.getLogical() instanceof XMLType) { XMLType xType = (XMLType)dataType.getLogical(); Class javaType = SIMPLE_TYPE_MAPPER.getJavaType(xType.getTypeName()); if (javaType != null) { type = javaType; } } return type; } public static XMLType getXmlTypeName(Class javaType) { if (javaType.isInterface()) { // JAXB doesn't support interfaces return null; } String namespace = null; String name = null; Package pkg = javaType.getPackage(); if (pkg != null) { XmlSchema schema = pkg.getAnnotation(XmlSchema.class); if (schema != null) { namespace = schema.namespace(); } } QName elementQName = null; QName typeQName = null; XmlRootElement rootElement = javaType.getAnnotation(XmlRootElement.class); if (rootElement != null) { String elementName = rootElement.name(); String elementNamespace = rootElement.namespace(); if (elementNamespace.equals("##default")) { elementNamespace = namespace; } if (elementName.equals("##default")) { elementName = jaxbDecapitalize(javaType.getSimpleName()); } elementQName = new QName(elementNamespace, elementName); } XmlType type = javaType.getAnnotation(XmlType.class); if (type != null) { String typeNamespace = type.namespace(); String typeName = type.name(); if (typeNamespace.equals("##default")) { // namespace is from the package typeNamespace = namespace; } if (typeName.equals("##default")) { typeName = jaxbDecapitalize(javaType.getSimpleName()); } typeQName = new QName(typeNamespace, typeName); } else { XmlEnum xmlEnum = javaType.getAnnotation(XmlEnum.class); // POJO can have the @XmlSchema on the package-info too if (xmlEnum != null || namespace != null) { name = jaxbDecapitalize(javaType.getSimpleName()); typeQName = new QName(namespace, name); } } if (elementQName == null && typeQName == null) { return null; } return new XMLType(elementQName, typeQName); } /** * The JAXB RI doesn't implement the decapitalization algorithm in the * JAXB spec. See Sun bug 6505643 for details. This means that instead * of calling java.beans.Introspector.decapitalize() as the JAXB spec says, * Tuscany needs to mimic the incorrect JAXB RI algorithm. */ public static String jaxbDecapitalize(String name) { // find first lower case char in name int lower = name.length(); for (int i = 0; i < name.length(); i++) { if (Character.isLowerCase(name.charAt(i))) { lower = i; break; } } int decap; if (name.length() == 0) { decap = 0; // empty string: nothing to do } else if (lower == 0) { decap = 0; // first char is lower case: nothing to do } else if (lower == 1) { decap = 1; // one upper followed by lower: decapitalize 1 char } else if (lower < name.length()) { decap = lower - 1; // n uppers followed by at least one lower: decapitalize n-1 chars } else { decap = name.length(); // all upper case: decapitalize all chars } return name.substring(0, decap).toLowerCase() + name.substring(decap); } public void removeJAXBContextForContribution(ClassLoader contributionClassloader){ cache.removeJAXBContextForContribution(contributionClassloader); } /** * Just for testing that the cache is being removed on stop */ public JAXBContextCache getJAXBContextCache(){ return cache; } }