From e5b7380c874745c989d1816b8f552504f038e1bc Mon Sep 17 00:00:00 2001 From: lresende Date: Thu, 26 Sep 2013 20:33:20 +0000 Subject: 2.0 branch for possible maintenance release git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1526672 13f79535-47bb-0310-9956-ffa450edef68 --- .../sca/databinding/jaxb/JAXBContextHelper.java | 600 +++++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java (limited to 'sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java') diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java new file mode 100644 index 0000000000..44c172a07a --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java @@ -0,0 +1,600 @@ +/* + * 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; + } +} -- cgit v1.2.3