summaryrefslogtreecommitdiffstats
path: root/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java')
-rw-r--r--sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java600
1 files changed, 600 insertions, 0 deletions
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<Class<?>> 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<Object, JAXBContext> map = cache.getCache();
+ Integer key = new Integer(System.identityHashCode(intf));
+ JAXBContext context = map.get(key);
+ if (context != null) {
+ return context;
+ }
+ List<DataType> dataTypes = getDataTypes(intf, useWrapper);
+ context = createJAXBContext(dataTypes);
+ map.put(key, context);
+ return context;
+ }
+ }
+
+ public JAXBContext createJAXBContext(List<DataType> dataTypes) throws JAXBException {
+ JAXBContext context;
+ Set<Class<?>> classes = new HashSet<Class<?>>();
+ Set<Type> visited = new HashSet<Type>();
+ for (DataType d : dataTypes) {
+ findClasses(d, classes, visited);
+ }
+
+ context = createJAXBContext(classes);
+ return context;
+ }
+
+ private static Set<Class<?>> findClasses(DataType d) {
+ Set<Class<?>> classes = new HashSet<Class<?>>();
+ Set<Type> visited = new HashSet<Type>();
+ findClasses(d, classes, visited);
+ return classes;
+ }
+
+ private static void findClasses(DataType d, Set<Class<?>> classes, Set<Type> 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<Class<?>> classSet, Set<Type> 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<Class<?>> findJAXBClassesByInterface(Class<?> cls) {
+ if (!cls.isInterface()) {
+ return Collections.emptySet();
+ }
+ Set<Class<?>> jaxbClasses = new HashSet<Class<?>>();
+ 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<? extends XmlAdapter> 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<DataType> getDataTypes(Interface intf, boolean useWrapper) {
+ List<DataType> dataTypes = new ArrayList<DataType>();
+ for (Operation op : intf.getOperations()) {
+ getDataTypes(dataTypes, op, useWrapper);
+ }
+ return dataTypes;
+ }
+
+ private static List<DataType> getDataTypes(Operation op, boolean useWrapper) {
+ List<DataType> dataTypes = new ArrayList<DataType>();
+ 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<XMLType>(JAXBDataBinding.NAME, cls, XMLType.UNKNOWN));
+ }
+ }
+ seeAlso = getSeeAlso(javaInterface.getCallbackClass());
+ if (seeAlso != null) {
+ for (Class<?> cls : seeAlso) {
+ dataTypes.add(new DataTypeImpl<XMLType>(JAXBDataBinding.NAME, cls, XMLType.UNKNOWN));
+ }
+ }
+ }
+ return dataTypes;
+ }
+
+ private static void getDataTypes(List<DataType> 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<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;
+ }
+}