diff options
Diffstat (limited to 'sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java')
24 files changed, 4044 insertions, 0 deletions
diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/AnyTypeXmlAdapter.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/AnyTypeXmlAdapter.java new file mode 100644 index 0000000000..57922e1c89 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/AnyTypeXmlAdapter.java @@ -0,0 +1,39 @@ +/* + * 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.annotation.adapters.XmlAdapter; + +/** + * This special XmlAdapter can be used by JAXB classes to annotate the references to java interfaces + */ +public class AnyTypeXmlAdapter extends XmlAdapter<Object, Object> { + + @Override + public Object marshal(Object v) throws Exception { + return v; + } + + @Override + public Object unmarshal(Object v) throws Exception { + return v; + } + +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DOMElementXmlAdapter.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DOMElementXmlAdapter.java new file mode 100644 index 0000000000..91cb39b0f2 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DOMElementXmlAdapter.java @@ -0,0 +1,57 @@ +/* + * 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.annotation.adapters.XmlAdapter; + +import org.apache.tuscany.sca.databinding.Mediator; +import org.apache.tuscany.sca.databinding.xml.DOMDataBinding; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; +import org.w3c.dom.Element; + +/** + * A generic XmlAdapter for JAXB to marshal/unmarshal between the java objects and DOM elements + */ +public class DOMElementXmlAdapter extends XmlAdapter<Element, Object> { + private Mediator mediator; + private DataType dataType; + private DataType domType; + + public DOMElementXmlAdapter(Mediator mediator, DataType dataType) { + this.mediator = mediator; + this.dataType = dataType; + this.domType = new DataTypeImpl(DOMDataBinding.NAME, Element.class, dataType.getLogical()); + } + + @Override + public Element marshal(Object value) throws Exception { + return (Element) mediator.mediate(value, dataType, domType, null); + } + + @Override + public Object unmarshal(Element element) throws Exception { + return mediator.mediate(element, domType, dataType, null); + } + + public void setMediator(Mediator mediator) { + this.mediator = mediator; + } +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DataConverter.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DataConverter.java new file mode 100644 index 0000000000..35adffe23b --- /dev/null +++ b/sca-java-2.x/branches/2.0/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.oasisopen.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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DefaultXMLAdapterExtensionPoint.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/DefaultXMLAdapterExtensionPoint.java new file mode 100644 index 0000000000..062da48206 --- /dev/null +++ b/sca-java-2.x/branches/2.0/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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/InputStream2JAXB.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/InputStream2JAXB.java new file mode 100644 index 0000000000..b6baf9c0bd --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/InputStream2JAXB.java @@ -0,0 +1,84 @@ +/* + * 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.InputStream; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; + +/** + * + * @version $Rev$ $Date$ + */ +public class InputStream2JAXB extends BaseTransformer<InputStream, Object> implements + PullTransformer<InputStream, Object> { + private JAXBContextHelper contextHelper; + + public InputStream2JAXB(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + public Object transform(final InputStream source, final TransformationContext context) { + if (source == null) { + return null; + } + try { + JAXBContext jaxbContext = contextHelper.createJAXBContext(context, false); + StreamSource streamSource = new StreamSource(source); + Unmarshaller unmarshaller = contextHelper.getUnmarshaller(jaxbContext); + try { + Object result = + unmarshaller.unmarshal(streamSource, JAXBContextHelper.getJavaType(context.getTargetDataType())); + return JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } finally { + contextHelper.releaseJAXBUnmarshaller(jaxbContext, unmarshaller); + } + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + protected Class<InputStream> getSourceType() { + return InputStream.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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2Node.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2Node.java new file mode 100644 index 0000000000..44c3a5e04f --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2Node.java @@ -0,0 +1,92 @@ +/* + * 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.common.xml.dom.DOMHelper; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +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.xml.DOMDataBinding; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * + * @version $Rev$ $Date$ + */ +public class JAXB2Node extends BaseTransformer<Object, Node> implements PullTransformer<Object, Node> { + private DOMHelper helper; + private JAXBContextHelper contextHelper; + + public JAXB2Node(ExtensionPointRegistry registry) { + super(); + helper = DOMHelper.getInstance(registry); + contextHelper = JAXBContextHelper.getInstance(registry); + } + + public Node transform(Object source, TransformationContext tContext) { + // if (source == null) { + // return null; + // } + try { + JAXBContext context = contextHelper.createJAXBContext(tContext, true); + + // FIXME: The default Marshaller doesn't support + // marshaller.getNode() + Document document = helper.newDocument(); + Object jaxbElement = JAXBContextHelper.createJAXBElement(context, tContext.getSourceDataType(), source); + Marshaller marshaller = contextHelper.getMarshaller(context); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.FALSE); + + try { + marshaller.marshal(jaxbElement, document); + } finally { + contextHelper.releaseJAXBMarshaller(context, marshaller); + } + return DOMDataBinding.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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2OutputStream.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2OutputStream.java new file mode 100644 index 0000000000..0dac091186 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2OutputStream.java @@ -0,0 +1,83 @@ +/* + * 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.OutputStream; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +import org.apache.tuscany.sca.databinding.PushTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; + +/** + * @version $Rev$ $Date$ + */ +public class JAXB2OutputStream extends BaseTransformer<Object, OutputStream> implements + PushTransformer<Object, OutputStream> { + + private JAXBContextHelper contextHelper; + + public JAXB2OutputStream(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + + @Override + protected Class<Object> getSourceType() { + return Object.class; + } + + @Override + protected Class<OutputStream> getTargetType() { + return OutputStream.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, OutputStream target, TransformationContext tContext) { + try { + JAXBContext context = contextHelper.createJAXBContext(tContext, true); + Object jaxbElement = JAXBContextHelper.createJAXBElement(context, tContext.getSourceDataType(), source); + Marshaller marshaller = contextHelper.getMarshaller(context); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.FALSE); + try { + marshaller.marshal(jaxbElement, target); + } finally { + contextHelper.releaseJAXBMarshaller(context, marshaller); + } + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + public int getWeight() { + return 20; + } + + @Override + public String getSourceDataBinding() { + return JAXBDataBinding.NAME; + } +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2SAX.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2SAX.java new file mode 100644 index 0000000000..83c7a78034 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2SAX.java @@ -0,0 +1,83 @@ +/* + * 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.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +import org.apache.tuscany.sca.databinding.PushTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.xml.sax.ContentHandler; + +/** + * @version $Rev$ $Date$ + */ +public class JAXB2SAX extends BaseTransformer<Object, ContentHandler> implements + PushTransformer<Object, ContentHandler> { + + private JAXBContextHelper contextHelper; + + public JAXB2SAX(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + + @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 = contextHelper.createJAXBContext(tContext, true); + Object jaxbElement = JAXBContextHelper.createJAXBElement(context, tContext.getSourceDataType(), source); + Marshaller marshaller = contextHelper.getMarshaller(context); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.FALSE); + + try { + marshaller.marshal(jaxbElement, target); + } finally { + contextHelper.releaseJAXBMarshaller(context, marshaller); + } + } catch (Exception e) { + throw new TransformationException(e); + } + } + + @Override + public int getWeight() { + return 20; + } + + @Override + public String getSourceDataBinding() { + return JAXBDataBinding.NAME; + } +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2String.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2String.java new file mode 100644 index 0000000000..6731f5ae1c --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXB2String.java @@ -0,0 +1,86 @@ +/* + * 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.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +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.xml.XMLStringDataBinding; + +/** + * + * @version $Rev$ $Date$ + */ +public class JAXB2String extends BaseTransformer<Object, String> implements PullTransformer<Object, String> { + private JAXBContextHelper contextHelper; + + public JAXB2String(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + public String transform(Object source, TransformationContext tContext) { + try { + JAXBContext context = contextHelper.createJAXBContext(tContext, true); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + Object jaxbElement = JAXBContextHelper.createJAXBElement(context, tContext.getSourceDataType(), source); + Marshaller marshaller = contextHelper.getMarshaller(context); + try { + marshaller.marshal(jaxbElement, result); + } finally { + contextHelper.releaseJAXBMarshaller(context, marshaller); + } + 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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextCache.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextCache.java new file mode 100644 index 0000000000..5ddf7d3604 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextCache.java @@ -0,0 +1,633 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.ref.SoftReference; +import java.net.URI; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +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.XmlEnum; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlTransient; +import javax.xml.bind.annotation.XmlType; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.transform.Source; + +import org.apache.tuscany.sca.common.java.collection.LRUCache; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.extensibility.ClassLoaderContext; +import org.oasisopen.sca.ServiceRuntimeException; + +/** + * @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 Pool<JAXBContext, Marshaller> mpool; + protected Pool<JAXBContext, Unmarshaller> upool; + + // protected JAXBContext commonContext; + protected JAXBContext defaultContext; + private ExtensionPointRegistry registry; + + public JAXBContextCache(ExtensionPointRegistry registry) { + this(CACHE_SIZE, CACHE_SIZE, CACHE_SIZE, registry); + } + + public JAXBContextCache(int contextSize, int marshallerSize, int unmarshallerSize, ExtensionPointRegistry registry) { + this.registry = registry; + cache = new LRUCache<Object, JAXBContext>(contextSize); + mpool = new Pool<JAXBContext, Marshaller>(); + upool = new Pool<JAXBContext, Unmarshaller>(); + defaultContext = getDefaultJAXBContext(); + } + + private JAXBContext newJAXBContext(final Class<?>... classesToBeBound) throws JAXBException { + try { + return AccessController.doPrivileged(new PrivilegedExceptionAction<JAXBContext>() { + public JAXBContext run() throws JAXBException { + // Try to set up TCCL so that JAXBContext service discovery works in OSGi + ClassLoader tccl = + ClassLoaderContext.setContextClassLoader(JAXBContextCache.class.getClassLoader(), + registry.getServiceDiscovery(), + // The service provider of JAXBContext doesn't extend JAXBContext + // We should use the service name instead of the class + JAXBContext.class.getName(), + DatatypeFactory.class.getName()); + try { + JAXBContext context = JAXBContext.newInstance(classesToBeBound); + return context; + } finally { + if (tccl != null) { + Thread.currentThread().setContextClassLoader(tccl); + } + } + } + }); + } catch (PrivilegedActionException e) { + throw (JAXBException)e.getException(); + } + } + + + public JAXBContext getDefaultJAXBContext() { + try { + return newJAXBContext(); + } 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; + } + + public Marshaller getMarshaller(JAXBContext context) throws JAXBException { + Marshaller marshaller = mpool.get(context); + if (marshaller == null) { + marshaller = context.createMarshaller(); + } + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + return marshaller; + } + + public void releaseJAXBMarshaller(JAXBContext context, Marshaller marshaller) { + if (marshaller != null) { + marshaller.setAttachmentMarshaller(null); + mpool.put(context, marshaller); + // No point unsetting marshaller's JAXB_FRAGMENT property, since we'll just reset it when + // doing the next get. + } + } + + public Unmarshaller getUnmarshaller(JAXBContext context) throws JAXBException { + Unmarshaller unmarshaller = upool.get(context); + if (unmarshaller == null) { + unmarshaller = context.createUnmarshaller(); + } + return unmarshaller; + } + + public void releaseJAXBUnmarshaller(JAXBContext context, Unmarshaller unmarshaller) { + if (unmarshaller != null) { + unmarshaller.setAttachmentUnmarshaller(null); + upool.put(context, unmarshaller); + } + } + + public LRUCache<Object, JAXBContext> getCache() { + return cache; + } + + public JAXBContext getJAXBContext(Class<?> cls) throws JAXBException { + if (BUILTIN_CLASSES_SET.contains(cls)) { + return defaultContext; + } + return getJAXBContext(new Class<?>[] {cls}); + } + + 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); + } + + classSet = getJAXBClasses(classSet); + + if(classSet.isEmpty()) { + return defaultContext; + } + + synchronized (cache) { + JAXBContext context = cache.get(classSet); + if (context != null) { + return context; + } + context = newJAXBContext(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(); + } + */ + } + + // + // This inner class is copied in its entirety from the Axis2 utility class, + // org.apache.axis2.jaxws.message.databinding.JAXBUtils. We could look into extending but it's such a basic data structure + // without other dependencies so we might be better off copying it and avoiding a new + // Axis2 dependency here. + // + + /** + * Pool a list of items for a specific key + * + * @param <K> Key + * @param <V> Pooled object + */ + private static class Pool<K,V> { + private SoftReference<Map<K,List<V>>> softMap = + new SoftReference<Map<K,List<V>>>( + new ConcurrentHashMap<K, List<V>>()); + + // The maps are freed up when a LOAD FACTOR is hit + private static final int MAX_LIST_FACTOR = 50; + private static final int MAX_LOAD_FACTOR = 32; // Maximum number of JAXBContext to store + + /** + * @param key + * @return removed item from pool or null. + */ + public V get(K key) { + List<V> values = getValues(key); + synchronized (values) { + if (values.size()>0) { + V v = values.remove(values.size()-1); + return v; + + } + } + return null; + } + + /** + * Add item back to pool + * @param key + * @param value + */ + public void put(K key, V value) { + adjustSize(); + List<V> values = getValues(key); + synchronized (values) { + if (values.size() < MAX_LIST_FACTOR) { + values.add(value); + } + } + } + + /** + * Get or create a list of the values for the key + * @param key + * @return list of values. + */ + private List<V> getValues(K key) { + Map<K,List<V>> map = softMap.get(); + List<V> values = null; + if (map != null) { + values = map.get(key); + if(values !=null) { + return values; + } + } + synchronized (this) { + if (map != null) { + values = map.get(key); + } + if (values == null) { + if (map == null) { + map = new ConcurrentHashMap<K, List<V>>(); + softMap = + new SoftReference<Map<K,List<V>>>(map); + } + values = new ArrayList<V>(); + map.put(key, values); + + } + return values; + } + } + + /** + * AdjustSize + * When the number of keys exceeds the maximum load, half + * of the entries are deleted. + * + * The assumption is that the JAXBContexts, UnMarshallers, Marshallers, etc. require + * a large footprint. + */ + private void adjustSize() { + Map<K,List<V>> map = softMap.get(); + if (map != null && map.size() > MAX_LOAD_FACTOR) { + // Remove every other Entry in the map. + Iterator it = map.entrySet().iterator(); + boolean removeIt = false; + while (it.hasNext()) { + it.next(); + if (removeIt) { + it.remove(); + } + removeIt = !removeIt; + } + } + } + public void removeCtx(K key){ + Map<K,List<V>> map = softMap.get(); + if (map !=null && key !=null){ + map.remove(key); + } + } + } + + /** + * Find the JAXB classes (looking into packages) to be bound + * @param classes A collection of classes + * @return A set of classes that include the ObjectFactory and indexed JAXB classes + * @throws JAXBException + */ + private static Set<Class<?>> getJAXBClasses(Collection<Class<?>> classes) throws JAXBException { + Set<Class<?>> classSet = new HashSet<Class<?>>(); + // Index the packages + Map<Package, ClassLoader> pkgs = getPackages(classes); + Set<Package> nonJAXBPackages = new HashSet<Package>(); + for (Map.Entry<Package, ClassLoader> p : pkgs.entrySet()) { + Package pkg = p.getKey(); + if (pkg == null) { + continue; + } + Set<Class<?>> set = getJAXBClasses(pkg.getName(), p.getValue()); + if (set.isEmpty()) { + // No JAXB package + nonJAXBPackages.add(pkg); + } else { + // Add JAXB ObjectFactory and indexed classes + classSet.addAll(set); + } + } + // Adding classes that are not part of JAXB packages + for (Class<?> cls : classes) { + + Package pkg = getPackage(cls); + if (pkg == null || nonJAXBPackages.contains(pkg)) { + classSet.add(cls); + } else { + // TUSCANY-3162: Test if a class is generated by JAXB + // There might be the case that non-JAXB classes are in the same package as the JAXB classes + if (!cls.isAnnotationPresent(XmlType.class) + && !cls.isAnnotationPresent(XmlEnum.class) + && !cls.isAnnotationPresent(XmlSeeAlso.class) + && !cls.isAnnotationPresent(XmlRootElement.class) + && !cls.isAnnotationPresent(XmlTransient.class)) { + classSet.add(cls); + } + } + } + return classSet; + } + + /** + * Get the package for a class, taking array into account + * @param cls + * @return + */ + private static Package getPackage(Class<?> cls) { + Class<?> type = cls; + while (type.isArray()) { + type = type.getComponentType(); + } + return type.getPackage(); + } + + /** + * Get a map of packages + * @param classes + * @return + */ + private static Map<Package, ClassLoader> getPackages(Collection<Class<?>> classes) { + Map<Package, ClassLoader> pkgs = new HashMap<Package, ClassLoader>(); + for (Class<?> cls : classes) { + Package pkg = getPackage(cls); + if (pkg != null) { + final Class fcls = cls; + ClassLoader cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + ClassLoader cl = fcls.getClassLoader(); + return cl; + } + }); + pkgs.put(pkg, cl); + } + } + return pkgs; + } + + /** + * Find ObjectFactory and indexed JAXB classes for the package + * @param pkg + * @param classLoader + * @return + * @throws JAXBException + */ + private static Set<Class<?>> getJAXBClasses(String pkg, ClassLoader classLoader) throws JAXBException { + Set<Class<?>> classes = new HashSet<Class<?>>(); + List<Class<?>> indexedClasses; + + // look for ObjectFactory and load it + final Class<?> o; + try { + o = forName(pkg + ".ObjectFactory", false, classLoader); + classes.add(o); + } catch (ClassNotFoundException e) { + // not necessarily an error + } + + // look for jaxb.index and load the list of classes + try { + indexedClasses = loadIndexedClasses(pkg, classLoader); + } catch (IOException e) { + throw new JAXBException(e); + } + if (indexedClasses != null) { + classes.addAll(indexedClasses); + } + + return classes; + } + + /** + * Look for jaxb.index file in the specified package and load it's contents + * + * @param pkg package name to search in + * @param classLoader ClassLoader to search in + * @return a List of Class objects to load, null if there weren't any + * @throws IOException if there is an error reading the index file + * @throws JAXBException if there are any errors in the index file + */ + private static List<Class<?>> loadIndexedClasses(String pkg, ClassLoader classLoader) throws IOException, + JAXBException { + if (classLoader == null) { + return null; + } + final String resource = pkg.replace('.', '/') + "/jaxb.index"; + final InputStream resourceAsStream = classLoader.getResourceAsStream(resource); + + if (resourceAsStream == null) { + return null; + } + + BufferedReader in = new BufferedReader(new InputStreamReader(resourceAsStream, "UTF-8")); + try { + List<Class<?>> classes = new ArrayList<Class<?>>(); + String className = in.readLine(); + while (className != null) { + className = className.trim(); + if (className.startsWith("#") || (className.length() == 0)) { + className = in.readLine(); + continue; + } + + try { + classes.add(forName(pkg + '.' + className, false, classLoader)); + } catch (ClassNotFoundException e) { + throw new JAXBException(e); + } + + className = in.readLine(); + } + return classes; + } finally { + in.close(); + } + } + + public void removeJAXBContextFromPools(JAXBContext ctx){ + if (mpool != null && ctx != null){ + mpool.removeCtx(ctx); + } + if (upool != null && ctx !=null){ + upool.removeCtx(ctx); + } + } + + /** + * Removes all the cached information relating to a contribution. The + * contribution is identified by the contribution classloader passed in + * as a parameter. This is used when a contribution is removed from + * the runtime. + * + * @param contributionClassloader + */ + public void removeJAXBContextForContribution(ClassLoader contributionClassloader){ + if (cache != null){ + try { + synchronized(cache) { + Set<Object> objSet = cache.keySet(); + List<Object> toRemove = new ArrayList<Object>(); + Iterator<Object> i = objSet.iterator(); + while(i.hasNext()) { + Object obj = i.next(); + if (obj instanceof Set){ + Set<Class> innerSet = (Set<Class>)obj; + Iterator<Class> j = innerSet.iterator(); + loop: + while(j.hasNext()) { + Class cls = j.next(); + ClassLoader cl = cls.getClassLoader(); + while (cl != null){ + if (cl == contributionClassloader){ + toRemove.add(obj); + break loop; + } + // take account of generated classes + cl = cl.getParent(); + } + } + } + } + for (Object obj : toRemove){ + JAXBContext ctx = cache.get(obj); + removeJAXBContextFromPools(ctx); + cache.remove(obj); + } + } + } catch(Exception e) { + throw new ServiceRuntimeException(e); + } + } + } + + +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java new file mode 100644 index 0000000000..44c172a07a --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBContextHelper.java @@ -0,0 +1,600 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tuscany.sca.databinding.jaxb; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.JAXBIntrospector; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlEnum; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSchema; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; +import javax.xml.namespace.QName; + +import org.apache.tuscany.sca.common.java.collection.LRUCache; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.UtilityExtensionPoint; +import org.apache.tuscany.sca.databinding.SimpleTypeMapper; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.apache.tuscany.sca.databinding.impl.SimpleTypeMapperImpl; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.Interface; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.apache.tuscany.sca.interfacedef.util.WrapperInfo; +import org.apache.tuscany.sca.interfacedef.util.XMLType; + +/** + * + * @version $Rev$ $Date$ + */ + +public final class JAXBContextHelper { + private final JAXBContextCache cache; + private final static SimpleTypeMapper SIMPLE_TYPE_MAPPER = new SimpleTypeMapperImpl(); + + public JAXBContextHelper(ExtensionPointRegistry registry) { + cache = new JAXBContextCache(registry); + } + + public static JAXBContextHelper getInstance(ExtensionPointRegistry registry) { + UtilityExtensionPoint utilityExtensionPoint = registry.getExtensionPoint(UtilityExtensionPoint.class); + return utilityExtensionPoint.getUtility(JAXBContextHelper.class); + } + + /** + * Create a JAXBContext for a given class + * @param cls + * @return + * @throws JAXBException + */ + public JAXBContext createJAXBContext(Class<?> cls) throws JAXBException { + return cache.getJAXBContext(cls); + } + + public JAXBContext createJAXBContext(TransformationContext tContext, boolean source) throws JAXBException { + if (tContext == null) + throw new TransformationException("JAXB context is not set for the transformation."); + + // TODO: [rfeng] Need to figure out what's the best granularity to create the JAXBContext + // per interface, operation or parameter + Operation op = source ? tContext.getSourceOperation() : tContext.getTargetOperation(); + if (op != null) { + synchronized (op) { + JAXBContext context = op.getInputType().getMetaData(JAXBContext.class); + if (context == null) { + context = createJAXBContext(getDataTypes(op, true)); + op.getInputType().setMetaData(JAXBContext.class, context); + } + return context; + } + } + + // For property transformation, the operation can be null + DataType<?> dataType = source ? tContext.getSourceDataType() : tContext.getTargetDataType(); + return createJAXBContext(dataType); + + } + + private static Class<?>[] getSeeAlso(Class<?> interfaze) { + if (interfaze == null) { + return null; + } + XmlSeeAlso seeAlso = interfaze.getAnnotation(XmlSeeAlso.class); + if (seeAlso == null) { + return null; + } else { + return seeAlso.value(); + } + } + + public JAXBContext createJAXBContext(DataType dataType) throws JAXBException { + return createJAXBContext(findClasses(dataType)); + } + + public Unmarshaller getUnmarshaller(JAXBContext context) throws JAXBException { + return cache.getUnmarshaller(context); + } + + public void releaseJAXBUnmarshaller(JAXBContext context, Unmarshaller unmarshaller) { + cache.releaseJAXBUnmarshaller(context, unmarshaller); + } + + public Marshaller getMarshaller(JAXBContext context) throws JAXBException { + return cache.getMarshaller(context); + } + + public void releaseJAXBMarshaller(JAXBContext context, Marshaller marshaller) { + cache.releaseJAXBMarshaller(context, marshaller); + } + + @SuppressWarnings("unchecked") + public static Object createJAXBElement(JAXBContext context, DataType dataType, Object value) { + Class<?> type = dataType == null ? value.getClass() : dataType.getPhysical(); + type = getValueType(type); + QName name = JAXBDataBinding.ROOT_ELEMENT; + if (context != null) { + Object logical = dataType == null ? null : dataType.getLogical(); + if (logical instanceof XMLType) { + XMLType xmlType = (XMLType)logical; + if (xmlType.isElement()) { + name = xmlType.getElementName(); + } else { + /** + * Set the declared type to Object.class so that xsi:type + * will be produced + */ + type = Object.class; + } + } else { + type = Object.class; + } + } + + JAXBIntrospector introspector = context.createJAXBIntrospector(); + Object element = null; + if (value != null && introspector.isElement(value)) { + // NOTE: [rfeng] We cannot wrap an element in a JAXBElement + element = value; + } + if (element == null) { + // For local elements, we still have to produce xsi:type + element = new JAXBElement(name, Object.class, value); + } + return element; + } + + @SuppressWarnings("unchecked") + public static Object createReturnValue(JAXBContext context, DataType dataType, Object value) { + Class<?> cls = getJavaType(dataType); + if (cls == JAXBElement.class) { + return createJAXBElement(context, dataType, value); + } else { + if (value instanceof JAXBElement) { + Object returnValue = ((JAXBElement)value).getValue(); + + if (returnValue == null) { + // TUSCANY-3530 + // something went wrong in the transformation that + // generated the JAXBElement. Have seen this when trying + // to convert a value to a simple type with an incompatible + // value. + throw new TransformationException("Null returned when trying to convert value to: " + cls.getName()); + } + return returnValue; + } else { + return value; + } + } + } + + /** + * Create a JAXContext for an array of classes + * @param classes + * @return + * @throws JAXBException + */ + public JAXBContext createJAXBContext(Class<?>[] classes) throws JAXBException { + return cache.getJAXBContext(classes); + } + + public JAXBContext createJAXBContext(Set<Class<?>> classes) throws JAXBException { + return cache.getJAXBContext(classes); + } + + /** + * Create a JAXBContext for a given java interface + * @param intf + * @return + * @throws JAXBException + */ + public JAXBContext createJAXBContext(Interface intf, boolean useWrapper) throws JAXBException { + synchronized (cache) { + LRUCache<Object, JAXBContext> map = cache.getCache(); + Integer key = new Integer(System.identityHashCode(intf)); + JAXBContext context = map.get(key); + if (context != null) { + return context; + } + List<DataType> dataTypes = getDataTypes(intf, useWrapper); + context = createJAXBContext(dataTypes); + map.put(key, context); + return context; + } + } + + public JAXBContext createJAXBContext(List<DataType> dataTypes) throws JAXBException { + JAXBContext context; + Set<Class<?>> classes = new HashSet<Class<?>>(); + Set<Type> visited = new HashSet<Type>(); + for (DataType d : dataTypes) { + findClasses(d, classes, visited); + } + + context = createJAXBContext(classes); + return context; + } + + private static Set<Class<?>> findClasses(DataType d) { + Set<Class<?>> classes = new HashSet<Class<?>>(); + Set<Type> visited = new HashSet<Type>(); + findClasses(d, classes, visited); + return classes; + } + + private static void findClasses(DataType d, Set<Class<?>> classes, Set<Type> visited) { + if (d == null) { + return; + } + String db = d.getDataBinding(); + if (JAXBDataBinding.NAME.equals(db) || (db != null && db.startsWith("java:")) || db == null) { + if (!d.getPhysical().isInterface() && !JAXBElement.class.isAssignableFrom(d.getPhysical())) { + classes.add(d.getPhysical()); + } else { + classes.addAll(findJAXBClassesByInterface(d.getPhysical())); + } + } + if (d.getPhysical() != d.getGenericType()) { + findClasses(d.getGenericType(), classes, visited); + } + } + + /** + * Find referenced classes in the generic type + * @param type + * @param classSet + * @param visited + */ + private static void findClasses(Type type, Set<Class<?>> classSet, Set<Type> visited) { + if (visited.contains(type) || type == null) { + return; + } + visited.add(type); + if (type instanceof Class) { + Class<?> cls = (Class<?>)type; + if (!cls.isInterface()) { + classSet.add(cls); + } else { + classSet.addAll(findJAXBClassesByInterface(cls)); + } + return; + } else if (type instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType)type; + findClasses(pType.getRawType(), classSet, visited); + for (Type t : pType.getActualTypeArguments()) { + findClasses(t, classSet, visited); + } + } else if (type instanceof TypeVariable) { + TypeVariable<?> tv = (TypeVariable<?>)type; + for (Type t : tv.getBounds()) { + findClasses(t, classSet, visited); + } + } else if (type instanceof GenericArrayType) { + GenericArrayType gType = (GenericArrayType)type; + findClasses(gType.getGenericComponentType(), classSet, visited); + } else if (type instanceof WildcardType) { + WildcardType wType = (WildcardType)type; + for (Type t : wType.getLowerBounds()) { + findClasses(t, classSet, visited); + } + for (Type t : wType.getUpperBounds()) { + findClasses(t, classSet, visited); + } + } + } + + /** + * Introspect the @XmlJavaTypeAdapter and @XmlSeeAlso for an interface + * @param cls + * @return + */ + private static Set<Class<?>> findJAXBClassesByInterface(Class<?> cls) { + if (!cls.isInterface()) { + return Collections.emptySet(); + } + Set<Class<?>> jaxbClasses = new HashSet<Class<?>>(); + Class<?> valueType = getValueType(cls); + if (valueType != null) { + jaxbClasses.add(valueType); + } + + Class<?>[] others = getSeeAlso(cls); + if (others != null) { + jaxbClasses.addAll(Arrays.asList(others)); + } + + Package pkg = cls.getPackage(); + if (pkg != null) { + XmlJavaTypeAdapters adapters = pkg.getAnnotation(XmlJavaTypeAdapters.class); + if (adapters != null) { + for (XmlJavaTypeAdapter a : adapters.value()) { + jaxbClasses.add(getValueType(a)); + } + } + } + return jaxbClasses; + } + + public static Class<?> getValueType(Class<?> cls) { + if (cls == null) { + return null; + } + if (cls.isInterface()) { + XmlJavaTypeAdapter adapter = cls.getAnnotation(XmlJavaTypeAdapter.class); + return getValueType(adapter); + } else { + return cls; + } + } + + private static Class<?> erase(Type type) { + if (type instanceof Class) { + return (Class<?>)type; + } + if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType)type; + return (Class<?>)pt.getRawType(); + } + if (type instanceof TypeVariable) { + TypeVariable tv = (TypeVariable)type; + Type[] bounds = tv.getBounds(); + return (0 < bounds.length) ? erase(bounds[0]) : Object.class; + } + if (type instanceof WildcardType) { + WildcardType wt = (WildcardType)type; + Type[] bounds = wt.getUpperBounds(); + return (0 < bounds.length) ? erase(bounds[0]) : Object.class; + } + if (type instanceof GenericArrayType) { + GenericArrayType gat = (GenericArrayType)type; + return Array.newInstance(erase(gat.getGenericComponentType()), 0).getClass(); + } + throw new IllegalArgumentException("Unknown Type kind: " + type.getClass()); + } + + public static Class<?> getValueType(XmlJavaTypeAdapter adapter) { + if (adapter != null) { + Class<? extends XmlAdapter> adapterClass = adapter.value(); + if (adapterClass != null) { + Type superClass = adapterClass.getGenericSuperclass(); + while (superClass instanceof ParameterizedType && XmlAdapter.class != ((ParameterizedType)superClass) + .getRawType()) { + superClass = erase(superClass).getGenericSuperclass(); + } + return erase(((ParameterizedType)superClass).getActualTypeArguments()[0]); + } + } + return null; + } + + public JAXBContext createJAXBContext(Interface intf) throws JAXBException { + return createJAXBContext(intf, true); + } + + /** + * @param intf + * @param useWrapper Use wrapper classes? + * @return + */ + private static List<DataType> getDataTypes(Interface intf, boolean useWrapper) { + List<DataType> dataTypes = new ArrayList<DataType>(); + for (Operation op : intf.getOperations()) { + getDataTypes(dataTypes, op, useWrapper); + } + return dataTypes; + } + + private static List<DataType> getDataTypes(Operation op, boolean useWrapper) { + List<DataType> dataTypes = new ArrayList<DataType>(); + getDataTypes(dataTypes, op, useWrapper); + // Adding classes referenced by @XmlSeeAlso in the java interface + Interface interface1 = op.getInterface(); + if (interface1 instanceof JavaInterface) { + JavaInterface javaInterface = (JavaInterface)interface1; + Class<?>[] seeAlso = getSeeAlso(javaInterface.getJavaClass()); + if (seeAlso != null) { + for (Class<?> cls : seeAlso) { + dataTypes.add(new DataTypeImpl<XMLType>(JAXBDataBinding.NAME, cls, XMLType.UNKNOWN)); + } + } + seeAlso = getSeeAlso(javaInterface.getCallbackClass()); + if (seeAlso != null) { + for (Class<?> cls : seeAlso) { + dataTypes.add(new DataTypeImpl<XMLType>(JAXBDataBinding.NAME, cls, XMLType.UNKNOWN)); + } + } + } + return dataTypes; + } + + private static void getDataTypes(List<DataType> dataTypes, Operation op, boolean useWrapper) { + WrapperInfo inputWrapper = op.getInputWrapper(); + WrapperInfo outputWrapper = op.getOutputWrapper(); + + if (useWrapper && (inputWrapper != null)) { + DataType dt1 = inputWrapper.getWrapperType(); + if (dt1 != null) { + dataTypes.add(dt1); + } + } + if (useWrapper && (outputWrapper != null)) { + DataType dt2 = outputWrapper.getWrapperType(); + if (dt2 != null) { + dataTypes.add(dt2); + } + } + // FIXME: [rfeng] We may need to find the referenced classes in the child types + // else + { + for (DataType dt1 : op.getInputType().getLogical()) { + dataTypes.add(dt1); + } + for (DataType dt2 : op.getOutputType().getLogical()) { + dataTypes.add(dt2); + } + } + for (DataType<DataType> dt3 : op.getFaultTypes()) { + DataType dt4 = dt3.getLogical(); + if (dt4 != null) { + dataTypes.add(dt4); + } + } + } + + public static Class<?> getJavaType(DataType<?> dataType) { + if (dataType == null) { + return null; + } + Class type = dataType.getPhysical(); + if (JAXBElement.class.isAssignableFrom(type)) { + Type generic = dataType.getGenericType(); + type = Object.class; + } + if (type == Object.class && dataType.getLogical() instanceof XMLType) { + XMLType xType = (XMLType)dataType.getLogical(); + Class javaType = SIMPLE_TYPE_MAPPER.getJavaType(xType.getTypeName()); + if (javaType != null) { + type = javaType; + } + } + return type; + } + + public static XMLType getXmlTypeName(Class<?> javaType) { + if (javaType.isInterface()) { + // JAXB doesn't support interfaces + return null; + } + String namespace = null; + String name = null; + Package pkg = javaType.getPackage(); + if (pkg != null) { + XmlSchema schema = pkg.getAnnotation(XmlSchema.class); + if (schema != null) { + namespace = schema.namespace(); + } + } + + QName elementQName = null; + QName typeQName = null; + XmlRootElement rootElement = javaType.getAnnotation(XmlRootElement.class); + if (rootElement != null) { + String elementName = rootElement.name(); + String elementNamespace = rootElement.namespace(); + if (elementNamespace.equals("##default")) { + elementNamespace = namespace; + } + if (elementName.equals("##default")) { + elementName = jaxbDecapitalize(javaType.getSimpleName()); + } + elementQName = new QName(elementNamespace, elementName); + } + XmlType type = javaType.getAnnotation(XmlType.class); + if (type != null) { + String typeNamespace = type.namespace(); + String typeName = type.name(); + + if (typeNamespace.equals("##default")) { + // namespace is from the package + typeNamespace = namespace; + } + + if (typeName.equals("##default")) { + typeName = jaxbDecapitalize(javaType.getSimpleName()); + } + typeQName = new QName(typeNamespace, typeName); + } else { + XmlEnum xmlEnum = javaType.getAnnotation(XmlEnum.class); + // POJO can have the @XmlSchema on the package-info too + if (xmlEnum != null || namespace != null) { + name = jaxbDecapitalize(javaType.getSimpleName()); + typeQName = new QName(namespace, name); + } + } + if (elementQName == null && typeQName == null) { + return null; + } + return new XMLType(elementQName, typeQName); + } + + /** + * The JAXB RI doesn't implement the decapitalization algorithm in the + * JAXB spec. See Sun bug 6505643 for details. This means that instead + * of calling java.beans.Introspector.decapitalize() as the JAXB spec says, + * Tuscany needs to mimic the incorrect JAXB RI algorithm. + */ + public static String jaxbDecapitalize(String name) { + // find first lower case char in name + int lower = name.length(); + for (int i = 0; i < name.length(); i++) { + if (Character.isLowerCase(name.charAt(i))) { + lower = i; + break; + } + } + + int decap; + if (name.length() == 0) { + decap = 0; // empty string: nothing to do + } else if (lower == 0) { + decap = 0; // first char is lower case: nothing to do + } else if (lower == 1) { + decap = 1; // one upper followed by lower: decapitalize 1 char + } else if (lower < name.length()) { + decap = lower - 1; // n uppers followed by at least one lower: decapitalize n-1 chars + } else { + decap = name.length(); // all upper case: decapitalize all chars + } + + return name.substring(0, decap).toLowerCase() + name.substring(decap); + } + + public void removeJAXBContextForContribution(ClassLoader contributionClassloader){ + cache.removeJAXBContextForContribution(contributionClassloader); + } + + /** + * Just for testing that the cache is being removed on stop + */ + public JAXBContextCache getJAXBContextCache(){ + return cache; + } +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBDataBinding.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBDataBinding.java new file mode 100644 index 0000000000..1eb78caeb1 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBDataBinding.java @@ -0,0 +1,160 @@ +/* + * 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 static org.apache.tuscany.sca.databinding.jaxb.JAXBContextHelper.getValueType; + +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.common.xml.dom.DOMHelper; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseDataBinding; +import org.apache.tuscany.sca.databinding.WrapperHandler; +import org.apache.tuscany.sca.databinding.XMLTypeHelper; +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 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; + private DOMHelper domHelper; + private JAXBContextHelper contextHelper; + + public JAXBDataBinding(ExtensionPointRegistry registry) { + super(NAME, JAXBElement.class); + this.wrapperHandler = new JAXBWrapperHandler(); + this.xmlTypeHelper = new JAXBTypeHelper(registry); + this.domHelper = DOMHelper.getInstance(registry); + contextHelper = JAXBContextHelper.getInstance(registry); + } + + @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; + } + + // If DataType is already an XMLType it might have an element name that we wish to preserve + // but check that we're not overwriting the UNKNOWN type + Object logical = dataType.getLogical(); + if (logical instanceof XMLType && + logical != XMLType.UNKNOWN) { + ((XMLType)logical).setTypeName(xmlType.getTypeName()); + } else { + dataType.setLogical(xmlType); + } + + dataType.setDataBinding(NAME); + return true; + } + + @SuppressWarnings("unchecked") + @Override + public Object copy(Object arg, + DataType sourceDataType, + DataType targetDataType, + Operation sourceOperation, + Operation targetOperation) { + try { + boolean isElement = false; + if (sourceDataType == null) { + Class cls = arg.getClass(); + if (arg instanceof JAXBElement) { + isElement = true; + cls = ((JAXBElement)arg).getDeclaredType(); + } + sourceDataType = new DataTypeImpl<XMLType>(NAME, cls, XMLType.UNKNOWN); + } + JAXBContext context = contextHelper.createJAXBContext(sourceDataType); + arg = JAXBContextHelper.createJAXBElement(context, sourceDataType, arg); + Document doc = domHelper.newDocument(); + context.createMarshaller().marshal(arg, doc); + + Object value; + if (targetDataType != null && targetDataType.getPhysical() != sourceDataType.getPhysical()) { + JAXBContext targetContext = contextHelper.createJAXBContext(targetDataType); + value = targetContext.createUnmarshaller().unmarshal(doc, getValueType(targetDataType.getPhysical())); + } else { + value = context.createUnmarshaller().unmarshal(doc, getValueType(sourceDataType.getPhysical())); + } + + if (isElement && value instanceof JAXBElement) { + return value; + } + return JAXBContextHelper.createReturnValue(context, sourceDataType, 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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBPropertyDescriptor.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBPropertyDescriptor.java new file mode 100644 index 0000000000..982d3c8aa3 --- /dev/null +++ b/sca-java-2.x/branches/2.0/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.oasisopen.sca.ServiceRuntimeException; + +/** + * A PropertyDescriptor provides access 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 semantics. + * <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 Atomic 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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBTypeHelper.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBTypeHelper.java new file mode 100644 index 0000000000..84529752de --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBTypeHelper.java @@ -0,0 +1,244 @@ +/* + * 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.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.core.ExtensionPointRegistry; +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.oasisopen.sca.ServiceRuntimeException; +import org.w3c.dom.Document; + +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 JAXBContextHelper contextHelper; + + public JAXBTypeHelper(ExtensionPointRegistry registry) { + super(); + contextHelper = JAXBContextHelper.getInstance(registry); + } + + 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(JAXBContextHelper.jaxbDecapitalize(javaType.getSimpleName())); + } + return new TypeInfo(xmlType, false, null); + } + } + + /* + 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 = contextHelper.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 = contextHelper.createJAXBContext(dataTypes); + List<XSDefinition> definitions = new ArrayList<XSDefinition>(); + generateSchemas(definitions, factory, context); + return definitions; + } catch (Throwable e) { + throw new ServiceRuntimeException(e); + } + } + +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperException.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperException.java new file mode 100644 index 0000000000..7473a8e56e --- /dev/null +++ b/sca-java-2.x/branches/2.0/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.oasisopen.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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHandler.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHandler.java new file mode 100644 index 0000000000..9b0c0dc8dc --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHandler.java @@ -0,0 +1,167 @@ +/* + * 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 inputWrapperInfo = operation.getInputWrapper(); + WrapperInfo outputWrapperInfo = operation.getOutputWrapper(); + + ElementInfo element = input ? inputWrapperInfo.getWrapperElement() : outputWrapperInfo.getWrapperElement(); + final Class<?> wrapperClass = input ? inputWrapperInfo.getWrapperClass() : outputWrapperInfo.getWrapperClass(); + + 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) { + WrapperInfo inputWrapperInfo = operation.getInputWrapper(); + WrapperInfo outputWrapperInfo = operation.getOutputWrapper(); + + List<ElementInfo> childElements = + input ? inputWrapperInfo.getChildElements() : outputWrapperInfo.getChildElements(); + + 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) { + WrapperInfo inputWrapperInfo = operation.getInputWrapper(); + WrapperInfo outputWrapperInfo = operation.getOutputWrapper(); + + List<ElementInfo> childElements = input? inputWrapperInfo.getChildElements(): + outputWrapperInfo.getChildElements(); + + 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 inputWrapperInfo = operation.getInputWrapper(); + WrapperInfo outputWrapperInfo = operation.getOutputWrapper(); + + DataType dt = input ? inputWrapperInfo.getWrapperType() : outputWrapperInfo.getWrapperType(); + return dt; + } + + /** + * @see org.apache.tuscany.sca.databinding.WrapperHandler#isInstance(java.lang.Object, Operation, boolean) + */ + public boolean isInstance(Object wrapper, Operation operation, boolean input) { + WrapperInfo inputWrapperInfo = operation.getInputWrapper(); + WrapperInfo outputWrapperInfo = operation.getOutputWrapper(); + + Class<?> wrapperClass = + input ? inputWrapperInfo.getWrapperClass() : outputWrapperInfo.getWrapperClass(); + + return wrapperClass == null ? false : wrapperClass.isInstance(wrapper); + } +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHelper.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/JAXBWrapperHelper.java new file mode 100644 index 0000000000..5f90aa4d7a --- /dev/null +++ b/sca-java-2.x/branches/2.0/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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Node2JAXB.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Node2JAXB.java new file mode 100644 index 0000000000..8c66195d29 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Node2JAXB.java @@ -0,0 +1,113 @@ +/* + * 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.bind.ValidationEvent; +import javax.xml.bind.util.ValidationEventCollector; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; +import org.w3c.dom.Node; + +/** + * + * @version $Rev$ $Date$ + */ +public class Node2JAXB extends BaseTransformer<Node, Object> implements PullTransformer<Node, Object> { + private JAXBContextHelper contextHelper; + + public Node2JAXB(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + + public Object transform(Node source, TransformationContext context) { + ValidationEventCollector validationEventCollector = new ValidationEventCollector(); + Object response = null; + if (source == null) + return null; + try { + JAXBContext jaxbContext = contextHelper.createJAXBContext(context, false); + Object result; + // TUSCANY-3791 + synchronized(source){ + /* some debug code + System.setProperty("jaxb.debug", "true"); + unmarshaller.setListener(new DebugListener()); + */ + Unmarshaller unmarshaller = contextHelper.getUnmarshaller(jaxbContext); + try { + validationEventCollector.reset(); + unmarshaller.setEventHandler(validationEventCollector); + result = unmarshaller.unmarshal(source, JAXBContextHelper.getJavaType(context.getTargetDataType())); + } finally { + contextHelper.releaseJAXBUnmarshaller(jaxbContext, unmarshaller); + } + } + response = JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } catch (Exception e) { + throw new TransformationException(e); + } + + if (validationEventCollector.hasEvents()){ + String validationErrors = ""; + for(ValidationEvent event : validationEventCollector.getEvents()){ + validationErrors += "Event: " + event.getMessage() + " "; + } + throw new TransformationException(validationErrors); + } + return response; + } + + @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; + } + + /* some debug code + class DebugListener extends Unmarshaller.Listener { + public void beforeUnmarshal(Object target, Object parent) { + + } + + public void afterUnmarshal(Object target, Object parent) { + + } + } + */ +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Reader2JAXB.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Reader2JAXB.java new file mode 100644 index 0000000000..59495d4234 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/Reader2JAXB.java @@ -0,0 +1,84 @@ +/* + * 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.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; + +/** + * + * @version $Rev$ $Date$ + */ +public class Reader2JAXB extends BaseTransformer<Reader, Object> implements + PullTransformer<Reader, Object> { + private JAXBContextHelper contextHelper; + + public Reader2JAXB(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + public Object transform(final Reader source, final TransformationContext context) { + if (source == null) { + return null; + } + try { + StreamSource streamSource = new StreamSource(source); + + JAXBContext jaxbContext = contextHelper.createJAXBContext(context, false); + Unmarshaller unmarshaller = contextHelper.getUnmarshaller(jaxbContext); + try { + Object result = unmarshaller.unmarshal(streamSource, JAXBContextHelper.getJavaType(context.getTargetDataType())); + return JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } finally { + contextHelper.releaseJAXBUnmarshaller(jaxbContext, unmarshaller); + } + } 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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/String2JAXB.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/String2JAXB.java new file mode 100644 index 0000000000..82259d8618 --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/String2JAXB.java @@ -0,0 +1,91 @@ +/* + * 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.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +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.xml.XMLStringDataBinding; + +/** + * + * @version $Rev$ $Date$ + */ +public class String2JAXB extends BaseTransformer<String, Object> implements + PullTransformer<String, Object> { + private JAXBContextHelper contextHelper; + + public String2JAXB(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + + public Object transform(final String source, final TransformationContext context) { + if (source == null) { + return null; + } + try { + JAXBContext jaxbContext = contextHelper.createJAXBContext(context, false); + + StreamSource streamSource = new StreamSource(new StringReader(source)); + Unmarshaller unmarshaller = contextHelper.getUnmarshaller(jaxbContext); + try { + Object result = unmarshaller.unmarshal(streamSource, JAXBContextHelper.getJavaType(context.getTargetDataType())); + return JAXBContextHelper.createReturnValue(jaxbContext, context.getTargetDataType(), result); + } finally { + contextHelper.releaseJAXBUnmarshaller(jaxbContext, unmarshaller); + } + } 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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLAdapterExtensionPoint.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLAdapterExtensionPoint.java new file mode 100644 index 0000000000..5fa98b5ed1 --- /dev/null +++ b/sca-java-2.x/branches/2.0/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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLRootElementUtil.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLRootElementUtil.java new file mode 100644 index 0000000000..d177d53eda --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLRootElementUtil.java @@ -0,0 +1,299 @@ +/* + * 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 java.util.WeakHashMap; + +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 { + + /** + * TUSCANY-3167 + * Cache for property descriptors + */ + private static Map<Class<?>, Map<String, JAXBPropertyDescriptor>> PROPERTY_MAP = + new WeakHashMap<Class<?>, Map<String, JAXBPropertyDescriptor>>(); + + /** 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 synchronized static Map<String, JAXBPropertyDescriptor> createPropertyDescriptorMap(Class<?> jaxbClass) + throws NoSuchFieldException, IntrospectionException { + + Map<String, JAXBPropertyDescriptor> map = PROPERTY_MAP.get(jaxbClass); + if (map != null) { + return map; + } + + map = new HashMap<String, JAXBPropertyDescriptor>(); + PropertyDescriptor[] pds = Introspector.getBeanInfo(jaxbClass).getPropertyDescriptors(); + + // 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++; + } + } + PROPERTY_MAP.put(jaxbClass, map); + 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/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLStreamReader2JAXB.java b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLStreamReader2JAXB.java new file mode 100644 index 0000000000..4d400f963d --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/XMLStreamReader2JAXB.java @@ -0,0 +1,86 @@ +/* + * 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.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.databinding.BaseTransformer; +import org.apache.tuscany.sca.databinding.PullTransformer; +import org.apache.tuscany.sca.databinding.TransformationContext; +import org.apache.tuscany.sca.databinding.TransformationException; + +/** + * + * @version $Rev$ $Date$ + */ +public class XMLStreamReader2JAXB extends BaseTransformer<XMLStreamReader, Object> implements + PullTransformer<XMLStreamReader, Object> { + + private JAXBContextHelper contextHelper; + + public XMLStreamReader2JAXB(ExtensionPointRegistry registry) { + contextHelper = JAXBContextHelper.getInstance(registry); + } + + public Object transform(XMLStreamReader source, TransformationContext context) { + if (source == null) { + return null; + } + try { + JAXBContext jaxbContext = contextHelper.createJAXBContext(context, false); + Unmarshaller unmarshaller = contextHelper.getUnmarshaller(jaxbContext); + try { + // 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); + } finally { + contextHelper.releaseJAXBUnmarshaller(jaxbContext, unmarshaller); + } + + } 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; + } +} diff --git a/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/package.html b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/package.html new file mode 100644 index 0000000000..3dd869384a --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/databinding-jaxb/src/main/java/org/apache/tuscany/sca/databinding/jaxb/package.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- + * 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. +--> +<html> +<head> +</head> +<body> +Base Package for the JAXB databinding extension. + +Whilst this package and its subpackages are not currently deemed to represent extension developers SPI, this extension has a special relationship with binding-atom-runtime. binding-atom-runtime can be viewed as a specialization of binding.http. + +</body> +</html> |