| /* |
| * 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.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.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| 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.namespace.QName; |
| |
| 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.databinding.util.LRUCache; |
| 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$ |
| */ |
| // FIXME: [rfeng] We probably should turn this into a pluggable system service |
| public class JAXBContextHelper { |
| |
| private static final JAXBContextCache cache = new JAXBContextCache(); |
| |
| private JAXBContextHelper() { |
| } |
| |
| /** |
| * Create a JAXBContext for a given class |
| * @param cls |
| * @return |
| * @throws JAXBException |
| */ |
| public static JAXBContext createJAXBContext(Class<?> cls) throws JAXBException { |
| return cache.getJAXBContext(cls); |
| } |
| |
| public static 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(); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static JAXBContext createJAXBContext(DataType dataType) throws JAXBException { |
| return createJAXBContext(findClasses(dataType)); |
| } |
| |
| public static Unmarshaller getUnmarshaller(JAXBContext context) throws JAXBException { |
| return cache.getUnmarshaller(context); |
| } |
| |
| public static void releaseJAXBUnmarshaller(JAXBContext context, Unmarshaller unmarshaller) { |
| cache.releaseJAXBUnmarshaller(context, unmarshaller); |
| } |
| |
| public static Marshaller getMarshaller(JAXBContext context) throws JAXBException { |
| return cache.getMarshaller(context); |
| } |
| |
| public static 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(); |
| 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) { |
| 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) { |
| return ((JAXBElement)value).getValue(); |
| } else { |
| return value; |
| } |
| } |
| } |
| |
| /** |
| * Create a JAXContext for an array of classes |
| * @param classes |
| * @return |
| * @throws JAXBException |
| */ |
| public static JAXBContext createJAXBContext(Class<?>[] classes) throws JAXBException { |
| return cache.getJAXBContext(classes); |
| } |
| |
| public static JAXBContext createJAXBContext(Set<Class<?>> classes) throws JAXBException { |
| return cache.getJAXBContext(classes); |
| } |
| |
| /** |
| * Create a JAXBContext for a given java interface |
| * @param intf |
| * @return |
| * @throws JAXBException |
| */ |
| @SuppressWarnings("unchecked") |
| public static 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; |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static 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; |
| } |
| |
| @SuppressWarnings("unchecked") |
| 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; |
| } |
| |
| @SuppressWarnings("unchecked") |
| 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()); |
| } |
| } |
| if (d.getPhysical() != d.getGenericType()) { |
| findClasses(d.getGenericType(), classes, visited); |
| } |
| } |
| |
| /** |
| * Find referenced classes in the generic type |
| * @param type |
| * @param classSet |
| * @param visited |
| */ |
| @SuppressWarnings("unchecked") |
| 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); |
| } |
| 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); |
| } |
| } |
| } |
| |
| public static JAXBContext createJAXBContext(Interface intf) throws JAXBException { |
| return createJAXBContext(intf, true); |
| } |
| |
| /** |
| * @param intf |
| * @param useWrapper Use wrapper classes? |
| * @return |
| */ |
| @SuppressWarnings("unchecked") |
| 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; |
| } |
| |
| @SuppressWarnings("unchecked") |
| 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; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static void getDataTypes(List<DataType> dataTypes, Operation op, boolean useWrapper) { |
| WrapperInfo inputWrapperInfo = op.getInputWrapper(); |
| WrapperInfo outputWrapperInfo = op.getOutputWrapper(); |
| |
| // TUSCANY-3298: Add the wrapper type instead of individual elements |
| // if possible. JAXB will implicitly add all types that are statically |
| // reachable from the wrapper class, with the exception of type arguments |
| // for parameterized types that aren't collections. |
| DataType dt1 = null; |
| if (useWrapper && (inputWrapperInfo != null)) { |
| dt1 = inputWrapperInfo.getWrapperType(); |
| if (dt1 != null) { |
| dataTypes.add(dt1); |
| for (DataType in : op.getInputType().getLogical()) { |
| if (isParameterizedNonCollectionType(in)) { |
| // JAXB won't add the type arguments, so we need to add them |
| dataTypes.add(in); |
| } |
| } |
| } |
| } |
| if (dt1 == null) { |
| // We couldn't add the wrapper, so add the elements individually |
| for (DataType dt : op.getInputType().getLogical()) { |
| dataTypes.add(dt); |
| } |
| } |
| |
| // TUSCANY-3298: Add the wrapper type instead of the output type |
| // if possible. JAXB will implicitly add all types that are statically |
| // reachable from the wrapper class, with the exception of type arguments |
| // for parameterized types that aren't collections or maps. |
| DataType dt2 = null; |
| if (useWrapper && (outputWrapperInfo != null)) { |
| dt2 = outputWrapperInfo.getWrapperType(); |
| if (dt2 != null) { |
| dataTypes.add(dt2); |
| DataType out = op.getOutputType(); |
| if (out != null && isParameterizedNonCollectionType(out)) { |
| // JAXB won't add the type arguments, so we need to add them |
| dataTypes.add(out); |
| } |
| } |
| } |
| if (dt2 == null) { |
| // We couldn't add the wrapper, so add the output type directly |
| dt2 = op.getOutputType(); |
| if (dt2 != null) { |
| dataTypes.add(dt2); |
| } |
| } |
| |
| for (DataType<DataType> dt3 : op.getFaultTypes()) { |
| DataType dt4 = dt3.getLogical(); |
| if (dt4 != null) { |
| dataTypes.add(dt4); |
| } |
| } |
| } |
| |
| /* |
| * We need to add parameterized non-collection types to the JAXB context |
| * explicitly, because type argument information for these types is erased |
| * from the generated wrapper bean. |
| */ |
| private static boolean isParameterizedNonCollectionType(DataType dt) { |
| Type type = dt.getGenericType(); |
| if (type instanceof ParameterizedType) { |
| Class physical = dt.getPhysical(); |
| if (!Collection.class.isAssignableFrom(physical) && |
| !Map.class.isAssignableFrom(physical)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @SuppressWarnings("unchecked") |
| 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 = SimpleTypeMapperImpl.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); |
| } |
| |
| } |