diff options
Diffstat (limited to 'branches/sca-equinox/modules/databinding-jaxb/src/main/java/org')
19 files changed, 3107 insertions, 0 deletions
diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DataConverter.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DataConverter.java new file mode 100644 index 0000000000..003b4fb992 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DataConverter.java @@ -0,0 +1,378 @@ +/* + * 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.awt.Image; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.activation.DataHandler; +import javax.imageio.ImageIO; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.osoa.sca.ServiceRuntimeException; + +/** + * Provides utilities to convert an object into a different kind of Object. For example, convert a + * String[] into a List<String> + */ +public class DataConverter { + + /** + * This method should return true if the convert method will succeed. + * <p/> + * Note that any changes to isConvertable() must also be accompanied by similar changes to + * convert() + * + * @param obj source object or class + * @param dest destination class + * @return boolean true if convert(..) can convert obj to the destination class + */ + public static boolean isConvertable(Object obj, Class dest) { + Class src = null; + + if (obj != null) { + if (obj instanceof Class) { + src = (Class)obj; + } else { + src = obj.getClass(); + } + } + + if (dest == null) { + return false; + } + + if (src == null) { + return true; + } + + // If we're directly assignable, we're good. + if (dest.isAssignableFrom(src)) { + return true; + } + + // If it's a wrapping conversion, we're good. + if (getWrapperClass(src) == dest) { + return true; + } + if (getWrapperClass(dest) == src) { + return true; + } + + // If it's List -> Array or vice versa, we're good. + if ((Collection.class.isAssignableFrom(src) || src.isArray()) && (Collection.class.isAssignableFrom(dest) || dest + .isArray())) { + + // TODO this should consider the component types instead of returning true. + return true; + } + + // Allow mapping of HashMaps to Hashtables + if (src == HashMap.class && dest == Hashtable.class) + return true; + + // Allow mapping of Calendar to Date + if (Calendar.class.isAssignableFrom(src) && dest == Date.class) { + return true; + } + + if (src.isPrimitive()) { + return isConvertable(getWrapperClass(src), dest); + } + + if (InputStream.class.isAssignableFrom(src) && dest == byte[].class) { + return true; + } + + if (Source.class.isAssignableFrom(src) && dest == byte[].class) { + return true; + } + + if (DataHandler.class.isAssignableFrom(src) && isConvertable(byte[].class, dest)) { + return true; + } + + if (DataHandler.class.isAssignableFrom(src) && dest == Image.class) { + return true; + } + + if (DataHandler.class.isAssignableFrom(src) && dest == Source.class) { + return true; + } + + if (byte[].class.isAssignableFrom(src) && dest == String.class) { + return true; + } + + // If it's a MIME type mapping and we want a DataHandler, + // then we're good. + // REVIEW Do we want to support this + /* + if (dest.getName().equals("javax.activation.DataHandler")) { + String name = src.getName(); + if (src == String.class + || src == java.awt.Image.class + || name.equals("javax.mail.internet.MimeMultipart") + || name.equals("javax.xml.transform.Source")) + return true; + } + */ + + return false; + } + + /** + * Utility function to convert an Object to some desired Class. + * <p/> + * Normally this is used for T[] to List<T> processing. Other conversions are also done (i.e. + * HashMap <->Hashtable, etc.) + * <p/> + * Use the isConvertable() method to determine if conversion is possible. Note that any changes + * to convert() must also be accompanied by similar changes to isConvertable() + * + * @param arg the array to convert + * @param destClass the actual class we want + * @return object of destClass if conversion possible, otherwise returns arg + */ + public static Object convert(Object arg, Class<?> destClass) { + if (destClass == null) { + return arg; + } + + if (arg != null && destClass.isAssignableFrom(arg.getClass())) { + return arg; + } + + // Convert between Calendar and Date + if (arg instanceof Calendar && destClass == Date.class) { + return ((Calendar)arg).getTime(); + } + + // Convert between HashMap and Hashtable + if (arg instanceof HashMap && destClass == Hashtable.class) { + return new Hashtable((HashMap)arg); + } + + if (arg instanceof InputStream && destClass == byte[].class) { + + try { + InputStream is = (InputStream)arg; + return getBytesFromStream(is); + } catch (IOException e) { + throw new ServiceRuntimeException(e); + } + } + + if (arg instanceof Source && destClass == byte[].class) { + try { + if (arg instanceof StreamSource) { + InputStream is = ((StreamSource)arg).getInputStream(); + if (is != null) { + return getBytesFromStream(is); + } + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Result result = new StreamResult(out); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.transform((Source)arg, result); + byte[] bytes = out.toByteArray(); + return bytes; + + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } + + if (arg instanceof DataHandler) { + try { + InputStream is = ((DataHandler)arg).getInputStream(); + if (destClass == Image.class) { + return ImageIO.read(is); + } else if (destClass == Source.class) { + return new StreamSource(is); + } + byte[] bytes = getBytesFromStream(is); + return convert(bytes, destClass); + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } + + if (arg instanceof byte[] && destClass == String.class) { + return new String((byte[])arg); + } + + // If the destination is an array and the source + // is a suitable component, return an array with + // the single item. + /* REVIEW do we need to support atomic to array conversion ? + if (arg != null && + destClass.isArray() && + !destClass.getComponentType().equals(Object.class) && + destClass.getComponentType().isAssignableFrom(arg.getClass())) { + Object array = + Array.newInstance(destClass.getComponentType(), 1); + Array.set(array, 0, arg); + return array; + } + */ + + // Return if no conversion is available + if (!(arg instanceof Collection || (arg != null && arg.getClass().isArray()))) { + return arg; + } + + if (arg == null) { + return null; + } + + // The arg may be an array or List + Object destValue = null; + int length = 0; + if (arg.getClass().isArray()) { + length = Array.getLength(arg); + } else { + length = ((Collection)arg).size(); + } + + try { + if (destClass.isArray()) { + if (destClass.getComponentType().isPrimitive()) { + + Object array = Array.newInstance(destClass.getComponentType(), length); + // Assign array elements + if (arg.getClass().isArray()) { + for (int i = 0; i < length; i++) { + Array.set(array, i, Array.get(arg, i)); + } + } else { + int idx = 0; + for (Iterator i = ((Collection)arg).iterator(); i.hasNext();) { + Array.set(array, idx++, i.next()); + } + } + destValue = array; + + } else { + Object[] array; + try { + array = (Object[])Array.newInstance(destClass.getComponentType(), length); + } catch (Exception e) { + return arg; + } + + // Use convert to assign array elements. + if (arg.getClass().isArray()) { + for (int i = 0; i < length; i++) { + array[i] = convert(Array.get(arg, i), destClass.getComponentType()); + } + } else { + int idx = 0; + for (Iterator i = ((Collection)arg).iterator(); i.hasNext();) { + array[idx++] = convert(i.next(), destClass.getComponentType()); + } + } + destValue = array; + } + } else if (Collection.class.isAssignableFrom(destClass)) { + Collection newList = null; + try { + // if we are trying to create an interface, build something + // that implements the interface + if (destClass == Collection.class || destClass == List.class) { + newList = new ArrayList(); + } else if (destClass == Set.class) { + newList = new HashSet(); + } else { + newList = (Collection)destClass.newInstance(); + } + } catch (Exception e) { + // No FFDC code needed + // Couldn't build one for some reason... so forget it. + return arg; + } + + if (arg.getClass().isArray()) { + for (int j = 0; j < length; j++) { + newList.add(Array.get(arg, j)); + } + } else { + for (Iterator j = ((Collection)arg).iterator(); j.hasNext();) { + newList.add(j.next()); + } + } + destValue = newList; + } else { + destValue = arg; + } + } catch (Throwable t) { + throw new ServiceRuntimeException(t); + } + + return destValue; + } + + private static byte[] getBytesFromStream(InputStream is) throws IOException { + // TODO This code assumes that available is the length of the stream. + byte[] bytes = new byte[is.available()]; + is.read(bytes); + return bytes; + } + + public static Class getWrapperClass(Class primitive) { + if (primitive == int.class) { + return java.lang.Integer.class; + } else if (primitive == short.class) { + return java.lang.Short.class; + } else if (primitive == boolean.class) { + return java.lang.Boolean.class; + } else if (primitive == byte.class) { + return java.lang.Byte.class; + } else if (primitive == long.class) { + return java.lang.Long.class; + } else if (primitive == double.class) { + return java.lang.Double.class; + } else if (primitive == float.class) { + return java.lang.Float.class; + } else if (primitive == char.class) { + return java.lang.Character.class; + } + + return null; + } +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DefaultXMLAdapterExtensionPoint.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DefaultXMLAdapterExtensionPoint.java new file mode 100644 index 0000000000..062da48206 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DefaultXMLAdapterExtensionPoint.java @@ -0,0 +1,60 @@ +/* + * 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.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +/** + * @version $Rev$ $Date$ + */ +public class DefaultXMLAdapterExtensionPoint implements XMLAdapterExtensionPoint { + private Map<Class<?>, Class<? extends XmlAdapter>> adapters = + new ConcurrentHashMap<Class<?>, Class<? extends XmlAdapter>>(); + + public void addAdapter(Class<?> boundType, Class<? extends XmlAdapter> adapter) { + adapters.put(boundType, adapter); + } + + public Class<? extends XmlAdapter> getAdapter(Class<?> boundType) { + Class<? extends XmlAdapter> cls = adapters.get(boundType); + if (cls != null) { + return cls; + } + for (Map.Entry<Class<?>, Class<? extends XmlAdapter>> e : adapters.entrySet()) { + if (e.getKey().isAssignableFrom(boundType)) { + return e.getValue(); + } + } + return null; + } + + @SuppressWarnings("unchecked") + public Class<? extends XmlAdapter> removeAdapter(Class<?> boundType) { + return adapters.remove(boundType); + } + + public Map<Class<?>, Class<? extends XmlAdapter>> getAdapters() { + return adapters; + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2Node.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2Node.java new file mode 100644 index 0000000000..3e262c7d12 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2Node.java @@ -0,0 +1,75 @@ +/* + * 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 javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.BaseTransformer; +import org.apache.tuscany.sca.databinding.impl.DOMHelper; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * + * @version $Rev$ $Date$ + */ +public class JAXB2Node extends BaseTransformer<Object, Node> implements PullTransformer<Object, Node> { + + public Node transform(Object source, TransformationContext tContext) { +// if (source == null) { +// return null; +// } + try { + JAXBContext context = JAXBContextHelper.createJAXBContext(tContext, true); + Marshaller marshaller = context.createMarshaller(); + // FIXME: The default Marshaller doesn't support + // marshaller.getNode() + Document document = DOMHelper.newDocument(); + Object jaxbElement = JAXBContextHelper.createJAXBElement(context, tContext.getSourceDataType(), source); + marshaller.marshal(jaxbElement, document); + return DOMHelper.adjustElementName(tContext, document.getDocumentElement()); + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + protected Class<Object> getSourceType() { + return Object.class; + } + + @Override + protected Class<Node> getTargetType() { + return Node.class; + } + + @Override + public int getWeight() { + return 30; + } + + @Override + public String getSourceDataBinding() { + return JAXBDataBinding.NAME; + } +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2SAX.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2SAX.java new file mode 100644 index 0000000000..eee5c77285 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2SAX.java @@ -0,0 +1,70 @@ +/* + * 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 javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import org.apache.tuscany.sca.databinding.PushTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.BaseTransformer; +import org.xml.sax.ContentHandler; + +/** + * @version $Rev$ $Date$ + */ +public class JAXB2SAX extends BaseTransformer<Object, ContentHandler> implements + PushTransformer<Object, ContentHandler> { + + @Override + protected Class<Object> getSourceType() { + return Object.class; + } + + @Override + protected Class<ContentHandler> getTargetType() { + return ContentHandler.class; + } + + /** + * @see org.apache.tuscany.sca.databinding.PushTransformer#transform(java.lang.Object, java.lang.Object, org.apache.tuscany.sca.databinding.TransformationContext) + */ + public void transform(Object source, ContentHandler target, TransformationContext tContext) { + try { + JAXBContext context = JAXBContextHelper.createJAXBContext(tContext, true); + Marshaller marshaller = context.createMarshaller(); + Object jaxbElement = JAXBContextHelper.createJAXBElement(context, tContext.getSourceDataType(), source); + marshaller.marshal(jaxbElement, target); + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + public int getWeight() { + return 20; + } + + @Override + public String getSourceDataBinding() { + return JAXBDataBinding.NAME; + } +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2String.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2String.java new file mode 100644 index 0000000000..29951a5e16 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2String.java @@ -0,0 +1,77 @@ +/* + * 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.io.StringWriter; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.transform.stream.StreamResult; + +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.BaseTransformer; +import org.apache.tuscany.sca.databinding.xml.XMLStringDataBinding; + +/** + * + * @version $Rev$ $Date$ + */ +public class JAXB2String extends BaseTransformer<Object, String> implements PullTransformer<Object, String> { + + public String transform(Object source, TransformationContext tContext) { + try { + JAXBContext context = JAXBContextHelper.createJAXBContext(tContext, true); + Marshaller marshaller = context.createMarshaller(); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + Object jaxbElement = JAXBContextHelper.createJAXBElement(context, tContext.getSourceDataType(), source); + marshaller.marshal(jaxbElement, result); + return writer.toString(); + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + protected Class<Object> getSourceType() { + return Object.class; + } + + @Override + protected Class<String> getTargetType() { + return String.class; + } + + @Override + public int getWeight() { + return 30; + } + + @Override + public String getSourceDataBinding() { + return JAXBDataBinding.NAME; + } + + @Override + public String getTargetDataBinding() { + return XMLStringDataBinding.NAME; + } +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextCache.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextCache.java new file mode 100644 index 0000000000..296c39c50f --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextCache.java @@ -0,0 +1,293 @@ +/* + * 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.awt.Image; +import java.net.URI; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import javax.activation.DataHandler; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlSchema; +import javax.xml.transform.Source; + +import org.apache.tuscany.sca.databinding.util.LRUCache; + +/** + * @version $Rev$ $Date$ + */ +public class JAXBContextCache { + private static final int CACHE_SIZE = 128; + + private static HashMap<String, Class<?>> loadClassMap = new HashMap<String, Class<?>>(); + + static { + loadClassMap.put("byte", byte.class); + loadClassMap.put("int", int.class); + loadClassMap.put("short", short.class); + loadClassMap.put("long", long.class); + loadClassMap.put("float", float.class); + loadClassMap.put("double", double.class); + loadClassMap.put("boolean", boolean.class); + loadClassMap.put("char", char.class); + loadClassMap.put("void", void.class); + } + + protected static Class<?>[] JAXB_BUILTIN_CLASSES = + {byte[].class, boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class, + short.class, void.class, java.awt.Image.class, java.io.File.class, java.lang.Boolean.class, + java.lang.Byte.class, java.lang.Character.class, java.lang.Class.class, java.lang.Double.class, + java.lang.Float.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Object.class, + java.lang.Short.class, java.lang.String.class, java.lang.Void.class, java.math.BigDecimal.class, + java.math.BigInteger.class, java.net.URI.class, java.net.URL.class, java.util.Calendar.class, + java.util.Date.class, java.util.GregorianCalendar.class, java.util.UUID.class, + javax.activation.DataHandler.class, javax.xml.bind.JAXBElement.class, javax.xml.datatype.Duration.class, + javax.xml.datatype.XMLGregorianCalendar.class, javax.xml.namespace.QName.class, + javax.xml.transform.Source.class}; + + protected static final Set<Class<?>> BUILTIN_CLASSES_SET = new HashSet<Class<?>>(Arrays.asList(JAXB_BUILTIN_CLASSES)); + + /* + protected static Class<?>[] COMMON_ARRAY_CLASSES = + new Class[] {char[].class, short[].class, int[].class, long[].class, float[].class, double[].class, + String[].class + }; + + protected static final Set<Class<?>> COMMON_CLASSES_SET = new HashSet<Class<?>>(Arrays.asList(COMMON_ARRAY_CLASSES)); + */ + + protected LRUCache<Object, JAXBContext> cache; + protected LRUCache<JAXBContext, Unmarshaller> upool; + protected LRUCache<JAXBContext, Marshaller> mpool; + + // protected JAXBContext commonContext; + protected JAXBContext defaultContext; + + public JAXBContextCache() { + this(CACHE_SIZE, CACHE_SIZE, CACHE_SIZE); + } + + public JAXBContextCache(int contextSize, int marshallerSize, int unmarshallerSize) { + cache = new LRUCache<Object, JAXBContext>(contextSize); + upool = new LRUCache<JAXBContext, Unmarshaller>(unmarshallerSize); + mpool = new LRUCache<JAXBContext, Marshaller>(marshallerSize); + defaultContext = getDefaultJAXBContext(); + } + + public static JAXBContext getDefaultJAXBContext() { + try { + return JAXBContext.newInstance(); + } catch (JAXBException e) { + throw new IllegalArgumentException(e); + } + } + + /** + * @param name of primitive type + * @return primitive Class or null + */ + public static Class<?> getPrimitiveClass(String text) { + return loadClassMap.get(text); + } + + /** + * Return the class for this name + * + * @return Class + */ + private static Class<?> forName(final String className, final boolean initialize, final ClassLoader classloader) + throws ClassNotFoundException { + // NOTE: This method must remain private because it uses AccessController + Class<?> cl = null; + try { + cl = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() { + public Class<?> run() throws ClassNotFoundException { + // Class.forName does not support primitives + Class<?> cls = getPrimitiveClass(className); + if (cls == null) { + cls = Class.forName(className, initialize, classloader); + } + return cls; + } + }); + } catch (PrivilegedActionException e) { + throw (ClassNotFoundException)e.getException(); + } + + return cl; + } + + /** + * @param p Package + * @param cl + * @return true if each package has a ObjectFactory class or package-info + */ + public static boolean checkPackage(String p, ClassLoader cl) { + + // Each package must have an ObjectFactory + try { + Class<?> cls = forName(p + ".ObjectFactory", false, cl); + if (cls != null) { + return true; + } + //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that + //does not extend Exception. So we will absorb any Throwable exception here. + } catch (Throwable e) { + // Ignore + } + + try { + Class<?> cls = forName(p + ".package-info", false, cl); + if (cls != null) { + return cls.isAnnotationPresent(XmlSchema.class); + } + //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that + //does not extend Exception. So we will absorb any Throwable exception here. + } catch (Throwable e) { + // Ignore + } + + return false; + } + + public Marshaller getMarshaller(JAXBContext context) throws JAXBException { + synchronized (mpool) { + Marshaller marshaller = mpool.get(context); + if (marshaller == null) { + marshaller = context.createMarshaller(); + mpool.put(context, marshaller); + } + return marshaller; + } + } + + public Unmarshaller getUnmarshaller(JAXBContext context) throws JAXBException { + synchronized (upool) { + Unmarshaller unmarshaller = upool.get(context); + if (unmarshaller == null) { + unmarshaller = context.createUnmarshaller(); + upool.put(context, unmarshaller); + } + return unmarshaller; + } + } + + public LRUCache<Object, JAXBContext> getCache() { + return cache; + } + + public JAXBContext getJAXBContext(Class<?> cls) throws JAXBException { + if (BUILTIN_CLASSES_SET.contains(cls)) { + return defaultContext; + } + synchronized (cache) { + JAXBContext context = cache.get(cls); + if (context != null) { + return context; + } + Package pkg = cls.getPackage(); + if (pkg != null) { + context = cache.get(pkg); + if (context != null) { + return context; + } + } + + if (pkg != null && checkPackage(pkg.getName(), cls.getClassLoader())) { + context = JAXBContext.newInstance(pkg.getName(), cls.getClassLoader()); + cache.put(pkg, context); + } else { + context = JAXBContext.newInstance(cls); + cache.put(cls, context); + } + return context; + + } + } + + public JAXBContext getJAXBContext(Class<?>[] classes) throws JAXBException { + Set<Class<?>> classSet = new HashSet<Class<?>>(Arrays.asList(classes)); + return getJAXBContext(classSet); + } + + public JAXBContext getJAXBContext(Set<Class<?>> classes) throws JAXBException { + // Remove the JAXB built-in types to maximize the cache hit + Set<Class<?>> classSet = new HashSet<Class<?>>(classes); + classSet.removeAll(BUILTIN_CLASSES_SET); + + // FIXME: [rfeng] Remove java classes that are mapped to the same XSD type to avoid + // conflicts + if (classSet.contains(Date[].class)) { + classSet.remove(Calendar[].class); + } + + if (classSet.contains(URI[].class)) { + classSet.remove(UUID[].class); + } + + if (classSet.contains(Source[].class)) { + classSet.remove(Image[].class); + classSet.remove(DataHandler[].class); + } + + if(classSet.isEmpty()) { + return defaultContext; + } + + // For single class + if (classSet.size() == 1) { + return getJAXBContext(classSet.iterator().next()); + } + synchronized (cache) { + JAXBContext context = cache.get(classSet); + if (context != null) { + return context; + } + context = JAXBContext.newInstance(classSet.toArray(new Class<?>[classSet.size()])); + cache.put(classSet, context); + return context; + } + } + + public void clear() { + synchronized (cache) { + cache.clear(); + } + synchronized (upool) { + upool.clear(); + } + synchronized (upool) { + upool.clear(); + } + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java new file mode 100644 index 0000000000..6d66f10a8e --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java @@ -0,0 +1,429 @@ +/* + * 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.beans.Introspector; +import java.io.IOException; +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.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.SchemaOutputResolver; +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.XmlType; +import javax.xml.namespace.QName; +import javax.xml.transform.Result; +import javax.xml.transform.dom.DOMResult; + +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.util.WrapperInfo; +import org.apache.tuscany.sca.interfacedef.util.XMLType; +import org.w3c.dom.Node; + +/** + * + * @version $Rev$ $Date$ + */ +// FIXME: [rfeng] We probably should turn this into a pluggable system service +public class JAXBContextHelper { + // public static final String JAXB_CLASSES = "jaxb.classes"; + + // public static final String JAXB_CONTEXT_PATH = "jaxb.contextPath"; + + 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 grantularity 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); + + } + + 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 Marshaller getMarshaller(JAXBContext context) throws JAXBException { + return cache.getMarshaller(context); + } + + @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, type, 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 + */ + 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; + } + } + + 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; + } + + 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()); + } + } + 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); + } + 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, 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 + */ + 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); + return dataTypes; + } + + private static void getDataTypes(List<DataType> dataTypes, Operation op, boolean useWrapper) { + WrapperInfo wrapper = op.getWrapper(); + if (useWrapper && wrapper != null) { + DataType dt1 = wrapper.getInputWrapperType(); + if (dt1 != null) { + dataTypes.add(dt1); + } + DataType dt2 = wrapper.getOutputWrapperType(); + 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); + } + DataType 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); + } + } + } + + 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 = Introspector.decapitalize(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 = Introspector.decapitalize(javaType.getSimpleName()); + } + typeQName = new QName(typeNamespace, typeName); + } else { + XmlEnum xmlEnum = javaType.getAnnotation(XmlEnum.class); + if (xmlEnum != null) { + name = Introspector.decapitalize(javaType.getSimpleName()); + typeQName = new QName(namespace, name); + } + } + if (elementQName == null && typeQName == null) { + return null; + } + return new XMLType(elementQName, typeQName); + } + + public static Node generateSchema(JAXBContext context) throws Exception { + SchemaOutputResolverImpl resolver = new SchemaOutputResolverImpl(); + context.generateSchema(resolver); + return resolver.getSchema(); + } + + public static class SchemaOutputResolverImpl extends SchemaOutputResolver { + private DOMResult result = new DOMResult(); + + @Override + public Result createOutput(String ns, String file) throws IOException { + result.setSystemId("sca:dom"); + return result; + } + + public Node getSchema() { + return result != null ? result.getNode() : null; + } + + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBDataBinding.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBDataBinding.java new file mode 100644 index 0000000000..eebb4b2e37 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBDataBinding.java @@ -0,0 +1,132 @@ +/* + * 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.ParameterizedType; +import java.lang.reflect.Type; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; + +import org.apache.tuscany.sca.databinding.WrapperHandler; +import org.apache.tuscany.sca.databinding.XMLTypeHelper; +import org.apache.tuscany.sca.databinding.impl.BaseDataBinding; +import org.apache.tuscany.sca.databinding.impl.DOMHelper; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; +import org.apache.tuscany.sca.interfacedef.util.XMLType; +import org.w3c.dom.Document; + +/** + * JAXB DataBinding + * + * @version $Rev$ $Date$ + */ +public class JAXBDataBinding extends BaseDataBinding { + public static final String NAME = JAXBElement.class.getName(); + public static final String[] ALIASES = new String[] {"jaxb"}; + + public static final String ROOT_NAMESPACE = "http://tuscany.apache.org/xmlns/sca/databinding/jaxb/1.0"; + public static final QName ROOT_ELEMENT = new QName(ROOT_NAMESPACE, "root"); + + private JAXBWrapperHandler wrapperHandler; + private JAXBTypeHelper xmlTypeHelper; + + public JAXBDataBinding() { + super(NAME, ALIASES, JAXBElement.class); + this.wrapperHandler = new JAXBWrapperHandler(); + this.xmlTypeHelper = new JAXBTypeHelper(); + } + + @Override + public boolean introspect(DataType dataType, Operation operation) { + Class javaType = dataType.getPhysical(); + if (JAXBElement.class.isAssignableFrom(javaType)) { + Type type = javaType.getGenericSuperclass(); + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = ((ParameterizedType)type); + Type rawType = parameterizedType.getRawType(); + if (rawType == JAXBElement.class) { + Type actualType = parameterizedType.getActualTypeArguments()[0]; + if (actualType instanceof Class) { + XMLType xmlType = JAXBContextHelper.getXmlTypeName((Class)actualType); + dataType.setLogical(xmlType); + dataType.setDataBinding(NAME); + return true; + } + } + } + if (dataType.getLogical() == null) { + dataType.setLogical(XMLType.UNKNOWN); + } + dataType.setDataBinding(NAME); + return true; + } + + XMLType xmlType = JAXBContextHelper.getXmlTypeName(javaType); + if (xmlType == null) { + return false; + } + dataType.setLogical(xmlType); + dataType.setDataBinding(NAME); + return true; + } + + @SuppressWarnings("unchecked") + @Override + public Object copy(Object arg, DataType dataType, Operation operation) { + try { + boolean isElement = false; + if (dataType == null) { + Class cls = arg.getClass(); + if (arg instanceof JAXBElement) { + isElement = true; + cls = ((JAXBElement)arg).getDeclaredType(); + } + dataType = new DataTypeImpl<XMLType>(NAME, cls, XMLType.UNKNOWN); + } + JAXBContext context = JAXBContextHelper.createJAXBContext(dataType); + arg = JAXBContextHelper.createJAXBElement(context, dataType, arg); + Document doc = DOMHelper.newDocument(); + context.createMarshaller().marshal(arg, doc); + Object value = context.createUnmarshaller().unmarshal(doc, dataType.getPhysical()); + if (isElement && value instanceof JAXBElement) { + return value; + } + return JAXBContextHelper.createReturnValue(context, dataType, value); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public WrapperHandler getWrapperHandler() { + return wrapperHandler; + } + + @Override + public XMLTypeHelper getXMLTypeHelper() { + // return new JAXBTypeHelper(); + return xmlTypeHelper; + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBPropertyDescriptor.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBPropertyDescriptor.java new file mode 100644 index 0000000000..8805d72627 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBPropertyDescriptor.java @@ -0,0 +1,302 @@ +/* + * 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.beans.IndexedPropertyDescriptor; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; + +import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; + +import org.osoa.sca.ServiceRuntimeException; + +/** + * A PropertyDescriptor provides acesss to a bean property. Values can be queried/changed using the + * read and writer methods of the PropertyDescriptor. + * <p/> + * A PropertyDescriptorPlus object wraps a PropertyDescriptor and supplies enhanced set/get methods + * that match JAXB semantis. + * <p/> + * For example, the set(..) method is smart enough to add lists, arrays and atomic values on JAXB + * beans. + * <p/> + * The PropertyDescriptorPlus object also stores the xmlName of the property. + * + * @See XMLRootElementUtil.createPropertyDescriptorMap , which creates the PropertyDescriptorPlus + * objects + */ +public class JAXBPropertyDescriptor implements Comparable<JAXBPropertyDescriptor> { + PropertyDescriptor descriptor; + QName xmlName = null; + int index; + + /** + * Package protected constructor. Only created by XMLRootElementUtil.createPropertyDescriptorMap + * @param descriptor + * @param index TODO + * @param propertyName + * + * @see XMLRootElementUtil.createPropertyDescriptorMap + */ + JAXBPropertyDescriptor(PropertyDescriptor descriptor, QName xmlName, int index) { + super(); + this.descriptor = descriptor; + this.xmlName = xmlName; + } + + /** + * Package protected constructor. Only created by XMLRootElementUtil.createPropertyDescriptorMap + * @param descriptor + * @param index TODO + * @param propertyName + * + * @see XMLRootElementUtil.createPropertyDescriptorMap + */ + JAXBPropertyDescriptor(PropertyDescriptor descriptor, String xmlName, int index) { + super(); + this.descriptor = descriptor; + this.xmlName = new QName("", xmlName); + } + + public int compareTo(JAXBPropertyDescriptor o) { + return index - o.index; + } + + /** @return xmlname */ + public String getXmlName() { + return xmlName.getLocalPart(); + } + + public QName getXmlQName() { + return xmlName; + } + + /** @return property type */ + public Class<?> getPropertyType() { + return descriptor.getPropertyType(); + } + + /** @return property name */ + public String getPropertyName() { + return descriptor.getName(); + } + + /** + * Get the object + * + * @param targetBean + * @return Object for this property or null + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + public Object get(Object targetBean) throws InvocationTargetException, IllegalAccessException { + Method method = descriptor.getReadMethod(); + if (method == null && descriptor.getPropertyType() == Boolean.class) { + String propertyName = descriptor.getName(); + if (propertyName != null) { + String methodName = "is"; + methodName = + methodName + ((propertyName.length() > 0) ? propertyName.substring(0, 1).toUpperCase() : ""); + methodName = methodName + ((propertyName.length() > 1) ? propertyName.substring(1) : ""); + try { + method = targetBean.getClass().getMethod(methodName); + } catch (NoSuchMethodException e) { + } + } + } + if (method == null) { + throw new ServiceRuntimeException("No getter is found"); + } + Object ret = method.invoke(targetBean); + if (method.getReturnType() == JAXBElement.class) { + ret = ((JAXBElement<?>)ret).getValue(); + } + return ret; + } + + /** + * Set the object + * + * @param targetBean + * @param propValue + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws JAXBWrapperException + */ + public void set(Object targetBean, Object propValue) throws InvocationTargetException, IllegalAccessException, + JAXBWrapperException { + + Method writeMethod = null; + try { + // No set occurs if the value is null + if (propValue == null) { + return; + } + + // There are 3 different types of setters that can occur. + // 1) Normal Attomic Setter : setFoo(type) + // 2) Indexed Array Setter : setFoo(type[]) + // 3) No Setter case if the property is a List<T>. + + writeMethod = descriptor.getWriteMethod(); + if (descriptor instanceof IndexedPropertyDescriptor) { + // Set for indexed T[] + setIndexedArray(targetBean, propValue, writeMethod); + } else if (writeMethod == null) { + // Set for List<T> + setList(targetBean, propValue); + } else if (descriptor.getPropertyType() == JAXBElement.class) { + if (propValue != null) { + Class<?> clazz = propValue.getClass(); + JAXBElement<?> element = new JAXBElement(xmlName, clazz, propValue); + setAtomic(targetBean, element, writeMethod); + } + } else { + // Normal case + setAtomic(targetBean, propValue, writeMethod); + } + } catch (RuntimeException e) { + throw e; + } + } + + /** + * Set the property value onto targetBean using the writeMethod + * + * @param targetBean + * @param propValue + * @param writeMethod (set(T)) + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws JAXBWrapperException + */ + private void setAtomic(Object targetBean, Object propValue, Method writeMethod) throws InvocationTargetException, + IllegalAccessException, JAXBWrapperException { + // JAXB provides setters for atomic value. + + if (propValue != null) { + // Normal case + Object[] SINGLE_PARAM = new Object[1]; + SINGLE_PARAM[0] = propValue; + writeMethod.invoke(targetBean, SINGLE_PARAM); + } else { + Class<?>[] paramTypes = writeMethod.getParameterTypes(); + + if (paramTypes != null && paramTypes.length == 1) { + Class<?> paramType = paramTypes[0]; + if (paramType.isPrimitive() && propValue == null) { + //Ignoring null value for primitive type, this could potentially be the way of a customer indicating to set + //default values defined in JAXBObject/xmlSchema. + return; + } + } + } + + } + + /** + * Set the property value using the indexed array setter + * + * @param targetBean + * @param propValue + * @param writeMethod set(T[]) + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws JAXBWrapperException + */ + private void setIndexedArray(Object targetBean, Object propValue, Method writeMethod) + throws InvocationTargetException, IllegalAccessException, JAXBWrapperException { + + Class<?> paramType = writeMethod.getParameterTypes()[0]; + Object value = asArray(propValue, paramType); + // JAXB provides setters for atomic value. + Object[] SINGLE_PARAM = new Object[1]; + SINGLE_PARAM[0] = value; + + writeMethod.invoke(targetBean, SINGLE_PARAM); + } + + /** + * Set the property value for the collection case. + * + * @param targetBean + * @param propValue + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws JAXBWrapperException + */ + private void setList(Object targetBean, Object propValue) throws InvocationTargetException, IllegalAccessException, + JAXBWrapperException { + // For the List<T> case, there is no setter. + // You are supposed to use the getter to obtain access to the collection and then add the collection + + Collection value = asCollection(propValue, descriptor.getPropertyType()); + Collection collection = (Collection)get(targetBean); + + // Now add our our object to the collection + collection.clear(); + if (propValue != null) { + collection.addAll(value); + } + } + + /** + * @param propValue + * @param destType + * @return propValue as a Collection + */ + private static Collection asCollection(Object propValue, Class<?> destType) { + // TODO Missing function + // Convert the object into an equivalent object that is a collection + if (DataConverter.isConvertable(propValue, destType)) { + return (Collection)DataConverter.convert(propValue, destType); + } else { + String objectClass = (propValue == null) ? "null" : propValue.getClass().getName(); + throw new ServiceRuntimeException("Cannot convert " + objectClass); + } + } + + /** + * @param propValue + * @param destType T[] + * @return array of component type + */ + private static Object asArray(Object propValue, Class<?> destType) { + if (DataConverter.isConvertable(propValue, destType)) { + return DataConverter.convert(propValue, destType); + } else { + String objectClass = (propValue == null) ? "null" : propValue.getClass().getName(); + throw new ServiceRuntimeException("Cannot convert " + objectClass); + + } + } + + @Override + public String toString() { + String value = "PropertyDescriptorPlus["; + value += " name=" + this.getPropertyName(); + value += " type=" + this.getPropertyType().getName(); + value += " propertyDecriptor=" + this.descriptor; + return value + "]"; + } +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBTypeHelper.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBTypeHelper.java new file mode 100644 index 0000000000..573e636884 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBTypeHelper.java @@ -0,0 +1,276 @@ +/* + * 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.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.SchemaOutputResolver; +import javax.xml.namespace.QName; +import javax.xml.transform.Result; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.stream.StreamResult; + +import org.apache.tuscany.sca.contribution.resolver.ModelResolver; +import org.apache.tuscany.sca.databinding.XMLTypeHelper; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.Interface; +import org.apache.tuscany.sca.interfacedef.util.JavaXMLMapper; +import org.apache.tuscany.sca.interfacedef.util.TypeInfo; +import org.apache.tuscany.sca.interfacedef.util.XMLType; +import org.apache.tuscany.sca.xsd.XSDFactory; +import org.apache.tuscany.sca.xsd.XSDefinition; +import org.apache.ws.commons.schema.resolver.URIResolver; +import org.osoa.sca.ServiceRuntimeException; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; + +public class JAXBTypeHelper implements XMLTypeHelper { + private static final String SCHEMA_NS = "http://www.w3.org/2001/XMLSchema"; + private static final String ANYTYPE_NAME = "anyType"; + private static final QName ANYTYPE_QNAME = new QName(SCHEMA_NS, ANYTYPE_NAME); + + // private List<Class<?>> types = new ArrayList<Class<?>>(); + + public JAXBTypeHelper() { + super(); + } + + public TypeInfo getTypeInfo(Class javaType, Object logical) { + QName xmlType = JavaXMLMapper.getXMLType(javaType); + if (xmlType != null) { + return new TypeInfo(xmlType, true, null); + } else if (javaType.isInterface()) { + return new TypeInfo(ANYTYPE_QNAME, true, null); + } else { + // types.add(javaType); + if (logical instanceof XMLType) { + xmlType = ((XMLType)logical).getTypeName(); + } + if (xmlType == null) { + xmlType = new QName(jaxbRIDecapitalize(javaType.getSimpleName())); + } + return new TypeInfo(xmlType, false, null); + } + } + + /** + * The JAXB RI doesn't implement the decapitalization algorithm in the + * JAXB spec. See Sun bug 6505643 for details. This means that we need + * to mimic the incorrect algorithm for references from wrapper schemas. + */ + private String jaxbRIDecapitalize(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 List<XSDefinition> getSchemaDefinitions(XSDFactory factory, ModelResolver resolver) { + List<XSDefinition> definitions = new ArrayList<XSDefinition>(); + generateJAXBSchemas(definitions, factory); + return definitions; + } + */ + + public static Map<String, String> generateSchema(JAXBContext context) throws IOException { + StringResolverImpl resolver = new StringResolverImpl(); + context.generateSchema(resolver); + Map<String, String> xsds = new HashMap<String, String>(); + for (Map.Entry<String, StreamResult> xsd : resolver.getResults().entrySet()) { + xsds.put(xsd.getKey(), xsd.getValue().getWriter().toString()); + } + return xsds; + } + + private static class XSDResolver implements URIResolver { + private Map<String, String> xsds; + + public XSDResolver(Map<String, String> xsds) { + super(); + this.xsds = xsds; + } + + public InputSource resolveEntity(java.lang.String namespace, + java.lang.String schemaLocation, + java.lang.String baseUri) { + String xsd = xsds.get(schemaLocation); + if (xsd == null) { + return null; + } + return new InputSource(new StringReader(xsd)); + } + + } + + /* + private void generateJAXBSchemas1(List<XSDefinition> definitions, XSDFactory factory) { + if (types.size() > 0) { + try { + XmlSchemaCollection collection = new XmlSchemaCollection(); + Class[] typesArray = new Class[types.size()]; + typesArray = types.toArray(typesArray); + JAXBContext context = JAXBContextHelper.createJAXBContext(typesArray); + Map<String, String> results = generateSchema(context); + collection.setSchemaResolver(new XSDResolver(results)); + + for (Map.Entry<String, String> entry : results.entrySet()) { + XSDefinition definition = factory.createXSDefinition(); + int index = entry.getKey().lastIndexOf('#'); + String ns = entry.getKey().substring(0, index); + String file = entry.getKey().substring(index + 1); + definition.setUnresolved(true); + definition.setNamespace(ns); + definition.setSchema(collection.read(new StringReader(entry.getValue()), null)); + definition.setSchemaCollection(collection); + definition.setUnresolved(false); + definitions.add(definition); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + */ + + private static class DOMResolverImpl extends SchemaOutputResolver { + private Map<String, DOMResult> results = new HashMap<String, DOMResult>(); + + @Override + public Result createOutput(String ns, String file) throws IOException { + DOMResult result = new DOMResult(); + // TUSCANY-2498: Set the system id to "" so that the xsd:import doesn't produce + // an illegal schemaLocation attr + result.setSystemId(""); + results.put(ns, result); + return result; + } + + public Map<String, DOMResult> getResults() { + return results; + } + } + + /* + private void generateJAXBSchemas(List<XSDefinition> definitions, XSDFactory factory) { + if (types.size() > 0) { + try { + Class<?>[] typesArray = new Class<?>[types.size()]; + typesArray = types.toArray(typesArray); + JAXBContext context = JAXBContext.newInstance(typesArray); + generateSchemas(definitions, factory, context); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + */ + + private void generateSchemas(List<XSDefinition> definitions, XSDFactory factory, JAXBContext context) + throws IOException { + DOMResolverImpl resolver = new DOMResolverImpl(); + context.generateSchema(resolver); + Map<String, DOMResult> results = resolver.getResults(); + for (Map.Entry<String, DOMResult> entry : results.entrySet()) { + XSDefinition definition = factory.createXSDefinition(); + definition.setUnresolved(true); + definition.setDocument((Document)entry.getValue().getNode()); + definition.setNamespace(entry.getKey()); + URI location = null; + try { + location = new URI(entry.getValue().getSystemId()); + } catch (URISyntaxException e) { + // ignore: use null value + } + definition.setLocation(location); + definitions.add(definition); + } + } + + private static class StringResolverImpl extends SchemaOutputResolver { + private Map<String, StreamResult> results = new HashMap<String, StreamResult>(); + + @Override + public Result createOutput(String ns, String file) throws IOException { + StringWriter sw = new StringWriter(); + StreamResult result = new StreamResult(sw); + String sysId = ns + '#' + file; + result.setSystemId(sysId); + results.put(sysId, result); + return result; + } + + public Map<String, StreamResult> getResults() { + return results; + } + } + + public List<XSDefinition> getSchemaDefinitions(XSDFactory factory, ModelResolver resolver, Interface intf) { + try { + JAXBContext context = JAXBContextHelper.createJAXBContext(intf, false); + List<XSDefinition> definitions = new ArrayList<XSDefinition>(); + generateSchemas(definitions, factory, context); + return definitions; + } catch (Throwable e) { + throw new ServiceRuntimeException(e); + } + } + + public List<XSDefinition> getSchemaDefinitions(XSDFactory factory, ModelResolver resolver, List<DataType> dataTypes) { + try { + + JAXBContext context = JAXBContextHelper.createJAXBContext(dataTypes); + List<XSDefinition> definitions = new ArrayList<XSDefinition>(); + generateSchemas(definitions, factory, context); + return definitions; + } catch (Throwable e) { + throw new ServiceRuntimeException(e); + } + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperException.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperException.java new file mode 100644 index 0000000000..f83c7f09d9 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperException.java @@ -0,0 +1,56 @@ +/* + * 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 org.osoa.sca.ServiceRuntimeException; + +public class JAXBWrapperException extends ServiceRuntimeException { + private static final long serialVersionUID = 1L; + + /** + * + */ + public JAXBWrapperException() { + super(); + + } + + /** + * @param message + * @param cause + */ + public JAXBWrapperException(String message, Throwable cause) { + super(message, cause); + + } + + /** @param message */ + public JAXBWrapperException(String message) { + super(message); + + } + + /** @param cause */ + public JAXBWrapperException(Throwable cause) { + super(cause); + + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHandler.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHandler.java new file mode 100644 index 0000000000..a1f4c12c8f --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHandler.java @@ -0,0 +1,151 @@ +/* + * 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.Method; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.WrapperHandler; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.interfacedef.util.ElementInfo; +import org.apache.tuscany.sca.interfacedef.util.WrapperInfo; + +/** + * JAXB WrapperHandler implementation + * + * @version $Rev$ $Date$ + */ +public class JAXBWrapperHandler implements WrapperHandler<Object> { + private JAXBWrapperHelper helper = new JAXBWrapperHelper(); + + public Object create(Operation operation, boolean input) { + WrapperInfo wrapperInfo = operation.getWrapper(); + ElementInfo element = input ? wrapperInfo.getInputWrapperElement() : wrapperInfo.getOutputWrapperElement(); + final Class<?> wrapperClass = input ? wrapperInfo.getInputWrapperClass() : wrapperInfo.getOutputWrapperClass(); + try { + if (wrapperClass == null) { + return null; + } + return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() throws Exception { + return wrapperClass.newInstance(); + } + }); + } catch (PrivilegedActionException e) { + throw new TransformationException(e); + } + } + + public void setChildren(Object wrapper, Object[] childObjects, Operation operation, boolean input) { + List<ElementInfo> childElements = + input ? operation.getWrapper().getInputChildElements() : operation.getWrapper().getOutputChildElements(); + List<String> childNames = new ArrayList<String>(); + Map<String, Object> values = new HashMap<String, Object>(); + for (int i = 0; i < childElements.size(); i++) { + ElementInfo e = childElements.get(i); + String name = e.getQName().getLocalPart(); + childNames.add(name); + values.put(name, childObjects[i]); + } + // Get the property descriptor map + Map<String, JAXBPropertyDescriptor> pdMap = null; + try { + pdMap = XMLRootElementUtil.createPropertyDescriptorMap(wrapper.getClass()); + } catch (Throwable t) { + throw new JAXBWrapperException(t); + } + helper.wrap(wrapper, childNames, values, pdMap); + } + + public void setChild(Object wrapper, int i, ElementInfo childElement, Object value) { + Object wrapperValue = wrapper; + Class<?> wrapperClass = wrapperValue.getClass(); + + // FIXME: We probably should use the jaxb-reflection to handle the properties + try { + String prop = childElement.getQName().getLocalPart(); + boolean collection = (value instanceof Collection); + Method getter = null; + for (Method m : wrapperClass.getMethods()) { + Class<?>[] paramTypes = m.getParameterTypes(); + if (paramTypes.length == 1 && m.getName().equals("set" + capitalize(prop))) { + m.invoke(wrapperValue, new Object[] {value}); + return; + } + if (collection && paramTypes.length == 0 && m.getName().equals("get" + capitalize(prop))) { + getter = m; + } + } + if (getter != null && Collection.class.isAssignableFrom(getter.getReturnType())) { + ((Collection)getter.invoke(wrapperValue)).addAll((Collection)value); + } + + } catch (Throwable e) { + throw new TransformationException(e); + } + } + + private static String capitalize(String name) { + char first = Character.toUpperCase(name.charAt(0)); + return first + name.substring(1); + } + + /** + * @see org.apache.tuscany.sca.databinding.WrapperHandler#getChildren(java.lang.Object, Operation, boolean) + */ + public List getChildren(Object wrapper, Operation operation, boolean input) { + List<ElementInfo> childElements = input? operation.getWrapper().getInputChildElements(): + operation.getWrapper().getOutputChildElements(); + + List<String> childNames = new ArrayList<String>(); + for (ElementInfo e : childElements) { + childNames.add(e.getQName().getLocalPart()); + } + return Arrays.asList(helper.unwrap(wrapper, childNames)); + } + + /** + * @see org.apache.tuscany.sca.databinding.WrapperHandler#getWrapperType(Operation, boolean) + */ + public DataType getWrapperType(Operation operation, boolean input) { + WrapperInfo wrapper = operation.getWrapper(); + DataType dt = input ? wrapper.getInputWrapperType() : wrapper.getOutputWrapperType(); + return dt; + } + + /** + * @see org.apache.tuscany.sca.databinding.WrapperHandler#isInstance(java.lang.Object, Operation, boolean) + */ + public boolean isInstance(Object wrapper, Operation operation, boolean input) { + Class<?> wrapperClass = + input ? operation.getWrapper().getInputWrapperClass() : operation.getWrapper().getOutputWrapperClass(); + return wrapperClass == null ? false : wrapperClass.isInstance(wrapper); + } +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHelper.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHelper.java new file mode 100644 index 0000000000..5f90aa4d7a --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHelper.java @@ -0,0 +1,166 @@ +/* + * 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.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * The JAXBWrapper tool is used to create a JAXB Object from a series of child objects (wrap) or get + * the child objects from a JAXB Object (unwrap) + */ +public class JAXBWrapperHelper { + + /** + * unwrap Returns the list of child objects of the jaxb object + * + * @param jaxbObject that represents the type + * @param childNames list of xml child names as String + * @param pdMap PropertyDescriptor map for this jaxbObject + * @return list of Objects in the same order as the element names. + */ + public Object[] unwrap(Object jaxbObject, List<String> childNames, Map<String, JAXBPropertyDescriptor> pdMap) + throws JAXBWrapperException { + + // Get the object that will have the property descriptors (i.e. the object representing the complexType) + Object jaxbComplexTypeObj = jaxbObject; + + // Get the PropertyDescriptorPlus map. + // The method makes sure that each child name has a matching jaxb property + // checkPropertyDescriptorMap(jaxbComplexTypeObj.getClass(), childNames, pdMap); + + // Get the corresponsing objects from the jaxb bean + ArrayList<Object> objList = new ArrayList<Object>(); + int index = 0; + for (String childName : childNames) { + JAXBPropertyDescriptor propInfo = getPropertyDescriptor(pdMap, childName, index); + + Object object = null; + try { + object = propInfo.get(jaxbComplexTypeObj); + } catch (Throwable e) { + throw new JAXBWrapperException(e); + } + + objList.add(object); + index++; + } + Object[] jaxbObjects = objList.toArray(); + objList = null; + return jaxbObjects; + + } + + private JAXBPropertyDescriptor getPropertyDescriptor(Map<String, JAXBPropertyDescriptor> pdMap, + String childName, + int index) { + JAXBPropertyDescriptor propInfo = pdMap.get(childName); + if (propInfo == null) { + // FIXME: [rfeng] Sometimes the child element names don't match. Get chilld by location? + List<JAXBPropertyDescriptor> props = new ArrayList<JAXBPropertyDescriptor>(pdMap.values()); + // Sort the properties by index. We might need to take propOrder into consideration + Collections.sort(props); + propInfo = props.get(index); + } + return propInfo; + } + + /** + * wrap Creates a jaxb object that is initialized with the child objects. + * <p/> + * Note that the jaxbClass must be the class the represents the complexType. (It should never be + * JAXBElement) + * + * @param jaxbClass + * @param childNames list of xml child names as String + * @param childObjects, component type objects + * @param pdMap PropertyDescriptor map for this jaxbObject + */ + public Object wrap(Class<?> jaxbClass, + List<String> childNames, + Map<String, Object> childObjects, + Map<String, JAXBPropertyDescriptor> pdMap) throws JAXBWrapperException { + + // Just like unWrap, get the property info map + // checkPropertyDescriptorMap(jaxbClass, childNames, pdMap); + + // The jaxb object always has a default constructor. Create the object + Object jaxbObject = null; + try { + jaxbObject = jaxbClass.newInstance(); + } catch (Throwable t) { + throw new JAXBWrapperException(t); + } + + wrap(jaxbObject, childNames, childObjects, pdMap); + + // Return the jaxb object + return jaxbObject; + } + + public void wrap(Object jaxbObject, + List<String> childNames, + Map<String, Object> childObjects, + Map<String, JAXBPropertyDescriptor> pdMap) { + // Now set each object onto the jaxb object + int index = 0; + for (String childName : childNames) { + JAXBPropertyDescriptor propInfo = getPropertyDescriptor(pdMap, childName, index); + Object value = childObjects.get(childName); + try { + propInfo.set(jaxbObject, value); + } catch (Throwable t) { + throw new JAXBWrapperException(t); + } + index++; + } + } + + public Object[] unwrap(Object jaxbObject, List<String> childNames) throws JAXBWrapperException { + // Get the property descriptor map for this JAXBClass + Class<?> jaxbClass = jaxbObject.getClass(); + Map<String, JAXBPropertyDescriptor> pdMap = null; + try { + pdMap = XMLRootElementUtil.createPropertyDescriptorMap(jaxbClass); + } catch (Throwable t) { + throw new JAXBWrapperException(t); + } + + // Delegate + return unwrap(jaxbObject, childNames, pdMap); + } + + public Object wrap(Class<?> jaxbClass, List<String> childNames, Map<String, Object> childObjects) + throws JAXBWrapperException { + // Get the property descriptor map + Map<String, JAXBPropertyDescriptor> pdMap = null; + try { + pdMap = XMLRootElementUtil.createPropertyDescriptorMap(jaxbClass); + } catch (Throwable t) { + throw new JAXBWrapperException(t); + } + + // Delegate + return wrap(jaxbClass, childNames, childObjects, pdMap); + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Node2JAXB.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Node2JAXB.java new file mode 100644 index 0000000000..e375a9b85e --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Node2JAXB.java @@ -0,0 +1,73 @@ +/* + * 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 javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; + +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.BaseTransformer; +import org.w3c.dom.Node; + +/** + * + * @version $Rev$ $Date$ + */ +public class Node2JAXB extends BaseTransformer<Node, Object> implements PullTransformer<Node, Object> { + + public Node2JAXB() { + super(); + } + + public Object transform(Node source, TransformationContext context) { + if (source == null) + return null; + try { + JAXBContext jaxbContext = JAXBContextHelper.createJAXBContext(context, false); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + Object result = unmarshaller.unmarshal(source, JAXBContextHelper.getJavaType(context.getTargetDataType())); + return JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + protected Class<Node> getSourceType() { + return Node.class; + } + + @Override + protected Class<Object> getTargetType() { + return Object.class; + } + + @Override + public int getWeight() { + return 30; + } + + @Override + public String getTargetDataBinding() { + return JAXBDataBinding.NAME; + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Reader2JAXB.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Reader2JAXB.java new file mode 100644 index 0000000000..840edfd234 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Reader2JAXB.java @@ -0,0 +1,74 @@ +/* + * 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.io.Reader; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; + +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.BaseTransformer; + +/** + * + * @version $Rev$ $Date$ + */ +public class Reader2JAXB extends BaseTransformer<Reader, Object> implements + PullTransformer<Reader, Object> { + + public Object transform(final Reader source, final TransformationContext context) { + if (source == null) { + return null; + } + try { + JAXBContext jaxbContext = JAXBContextHelper.createJAXBContext(context, false); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + StreamSource streamSource = new StreamSource(source); + Object result = unmarshaller.unmarshal(streamSource, JAXBContextHelper.getJavaType(context.getTargetDataType())); + return JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + protected Class<Reader> getSourceType() { + return Reader.class; + } + + @Override + protected Class<Object> getTargetType() { + return Object.class; + } + + @Override + public int getWeight() { + return 30; + } + + @Override + public String getTargetDataBinding() { + return JAXBDataBinding.NAME; + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/String2JAXB.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/String2JAXB.java new file mode 100644 index 0000000000..5559690de8 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/String2JAXB.java @@ -0,0 +1,80 @@ +/* + * 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.io.StringReader; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; + +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.BaseTransformer; +import org.apache.tuscany.sca.databinding.xml.XMLStringDataBinding; + +/** + * + * @version $Rev$ $Date$ + */ +public class String2JAXB extends BaseTransformer<String, Object> implements + PullTransformer<String, Object> { + + public Object transform(final String source, final TransformationContext context) { + if (source == null) { + return null; + } + try { + JAXBContext jaxbContext = JAXBContextHelper.createJAXBContext(context, false); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + StreamSource streamSource = new StreamSource(new StringReader(source)); + Object result = unmarshaller.unmarshal(streamSource, JAXBContextHelper.getJavaType(context.getTargetDataType())); + return JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + protected Class<String> getSourceType() { + return String.class; + } + + @Override + protected Class<Object> getTargetType() { + return Object.class; + } + + @Override + public int getWeight() { + return 30; + } + + @Override + public String getSourceDataBinding() { + return XMLStringDataBinding.NAME; + } + + @Override + public String getTargetDataBinding() { + return JAXBDataBinding.NAME; + } + +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLAdapterExtensionPoint.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLAdapterExtensionPoint.java new file mode 100644 index 0000000000..5fa98b5ed1 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLAdapterExtensionPoint.java @@ -0,0 +1,52 @@ +/* + * 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.util.Map; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +/** + * @version $Rev$ $Date$ + */ +public interface XMLAdapterExtensionPoint { + /** + * @param boundType + * @param adapter + */ + void addAdapter(Class<?> boundType, Class<? extends XmlAdapter> adapter); + + /** + * @param boundType + * @return + */ + Class<? extends XmlAdapter> getAdapter(Class<?> boundType); + + /** + * @param boundType + * @return + */ + Class<? extends XmlAdapter> removeAdapter(Class<?> boundType); + + /** + * @return + */ + Map<Class<?>, Class<? extends XmlAdapter>> getAdapters(); +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLRootElementUtil.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLRootElementUtil.java new file mode 100644 index 0000000000..ad4bd04084 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLRootElementUtil.java @@ -0,0 +1,286 @@ +/* + * 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.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlEnumValue; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSchema; +import javax.xml.namespace.QName; + +/** + * + */ +public class XMLRootElementUtil { + + /** Constructor is intentionally private. This class only provides static utility methods */ + private XMLRootElementUtil() { + + } + + /** + * @param clazz + * @return namespace of root element qname or null if this is not object does not represent a + * root element + */ + public static QName getXmlRootElementQNameFromObject(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(); + return getXmlRootElementQName(clazz); + } + + /** + * @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(Class<?> clazz) { + + // See if the object represents a root element + XmlRootElement root = (XmlRootElement)getAnnotation(clazz, XmlRootElement.class); + if (root == null) { + return null; + } + + String name = root.name(); + String namespace = root.namespace(); + + // The name may need to be defaulted + if (name == null || name.length() == 0 || name.equals("##default")) { + name = getSimpleName(clazz.getCanonicalName()); + } + + // The namespace may need to be defaulted + if (namespace == null || namespace.length() == 0 || namespace.equals("##default")) { + Package pkg = clazz.getPackage(); + XmlSchema schema = (XmlSchema)getAnnotation(pkg, XmlSchema.class); + if (schema != null) { + namespace = schema.namespace(); + } else { + namespace = ""; + } + } + + return new QName(namespace, name); + } + + /** + * @param clazz + * @return namespace of root element qname or null if this is not object does not represent a root element + */ + public static String getEnumValue(Enum<?> myEnum) { + Field f; + String value; + try { + f = myEnum.getClass().getField(myEnum.name()); + + f.setAccessible(true); + + XmlEnumValue xev = (XmlEnumValue)getAnnotation(f, XmlEnumValue.class); + if (xev == null) { + value = f.getName(); + } else { + value = xev.value(); + } + } catch (SecurityException e) { + value = null; + } catch (NoSuchFieldException e) { + value = null; + } + + return value; + } + + /** + * utility method to get the last token in a "."-delimited package+classname string + * + * @return + */ + private static String getSimpleName(String in) { + if (in == null || in.length() == 0) { + return in; + } + String out = null; + StringTokenizer tokenizer = new StringTokenizer(in, "."); + if (tokenizer.countTokens() == 0) + out = in; + else { + while (tokenizer.hasMoreTokens()) { + out = tokenizer.nextToken(); + } + } + return out; + } + + /** + * 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, JAXBPropertyDescriptor> createPropertyDescriptorMap(Class<?> jaxbClass) + throws NoSuchFieldException, IntrospectionException { + + PropertyDescriptor[] pds = Introspector.getBeanInfo(jaxbClass).getPropertyDescriptors(); + Map<String, JAXBPropertyDescriptor> map = new HashMap<String, JAXBPropertyDescriptor>(); + + // Unfortunately the element names are stored on the fields. + // Get all of the fields in the class and super classes + + List<Field> fields = getFields(jaxbClass); + + // 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; + + int index = 0; + 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 + QName xmlName = getXmlElementRefOrElementQName(field.getDeclaringClass(), field); + found = true; + map.put(xmlName.getLocalPart(), new JAXBPropertyDescriptor(pd, xmlName, index)); + index++; + 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 + QName xmlName = getXmlElementRefOrElementQName(field.getDeclaringClass(), field); + found = true; + + map.put(xmlName.getLocalPart(), new JAXBPropertyDescriptor(pd, xmlName, index)); + index++; + break; + } + } + } + + // We didn't find a field. Default the xmlname to the property name + if (!found) { + String xmlName = pd.getName(); + + map.put(xmlName, new JAXBPropertyDescriptor(pd, xmlName, index)); + index++; + } + + } + return map; + } + + /** + * Gets all of the fields in this class and the super classes + * + * @param beanClass + * @return + */ + static private List<Field> getFields(final Class<?> beanClass) { + // This class must remain private due to Java 2 Security concerns + List<Field> fields = AccessController.doPrivileged(new PrivilegedAction<List<Field>>() { + public List<Field> run() { + List<Field> fields = new ArrayList<Field>(); + Class<?> cls = beanClass; + while (cls != null) { + Field[] fieldArray = cls.getDeclaredFields(); + for (Field field : fieldArray) { + fields.add(field); + } + cls = cls.getSuperclass(); + } + return fields; + } + }); + + return fields; + } + + /** + * Get the name of the field by looking at the XmlElement annotation. + * + * @param jaxbClass + * @param fieldName + * @return + * @throws NoSuchFieldException + */ + private static QName getXmlElementRefOrElementQName(Class<?> jaxbClass, Field field) throws NoSuchFieldException { + XmlElementRef xmlElementRef = (XmlElementRef)getAnnotation(field, XmlElementRef.class); + if (xmlElementRef != null) { + return new QName(xmlElementRef.namespace(), xmlElementRef.name()); + } + XmlElement xmlElement = (XmlElement)getAnnotation(field, XmlElement.class); + + // If XmlElement does not exist, default to using the field name + if (xmlElement == null || xmlElement.name().equals("##default")) { + return new QName("", field.getName()); + } + return new QName(xmlElement.namespace(), xmlElement.name()); + } + + /** + * Get an annotation. This is wrappered to avoid a Java2Security violation. + * @param cls Class that contains annotation + * @param annotation Class of requrested Annotation + * @return annotation or null + */ + private static <T extends Annotation> T getAnnotation(final AnnotatedElement element, final Class<T> annotation) { + return AccessController.doPrivileged(new PrivilegedAction<T>() { + public T run() { + return element.getAnnotation(annotation); + } + }); + } +} diff --git a/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLStreamReader2JAXB.java b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLStreamReader2JAXB.java new file mode 100644 index 0000000000..af3fa3fb66 --- /dev/null +++ b/branches/sca-equinox/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLStreamReader2JAXB.java @@ -0,0 +1,77 @@ +/* + * 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 javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import javax.xml.stream.XMLStreamReader; + +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.BaseTransformer; + +/** + * + * @version $Rev$ $Date$ + */ +public class XMLStreamReader2JAXB extends BaseTransformer<XMLStreamReader, Object> implements + PullTransformer<XMLStreamReader, Object> { + + public XMLStreamReader2JAXB() { + super(); + } + + public Object transform(XMLStreamReader source, TransformationContext context) { + if (source == null) { + return null; + } + try { + JAXBContext jaxbContext = JAXBContextHelper.createJAXBContext(context, false); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + // FIXME: [rfeng] If the java type is Object.class, the unmarshalled result will be + // a DOM Node + Object result = unmarshaller.unmarshal(source, JAXBContextHelper.getJavaType(context.getTargetDataType())); + source.close(); + return JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + public Class<XMLStreamReader> getSourceType() { + return XMLStreamReader.class; + } + + @Override + public Class<Object> getTargetType() { + return Object.class; + } + + @Override + public int getWeight() { + return 10; + } + + @Override + public String getTargetDataBinding() { + return JAXBDataBinding.NAME; + } +} |