You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by se...@apache.org on 2009/07/16 16:04:24 UTC

svn commit: r794680 - in /cxf/trunk: rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/ rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/ rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/ systests/src/test/java/or...

Author: sergeyb
Date: Thu Jul 16 14:04:24 2009
New Revision: 794680

URL: http://svn.apache.org/viewvc?rev=794680&view=rev
Log:
JAXRS : initial support for writing and reading explicit lists and arrays

Added:
    cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/CollectionsResource.java   (with props)
Modified:
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/AbstractJAXBProvider.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JSONProvider.java
    cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties
    cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JAXBElementProviderTest.java
    cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JSONProviderTest.java
    cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
    cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/AbstractJAXBProvider.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/AbstractJAXBProvider.java?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/AbstractJAXBProvider.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/AbstractJAXBProvider.java Thu Jul 16 14:04:24 2009
@@ -21,10 +21,14 @@
 
 import java.io.UnsupportedEncodingException;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
 import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.ResourceBundle;
+import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.logging.Logger;
 
@@ -41,14 +45,20 @@
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
 import javax.xml.bind.Unmarshaller;
+import javax.xml.bind.annotation.XmlAnyElement;
 import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.bind.annotation.adapters.XmlAdapter;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.namespace.QName;
 import javax.xml.validation.Schema;
 
 import org.apache.cxf.common.i18n.BundleUtils;
 import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.common.util.PackageUtils;
+import org.apache.cxf.common.util.ReflectionInvokationHandler;
+import org.apache.cxf.jaxb.JAXBBeanInfo;
+import org.apache.cxf.jaxb.JAXBContextProxy;
+import org.apache.cxf.jaxb.JAXBUtils;
 import org.apache.cxf.jaxrs.ext.MessageContext;
 import org.apache.cxf.jaxrs.utils.AnnotationUtils;
 import org.apache.cxf.jaxrs.utils.InjectionUtils;
@@ -60,22 +70,140 @@
     
     protected static final ResourceBundle BUNDLE = BundleUtils.getBundle(AbstractJAXBProvider.class);
     private static final Logger LOG = LogUtils.getL7dLogger(AbstractJAXBProvider.class);
+   
+    private static final String JAXB_DEFAULT_NAMESPACE = "##default";
+    private static final String JAXB_DEFAULT_NAME = "##default";
     
     private static final String CHARSET_PARAMETER = "charset";
     private static Map<String, JAXBContext> packageContexts = new WeakHashMap<String, JAXBContext>();
     private static Map<Class<?>, JAXBContext> classContexts = new WeakHashMap<Class<?>, JAXBContext>();
    
+    private static Set<Class<?>> collectionContextClasses = new HashSet<Class<?>>();
+    private static JAXBContext collectionContext; 
+    
     private MessageContext mc;
     private Schema schema;
+    private String collectionWrapperName;
+    private Map<String, String> collectionWrapperMap;
+    
+    public void setCollectionWrapperName(String wName) {
+        collectionWrapperName = wName;
+    }
+    
+    public void setCollectionWrapperMap(Map<String, String> map) {
+        collectionWrapperMap = map;
+    }
     
     protected void setContext(MessageContext context) {
         mc = context;
     }
     
     public boolean isWriteable(Class<?> type, Type genericType, Annotation[] anns, MediaType mt) {
+        
+        if (InjectionUtils.isSupportedCollectionOrArray(type)) {
+            type = InjectionUtils.getActualType(genericType);
+            if (type == null) {
+                return false;
+            }
+        }
+        
         return isSupported(type, genericType, anns);
     }
     
+    protected JAXBContext getCollectionContext(Class<?> type) throws JAXBException {
+        synchronized (collectionContextClasses) {
+            if (!collectionContextClasses.contains(type)) {
+                collectionContextClasses.add(CollectionWrapper.class);
+                collectionContextClasses.add(type);
+            }
+            collectionContext = JAXBContext.newInstance(collectionContextClasses.toArray(new Class[]{}));
+            return collectionContext;
+        }
+    }
+    
+    protected QName getCollectionWrapperQName(Class<?> cls, Type type, Object object, boolean pluralName)
+        throws Exception {
+        String name = getCollectionWrapperName(cls);
+        if (name == null) {
+            return getJaxbQName(cls, type, object, pluralName);
+        }
+            
+        int ind1 = name.indexOf('{');
+        if (ind1 != 0) {
+            return new QName(name);
+        }
+        
+        int ind2 = name.indexOf('}');
+        if (ind2 <= ind1 + 1 || ind2 >= name.length() - 1) {
+            return null;
+        }
+        String ns = name.substring(ind1 + 1, ind2);
+        String localName = name.substring(ind2 + 1);
+        return new QName(ns, localName);
+    }
+    
+    private String getCollectionWrapperName(Class<?> cls) {
+        if (collectionWrapperName != null) { 
+            return collectionWrapperName;
+        }
+        if (collectionWrapperMap != null) {
+            return collectionWrapperMap.get(cls.getName());
+        }
+        
+        return null;
+    }
+    
+    protected QName getJaxbQName(Class<?> cls, Type type, Object object, boolean pluralName) 
+        throws Exception {
+        //try the easy way first
+        XmlRootElement root = cls.getAnnotation(XmlRootElement.class);
+        QName qname = null;
+        if (root != null) {
+            String namespace = getNamespace(root.namespace());
+            String name = getLocalName(root.name(), cls.getSimpleName(), pluralName);
+            return new QName(namespace, name);
+        } else {
+            JAXBContext context = getJAXBContext(cls, type);
+            JAXBContextProxy proxy = ReflectionInvokationHandler.createProxyWrapper(context,
+                                                                                    JAXBContextProxy.class);
+            JAXBBeanInfo info = JAXBUtils.getBeanInfo(proxy, cls);
+            if (info != null) {
+                try {
+                    Object instance = object == null ? cls.newInstance() : object;
+                    String name = getLocalName(info.getElementLocalName(instance), cls.getSimpleName(), 
+                                               pluralName);
+                    String namespace = getNamespace(info.getElementNamespaceURI(instance));
+                    return new QName(namespace, name);
+                } catch (Exception ex) {
+                    // ignore
+                }
+            }
+        }
+        return qname;
+    }
+    
+    private String getLocalName(String name, String clsName, boolean pluralName) {
+        if (JAXB_DEFAULT_NAME.equals(name)) {
+            name = clsName;
+            if (name.length() > 1) {
+                name = name.substring(0, 1).toLowerCase() + name.substring(1); 
+            } else {
+                name = name.toLowerCase();
+            }
+        }
+        if (pluralName) {
+            name += 's';
+        }
+        return name;
+    }
+    
+    private String getNamespace(String namespace) {
+        if (JAXB_DEFAULT_NAMESPACE.equals(namespace)) {
+            return "";
+        }
+        return namespace;
+    }
+    
     public boolean isReadable(Class<?> type, Type genericType, Annotation[] anns, MediaType mt) {
         return isSupported(type, genericType, anns);
     }
@@ -124,7 +252,6 @@
         return context != null ? context : getClassContext(type);
     }
     
-    // TODO : move this method to a dedicated JAXBContextRegistry class
     public JAXBContext getClassContext(Class<?> type) throws JAXBException {
         synchronized (classContexts) {
             JAXBContext context = classContexts.get(type);
@@ -136,7 +263,6 @@
         }
     }
     
-    //  TODO : move this method to a dedicated JAXBContextRegistry class
     public JAXBContext getPackageContext(Class<?> type) {
         if (type == null) {
             return null;
@@ -159,12 +285,6 @@
     }
     
     protected boolean isSupported(Class<?> type, Type genericType, Annotation[] anns) {
-        // TODO : Shall we just return true and let readFrom/writeTo 
-        // fail if JAXB can't handle a given type ?
-        
-        // TODO: still not checked : 
-        // - XmlJavaTypeAdapter at package level
-        // - anything else ?
         return type.getAnnotation(XmlRootElement.class) != null
             || JAXBElement.class.isAssignableFrom(type)
             || objectFactoryForClass(type)
@@ -193,7 +313,13 @@
     
     protected Unmarshaller createUnmarshaller(Class<?> cls, Type genericType) 
         throws JAXBException {
-        JAXBContext context = getJAXBContext(cls, genericType);
+        return createUnmarshaller(cls, genericType, false);        
+    }
+    
+    protected Unmarshaller createUnmarshaller(Class<?> cls, Type genericType, boolean isCollection) 
+        throws JAXBException {
+        JAXBContext context = isCollection ? getCollectionContext(cls) 
+                                           : getJAXBContext(cls, genericType);
         Unmarshaller unmarshaller = context.createUnmarshaller();
         if (schema != null) {
             unmarshaller.setSchema(schema);
@@ -201,12 +327,15 @@
         return unmarshaller;        
     }
     
-    protected Marshaller createMarshaller(Object obj, Class<?> cls, Type genericType, String enc)
+    protected Marshaller createMarshaller(Object obj, Class<?> cls, Type genericType, String enc, 
+                                          boolean isCollection)
         throws JAXBException {
         
         Class<?> objClazz = JAXBElement.class.isAssignableFrom(cls) 
                             ? ((JAXBElement)obj).getDeclaredType() : cls;
-        JAXBContext context = getJAXBContext(objClazz, genericType);
+                            
+        JAXBContext context = isCollection ? getCollectionContext(objClazz) 
+            : getJAXBContext(objClazz, genericType);
         Marshaller marshaller = context.createMarshaller();
         if (enc != null) {
             marshaller.setProperty(Marshaller.JAXB_ENCODING, enc);
@@ -214,6 +343,12 @@
         return marshaller;
     }
     
+    protected Marshaller createMarshaller(Object obj, Class<?> cls, Type genericType, String enc)
+        throws JAXBException {
+        
+        return createMarshaller(obj, cls, genericType, enc, false);
+    }
+    
     protected String getEncoding(MediaType mt, MultivaluedMap<String, Object> headers) {
         String enc = mt.getParameters().get(CHARSET_PARAMETER);
         if (enc == null) {
@@ -296,13 +431,56 @@
     }
     
     protected static void handleJAXBException(JAXBException e) {
+        StringBuilder sb = new StringBuilder();
+        if (e.getMessage() != null) {
+            sb.append(e.getMessage()).append(". ");
+        }
+        if (e.getCause() != null && e.getCause().getMessage() != null) {
+            sb.append(e.getCause().getMessage()).append(". ");
+        }
+        if (e.getLinkedException() != null && e.getLinkedException().getMessage() != null) {
+            sb.append(e.getLinkedException().getMessage()).append(". ");
+        }
         Throwable t = e.getLinkedException() != null 
             ? e.getLinkedException() : e.getCause() != null ? e.getCause() : e;
         String message = new org.apache.cxf.common.i18n.Message("JAXB_EXCEPTION", 
-                             BUNDLE, t.getMessage()).toString();
+                             BUNDLE, sb.toString()).toString();
         LOG.warning(message);
         Response r = Response.status(Response.Status.INTERNAL_SERVER_ERROR)
             .type(MediaType.TEXT_PLAIN).entity(message).build();
         throw new WebApplicationException(t, r);
     }
+    
+    @XmlRootElement
+    protected static class CollectionWrapper {
+        
+        @XmlAnyElement(lax = true)
+        private List<?> l;
+        
+        public void setList(List<?> list) {
+            l = list;
+        }
+        
+        public List<?> getList() {
+            if (l == null) {
+                l = new ArrayList<Object>();
+            }
+            return l;
+        }
+        
+        @SuppressWarnings("unchecked")
+        public <T> Object getCollectionOrArray(Class<T> type, boolean isArray) {
+            List<?> theList = getList();
+            if (isArray) {
+                T[] values = (T[])Array.newInstance(type, theList.size());
+                for (int i = 0; i < theList.size(); i++) {
+                    values[i] = (T)theList.get(i);
+                }
+                return values;
+            } else {
+                return theList;
+            }
+        }
+        
+    }
 }

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java Thu Jul 16 14:04:24 2009
@@ -25,6 +25,8 @@
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -35,18 +37,24 @@
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.Provider;
 import javax.xml.bind.JAXBElement;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
+import javax.xml.bind.PropertyException;
 import javax.xml.bind.Unmarshaller;
 import javax.xml.bind.ValidationEventHandler;
+import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamReader;
 import javax.xml.stream.XMLStreamWriter;
 import javax.xml.transform.stream.StreamSource;
 
+import org.apache.cxf.jaxb.NamespaceMapper;
 import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
 import org.apache.cxf.jaxrs.utils.schemas.SchemaHandler;
+import org.apache.cxf.staxutils.DepthXMLStreamReader;
 import org.apache.cxf.staxutils.StaxUtils;
 
 @Produces({"application/xml", "application/*+xml", "text/xml" })
@@ -65,6 +73,19 @@
     private boolean enableStreaming;
     private ValidationEventHandler eventHandler;
     
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] anns, MediaType mt) {
+        
+        if (InjectionUtils.isSupportedCollectionOrArray(type)) {
+            type = InjectionUtils.getActualType(genericType);
+            if (type == null) {
+                return false;
+            }
+        }
+        
+        return isSupported(type, genericType, anns);
+    }
+    
     @Context
     public void setMessageContext(MessageContext mc) {
         super.setContext(mc);
@@ -114,8 +135,11 @@
         MultivaluedMap<String, String> headers, InputStream is) 
         throws IOException {
         try {
-            Class<?> theType = getActualType(type, genericType, anns);
-            Unmarshaller unmarshaller = createUnmarshaller(theType, genericType);
+            boolean isCollection = InjectionUtils.isSupportedCollectionOrArray(type);
+            Class<?> theType = isCollection ? InjectionUtils.getActualType(genericType) : type;
+            theType = getActualType(theType, genericType, anns);
+
+            Unmarshaller unmarshaller = createUnmarshaller(theType, genericType, isCollection);
             if (eventHandler != null) {
                 unmarshaller.setEventHandler(eventHandler);
             }
@@ -123,8 +147,12 @@
             if (JAXBElement.class.isAssignableFrom(type)) {
                 response = unmarshaller.unmarshal(new StreamSource(is), theType);
             } else {
-                response = doUnmarshal(unmarshaller, is, mt);
+                response = doUnmarshal(unmarshaller, type, is, mt);
             }
+            if (isCollection) {
+                response = ((CollectionWrapper)response).getCollectionOrArray(theType, type.isArray()); 
+            }
+            
             response = checkAdapter(response, anns, false);
             return response;
             
@@ -139,18 +167,25 @@
         return null;
     }
 
-    protected Object doUnmarshal(Unmarshaller unmarshaller, InputStream is, MediaType mt) 
+    protected Object doUnmarshal(Unmarshaller unmarshaller, Class<?> type, InputStream is, MediaType mt) 
         throws JAXBException {
-        XMLStreamReader reader = getStreamReader(is, mt);
+        XMLStreamReader reader = getStreamReader(is, type, mt);
         if (reader != null) {
             return unmarshalFromReader(unmarshaller, reader, mt);
         }
         return unmarshalFromInputStream(unmarshaller, is, mt);
     }
     
-    protected XMLStreamReader getStreamReader(InputStream is, MediaType mt) {
+    protected XMLStreamReader getStreamReader(InputStream is, Class<?> type, MediaType mt) {
         MessageContext mc = getContext();
-        return mc != null ? mc.getContent(XMLStreamReader.class) : null;
+        XMLStreamReader reader = mc != null ? mc.getContent(XMLStreamReader.class) : null;
+        if (InjectionUtils.isSupportedCollectionOrArray(type)) {
+            reader = reader == null ? StaxUtils.createXMLStreamReader(is) : reader;
+            return new JAXBCollectionWrapperReader(reader);
+        } else {
+            return reader;
+        }
+        
     }
     
     protected Object unmarshalFromInputStream(Unmarshaller unmarshaller, InputStream is, MediaType mt) 
@@ -169,11 +204,13 @@
         try {
             Object actualObject = checkAdapter(obj, anns, true);
             Class<?> actualClass = actualObject.getClass();
-            if (cls == genericType) {
-                genericType = actualClass;
-            }
             String encoding = getEncoding(m, headers);
-            marshal(actualObject, actualClass, genericType, encoding, os, m);
+            if (InjectionUtils.isSupportedCollectionOrArray(actualClass)) {
+                actualClass = InjectionUtils.getActualType(genericType);
+                marshalCollection(cls, actualObject, actualClass, genericType, encoding, os, m);
+            } else {
+                marshal(actualObject, actualClass, genericType, encoding, os, m);
+            }
         } catch (JAXBException e) {
             handleJAXBException(e);
         }  catch (WebApplicationException e) {
@@ -183,15 +220,66 @@
         }
     }
 
-    protected void marshal(Object obj, Class<?> cls, Type genericType, 
-                           String enc, OutputStream os, MediaType mt)
+    protected void marshalCollection(Class<?> originalCls, Object actualObject, Class<?> actualClass,
+                                     Type genericType, String encoding, OutputStream os, MediaType m) 
         throws Exception {
         
+        QName qname = getCollectionWrapperQName(actualClass, genericType, actualObject, true);
+        if (qname == null) {
+            String message = new org.apache.cxf.common.i18n.Message("NO_COLLECTION_ROOT", 
+                                                                    BUNDLE).toString();
+            throw new WebApplicationException(Response.serverError()
+                                              .entity(message).build());
+        }
+        
+        String startTag = null;
+        String endTag = null;
+        if (qname.getNamespaceURI().length() > 0) {
+            startTag = "<ns1:" + qname.getLocalPart() + " xmlns:ns1=\"" + qname.getNamespaceURI()
+                       + "\">";
+            endTag = "</ns1:" + qname.getLocalPart() + ">"; 
+        } else {
+            startTag = "<" + qname.getLocalPart() + ">";
+            endTag = "</" + qname.getLocalPart() + ">";
+        }
+        os.write(startTag.getBytes());
+        Object[] arr = originalCls.isArray() ? (Object[])actualObject : ((Collection)actualObject).toArray();
+        for (Object o : arr) {
+            marshalCollectionMember(o, actualClass, genericType, encoding, os, m, 
+                                    qname.getNamespaceURI());    
+        }
+        os.write(endTag.getBytes());
+    }
+    
+    protected void marshalCollectionMember(Object obj, Class<?> cls, Type genericType, 
+                           String enc, OutputStream os, MediaType mt, String ns) throws Exception {
         Marshaller ms = createMarshaller(obj, cls, genericType, enc);
+        ms.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
+        if (ns.length() > 0) {
+            Map<String, String> map = Collections.singletonMap(ns, "ns1");
+            NamespaceMapper nsMapper = new NamespaceMapper(map);
+            try {
+                ms.setProperty("com.sun.xml.bind.namespacePrefixMapper", nsMapper);
+            } catch (PropertyException ex) {
+                ms.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", nsMapper);
+            }
+        }
+        marshal(obj, cls, genericType, enc, os, mt, ms);
+    }
+    
+    protected void marshal(Object obj, Class<?> cls, Type genericType, 
+                           String enc, OutputStream os, MediaType mt) throws Exception {
+        Marshaller ms = createMarshaller(obj, cls, genericType, enc);
+        marshal(obj, cls, genericType, enc, os, mt, ms);
+    }
+    
+    protected void marshal(Object obj, Class<?> cls, Type genericType, 
+                           String enc, OutputStream os, MediaType mt, Marshaller ms)
+        throws Exception {
+        
         for (Map.Entry<String, Object> entry : mProperties.entrySet()) {
             ms.setProperty(entry.getKey(), entry.getValue());
         }
-        
         MessageContext mc = getContext();
         if (mc != null) {
             // check Marshaller properties which might've been set earlier on,
@@ -236,4 +324,35 @@
         throws Exception {
         ms.marshal(obj, writer);
     }
+    
+    
+    protected static class JAXBCollectionWrapperReader extends DepthXMLStreamReader {
+        
+        private boolean firstName;
+        private boolean firstNs;
+        
+        public JAXBCollectionWrapperReader(XMLStreamReader reader) {
+            super(reader);
+        }
+        
+        @Override
+        public String getNamespaceURI() {
+            if (!firstNs) {
+                firstNs = true;
+                return "";
+            }
+            return super.getNamespaceURI();
+        }
+        
+        @Override
+        public String getLocalName() {
+            if (!firstName) {
+                firstName = true;
+                return "collectionWrapper";
+            }
+            
+            return super.getLocalName();
+        }
+        
+    }
 }

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JSONProvider.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JSONProvider.java?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JSONProvider.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JSONProvider.java Thu Jul 16 14:04:24 2009
@@ -28,6 +28,7 @@
 import java.io.SequenceInputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
+import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
@@ -39,23 +40,21 @@
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.Provider;
-import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBElement;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Marshaller;
 import javax.xml.bind.Unmarshaller;
-import javax.xml.bind.annotation.XmlRootElement;
 import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
 
-import org.apache.cxf.common.util.ReflectionInvokationHandler;
-import org.apache.cxf.jaxb.JAXBBeanInfo;
-import org.apache.cxf.jaxb.JAXBContextProxy;
-import org.apache.cxf.jaxb.JAXBUtils;
 import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.utils.InjectionUtils;
 import org.apache.cxf.jaxrs.utils.schemas.SchemaHandler;
+import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;
 import org.codehaus.jettison.AbstractXMLStreamWriter;
 import org.codehaus.jettison.mapped.Configuration;
 import org.codehaus.jettison.mapped.MappedNamespaceConvention;
@@ -67,9 +66,6 @@
 @Provider
 public class JSONProvider extends AbstractJAXBProvider  {
     
-    private static final String JAXB_DEFAULT_NAMESPACE = "##default";
-    private static final String JAXB_DEFAULT_NAME = "##default";
-    
     private Map<String, String> namespaceMap = new HashMap<String, String>();
     private boolean serializeAsArray;
     private List<String> arrayKeys;
@@ -132,12 +128,10 @@
         
         try {
             Class<?> theType = getActualType(type, genericType, anns);
-            
             Unmarshaller unmarshaller = createUnmarshaller(theType, genericType);
             
             InputStream realStream = getInputStream(type, genericType, is);
-            MappedXMLInputFactory factory = new MappedXMLInputFactory(namespaceMap);
-            XMLStreamReader xsw = factory.createXMLStreamReader(realStream);
+            XMLStreamReader xsw = getStreamReader(type, realStream);
             
             Object response = null;
             if (JAXBElement.class.isAssignableFrom(type)) {
@@ -161,11 +155,18 @@
         return null;
     }
 
+    protected XMLStreamReader getStreamReader(Class<?> type, InputStream is) 
+        throws Exception {
+        MappedXMLInputFactory factory = new MappedXMLInputFactory(namespaceMap);
+        return factory.createXMLStreamReader(is);
+    }
+    
     protected InputStream getInputStream(Class<Object> cls, Type type, InputStream is) throws Exception {
         if (unwrapped) {
             String rootName = getRootName(cls, type);
             InputStream isBefore = new ByteArrayInputStream(rootName.getBytes());
-            InputStream isAfter = new ByteArrayInputStream("}".getBytes());
+            String after = "}";
+            InputStream isAfter = new ByteArrayInputStream(after.getBytes());
             final InputStream[] streams = new InputStream[]{isBefore, is, isAfter};
             
             Enumeration<InputStream> list = new Enumeration<InputStream>() {
@@ -225,27 +226,12 @@
             if (encoding == null) {
                 encoding = "UTF-8";
             }
-            Marshaller ms = createMarshaller(actualObject, actualClass, genericType, encoding);
-
-            QName qname = getQName(actualClass, genericType, actualObject, true);
-            Configuration c = new Configuration(namespaceMap);
-            MappedNamespaceConvention convention = new MappedNamespaceConvention(c);
-            AbstractXMLStreamWriter xsw = new MappedXMLStreamWriter(
-                                               convention, 
-                                               new OutputStreamWriter(os, encoding));
-            if (serializeAsArray) {
-                if (arrayKeys != null) {
-                    for (String key : arrayKeys) {
-                        xsw.seriliazeAsArray(key);
-                    }
-                } else {
-                    String key = getKey(convention, qname);
-                    xsw.seriliazeAsArray(key);
-                }
+            if (InjectionUtils.isSupportedCollectionOrArray(actualClass)) {
+                actualClass = InjectionUtils.getActualType(genericType);
+                marshalCollection(cls, actualObject, actualClass, genericType, encoding, os, m);
+            } else {
+                marshal(actualObject, actualClass, genericType, encoding, os);
             }
-                        
-            ms.marshal(actualObject, xsw);
-            xsw.close();
             
         } catch (JAXBException e) {
             handleJAXBException(e);
@@ -256,61 +242,85 @@
         }
     }
 
-    private String getKey(MappedNamespaceConvention convention, QName qname) throws Exception {
-        return convention.createKey(qname.getPrefix(), 
-                                    qname.getNamespaceURI(),
-                                    qname.getLocalPart());
-            
-        
-    }
-    
-    private QName getQName(Class<?> cls, Type type, Object object, boolean allocatePrefix) 
+    protected void marshalCollection(Class<?> originalCls, Object actualObject, Class<?> actualClass,
+                                     Type genericType, String encoding, OutputStream os, MediaType m) 
         throws Exception {
-        //try the easy way first
-        XmlRootElement root = cls.getAnnotation(XmlRootElement.class);
-        QName qname = null;
-        if (root != null) {
-            String namespace = getNamespace(root.namespace());
-            String name = getLocalName(root.name(), cls.getSimpleName());
-            String prefix = getPrefix(namespace, allocatePrefix);
-            qname = new QName(namespace, name, prefix);
+        
+        QName qname = getCollectionWrapperQName(actualClass, genericType, actualObject, false);
+        if (qname == null) {
+            String message = new org.apache.cxf.common.i18n.Message("NO_COLLECTION_ROOT", 
+                                                                    BUNDLE).toString();
+            throw new WebApplicationException(Response.serverError()
+                                              .entity(message).build());
+        }
+        String startTag = null;
+        String endTag = null;
+        if (qname.getNamespaceURI().length() > 0) {
+            startTag = "{\"ns1." + qname.getLocalPart() + "\":[";
         } else {
-            JAXBContext context = getJAXBContext(cls, type);
-            JAXBContextProxy proxy = ReflectionInvokationHandler.createProxyWrapper(context,
-                                                                                    JAXBContextProxy.class);
-            JAXBBeanInfo info = JAXBUtils.getBeanInfo(proxy, cls);
-            if (info != null) {
-                try {
-                    Object instance = object == null ? cls.newInstance() : object;
-                    String name = getLocalName(info.getElementLocalName(instance), cls.getSimpleName());
-                    String namespace = getNamespace(info.getElementNamespaceURI(instance));
-                    String prefix = getPrefix(namespace, allocatePrefix);
-                    qname = new QName(namespace, name, prefix);
-                } catch (Exception ex) {
-                    // ignore
-                }
+            startTag = "{\"" + qname.getLocalPart() + "\":[";
+        }
+        endTag = "]}";
+        os.write(startTag.getBytes());
+        Object[] arr = originalCls.isArray() ? (Object[])actualObject : ((Collection)actualObject).toArray();
+        for (int i = 0; i < arr.length; i++) {
+            Marshaller ms = createMarshaller(actualObject, actualClass, genericType, encoding);
+            marshal(ms, arr[i], actualClass, genericType, encoding, os, true);
+            if (i + 1 < arr.length) {
+                os.write(",".getBytes());
             }
         }
-        return qname;
+        os.write(endTag.getBytes());
     }
     
-    private String getLocalName(String name, String clsName) {
-        if (JAXB_DEFAULT_NAME.equals(name)) {
-            name = clsName;
-            if (name.length() > 1) {
-                name = name.substring(0, 1).toLowerCase() + name.substring(1); 
+    protected void marshal(Marshaller ms, Object actualObject, Class<?> actualClass, 
+                  Type genericType, String enc, OutputStream os, boolean isCollection) throws Exception {
+        QName qname = getQName(actualClass, genericType, actualObject, true);
+        Configuration c = new Configuration(namespaceMap);
+        MappedNamespaceConvention convention = new MappedNamespaceConvention(c);
+        AbstractXMLStreamWriter xsw = new MappedXMLStreamWriter(
+                                           convention, 
+                                           new OutputStreamWriter(os, enc));
+        if (serializeAsArray) {
+            if (arrayKeys != null) {
+                for (String key : arrayKeys) {
+                    xsw.seriliazeAsArray(key);
+                }
             } else {
-                name = name.toLowerCase();
+                String key = getKey(convention, qname);
+                xsw.seriliazeAsArray(key);
             }
         }
-        return name;
+
+        XMLStreamWriter writer = isCollection ? new JSONCollectionWriter(xsw, qname) : xsw; 
+        ms.marshal(actualObject, writer);
+        xsw.close();
     }
     
-    private String getNamespace(String namespace) {
-        if (JAXB_DEFAULT_NAMESPACE.equals(namespace)) {
-            return "";
+    
+    
+    protected void marshal(Object actualObject, Class<?> actualClass, 
+                           Type genericType, String enc, OutputStream os) throws Exception {
+        Marshaller ms = createMarshaller(actualObject, actualClass, genericType, enc);
+        marshal(ms, actualObject, actualClass, genericType, enc, os, false);
+    }
+    
+    private String getKey(MappedNamespaceConvention convention, QName qname) throws Exception {
+        return convention.createKey(qname.getPrefix(), 
+                                    qname.getNamespaceURI(),
+                                    qname.getLocalPart());
+            
+        
+    }
+    
+    private QName getQName(Class<?> cls, Type type, Object object, boolean allocatePrefix) 
+        throws Exception {
+        QName qname = getJaxbQName(cls, type, object, false);
+        if (qname != null) {
+            String prefix = getPrefix(qname.getNamespaceURI(), allocatePrefix);
+            return new QName(qname.getNamespaceURI(), qname.getLocalPart(), prefix);
         }
-        return namespace;
+        return null;
     }
     
     private String getPrefix(String namespace, boolean allocatePrefix) {
@@ -325,4 +335,23 @@
         }
         return prefix;
     }
+    
+    protected static class JSONCollectionWriter extends DelegatingXMLStreamWriter {
+        private QName ignoredQName;
+        public JSONCollectionWriter(XMLStreamWriter writer, QName qname) {
+            super(writer);
+            ignoredQName = qname;
+        }
+        
+        @Override
+        public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
+            if (ignoredQName.getLocalPart().equals(local) 
+                && ignoredQName.getNamespaceURI().equals(uri)) {
+                return;
+            }
+            super.writeStartElement(prefix, local, uri);
+        }
+    }
+    
+    
 }

Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties Thu Jul 16 14:04:24 2009
@@ -19,5 +19,6 @@
 #
 #
 JAXB_EXCEPTION=JAXBException occurred : {0}
-UNSUPPORTED_ENCODING=Unsupported encoding : {0}, defaulting to UTF-8 
+UNSUPPORTED_ENCODING=Unsupported encoding : {0}, defaulting to UTF-8
+NO_COLLECTION_ROOT=No collection name is provided
 

Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JAXBElementProviderTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JAXBElementProviderTest.java?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JAXBElementProviderTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JAXBElementProviderTest.java Thu Jul 16 14:04:24 2009
@@ -19,11 +19,13 @@
 
 package org.apache.cxf.jaxrs.provider;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.OutputStream;
 import java.io.Writer;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -48,6 +50,9 @@
 import org.xml.sax.ContentHandler;
 
 import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.resources.Book;
+import org.apache.cxf.jaxrs.resources.CollectionsResource;
+import org.apache.cxf.jaxrs.resources.TagVO2;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -55,6 +60,135 @@
 public class JAXBElementProviderTest extends Assert {
 
     @Test
+    public void testIsWriteableCollection() throws Exception {
+        JAXBElementProvider provider = new JAXBElementProvider();
+        provider.setCollectionWrapperName("foo");
+        Method m = CollectionsResource.class.getMethod("getBooks", new Class[0]);
+        assertTrue(provider.isWriteable(m.getReturnType(), m.getGenericReturnType(),
+                             new Annotation[0], MediaType.TEXT_XML_TYPE));
+    }
+    
+    @Test
+    public void testWriteCollection() throws Exception {
+        doWriteUnqualifiedCollection(true, false);
+    }
+    
+    @Test
+    public void testWriteCollectionJaxbName() throws Exception {
+        doWriteUnqualifiedCollection(false, false);
+    }
+    
+    @Test
+    public void testWriteArray() throws Exception {
+        doWriteUnqualifiedCollection(true, true);
+    }
+    
+    public void doWriteUnqualifiedCollection(boolean setName, boolean isArray) throws Exception {
+        JAXBElementProvider provider = new JAXBElementProvider();
+        if (setName) {
+            provider.setCollectionWrapperName("Books");
+        }
+        List<Book> books = new ArrayList<Book>();
+        books.add(new Book("CXF in Action", 123L));
+        books.add(new Book("CXF Rocks", 124L));
+        String mName = isArray ? "getBooksArray" : "getBooks";
+        Method m = CollectionsResource.class.getMethod(mName, new Class[0]);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        provider.writeTo(isArray ? books.toArray() : books, m.getReturnType(), m.getGenericReturnType(),
+                       new Annotation[0], MediaType.TEXT_XML_TYPE, new MetadataMap<String, Object>(), bos);
+        doReadUnqualifiedCollection(bos.toString());
+    }
+    
+    @Test
+    public void testWriteQualifiedCollection() throws Exception {
+        JAXBElementProvider provider = new JAXBElementProvider();
+        provider.setCollectionWrapperName("{http://tags}tags");
+        List<TagVO2> tags = new ArrayList<TagVO2>();
+        tags.add(new TagVO2("A", "B"));
+        tags.add(new TagVO2("C", "D"));
+        Method m = CollectionsResource.class.getMethod("getTags", new Class[0]);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        provider.writeTo(tags, m.getReturnType(), m.getGenericReturnType(),
+                       new Annotation[0], MediaType.TEXT_XML_TYPE, new MetadataMap<String, Object>(), bos);
+        doReadQualifiedCollection(bos.toString(), false);
+    }
+    
+    @Test
+    public void testReadUnqualifiedCollection() throws Exception {
+        String data = "<Books><Book><id>123</id><name>CXF in Action</name>"
+            + "</Book><Book><id>124</id><name>CXF Rocks</name></Book></Books>";
+        doReadUnqualifiedCollection(data);
+    }
+    
+    @SuppressWarnings("unchecked")
+    private void doReadUnqualifiedCollection(String data) throws Exception {
+        JAXBElementProvider provider = new JAXBElementProvider();
+        Method m = CollectionsResource.class.getMethod("setBooks", 
+                                                       new Class[]{List.class});
+        ByteArrayInputStream is = new ByteArrayInputStream(data.getBytes());
+        List<Book> books = (List<Book>)provider.readFrom(
+                      (Class)m.getParameterTypes()[0], m.getGenericParameterTypes()[0],
+                       new Annotation[0], MediaType.TEXT_XML_TYPE, new MetadataMap<String, String>(), is);
+        assertNotNull(books);
+        assertEquals(2, books.size());
+        Book b1 = books.get(0);
+        assertEquals(123, b1.getId());
+        assertEquals("CXF in Action", b1.getName());
+        
+        Book b2 = books.get(1);
+        assertEquals(124, b2.getId());
+        assertEquals("CXF Rocks", b2.getName());    
+    }
+    
+    
+    @Test
+    public void testReadQualifiedCollection() throws Exception {
+        String data = "<ns1:tags xmlns:ns1=\"http://tags\"><ns1:thetag><group>B</group><name>A</name>"
+            + "</ns1:thetag><ns1:thetag><group>D</group><name>C</name></ns1:thetag></ns1:tags>";
+        doReadQualifiedCollection(data, false);
+    }
+    
+    @Test
+    public void testReadQualifiedArray() throws Exception {
+        String data = "<ns1:tags xmlns:ns1=\"http://tags\"><ns1:thetag><group>B</group><name>A</name>"
+            + "</ns1:thetag><ns1:thetag><group>D</group><name>C</name></ns1:thetag></ns1:tags>";
+        doReadQualifiedCollection(data, true);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public void doReadQualifiedCollection(String data, boolean isArray) throws Exception {
+        JAXBElementProvider provider = new JAXBElementProvider();
+        Method m = null;
+        if (!isArray) {
+            m = CollectionsResource.class.getMethod("setTags", new Class[]{List.class});
+        } else {
+            m = CollectionsResource.class.getMethod("setTagsArray", new Class[]{TagVO2[].class});
+        }
+        
+        ByteArrayInputStream is = new ByteArrayInputStream(data.getBytes());
+        Object o = provider.readFrom(
+                      (Class)m.getParameterTypes()[0], m.getGenericParameterTypes()[0],
+                       new Annotation[0], MediaType.TEXT_XML_TYPE, new MetadataMap<String, String>(), is);
+        assertNotNull(o);
+        TagVO2 t1 = null;
+        TagVO2 t2 = null;
+        if (!isArray) {
+            assertEquals(2, ((List)o).size());
+            t1 = (TagVO2)((List)o).get(0);
+            t2 = (TagVO2)((List)o).get(1);
+        } else {
+            assertEquals(2, ((Object[])o).length);
+            t1 = (TagVO2)((Object[])o)[0];
+            t2 = (TagVO2)((Object[])o)[1];
+        }
+        assertEquals("A", t1.getName());
+        assertEquals("B", t1.getGroup());
+        
+        assertEquals("C", t2.getName());
+        assertEquals("D", t2.getGroup());
+    }
+    
+    @Test
     public void testSetSchemasFromClasspath() {
         JAXBElementProvider provider = new JAXBElementProvider();
         List<String> locations = new ArrayList<String>();

Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JSONProviderTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JSONProviderTest.java?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JSONProviderTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/JSONProviderTest.java Thu Jul 16 14:04:24 2009
@@ -21,6 +21,9 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -30,12 +33,14 @@
 import javax.ws.rs.ext.MessageBodyReader;
 
 import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.resources.CollectionsResource;
 import org.apache.cxf.jaxrs.resources.ManyTags;
 import org.apache.cxf.jaxrs.resources.TagVO;
 import org.apache.cxf.jaxrs.resources.TagVO2;
 import org.apache.cxf.jaxrs.resources.Tags;
 
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class JSONProviderTest extends Assert {
@@ -161,6 +166,83 @@
     }
     
     @Test
+    public void testWriteQualifiedCollection() throws Exception {
+        JSONProvider p = new JSONProvider();
+        p.setCollectionWrapperName("{http://tags}tag");
+        Map<String, String> namespaceMap = new HashMap<String, String>();
+        namespaceMap.put("http://tags", "ns1");
+        p.setNamespaceMap(namespaceMap);
+        List<TagVO2> tags = new ArrayList<TagVO2>();
+        tags.add(createTag2("a", "b"));
+        tags.add(createTag2("c", "d"));
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        Method m = CollectionsResource.class.getMethod("getTags", new Class[0]);
+        p.writeTo(tags, m.getReturnType(), m.getGenericReturnType(), new Annotation[0], 
+                  MediaType.APPLICATION_JSON_TYPE, new MetadataMap<String, Object>(), os);
+        
+        String s = os.toString();
+        String data = "{\"ns1.tag\":[{\"group\":\"b\",\"name\":\"a\"}"
+            + ",{\"group\":\"d\",\"name\":\"c\"}]}";
+        assertEquals(s, data);
+    }
+    
+    @Test
+    @Ignore
+    public void testReadQualifiedCollection() throws Exception {
+        String data = "{\"ns1.tag\":[{\"group\":\"b\",\"name\":\"a\"}"
+            + ",{\"group\":\"d\",\"name\":\"c\"}]}";
+        doReadQualifiedCollection(data, false);
+    }
+    
+    @Test
+    @Ignore
+    public void testReadQualifiedArray() throws Exception {
+        String data = "{\"ns1.tag\":[{\"group\":\"b\",\"name\":\"a\"}"
+            + ",{\"group\":\"d\",\"name\":\"c\"}]}";
+        doReadQualifiedCollection(data, true);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public void doReadQualifiedCollection(String data, boolean isArray) throws Exception {
+        
+        JSONProvider provider = new JSONProvider();
+        provider.setCollectionWrapperName("{http://tags}tag");
+        Map<String, String> namespaceMap = new HashMap<String, String>();
+        namespaceMap.put("http://tags", "ns1");
+        provider.setNamespaceMap(namespaceMap);
+        
+        Method m = null;
+        if (!isArray) {
+            m = CollectionsResource.class.getMethod("setTags", new Class[]{List.class});
+        } else {
+            m = CollectionsResource.class.getMethod("setTagsArray", new Class[]{TagVO2[].class});
+        }
+        
+        ByteArrayInputStream is = new ByteArrayInputStream(data.getBytes());
+        Object o = provider.readFrom(
+                      (Class)m.getParameterTypes()[0], m.getGenericParameterTypes()[0],
+                       new Annotation[0], MediaType.APPLICATION_JSON_TYPE, 
+                       new MetadataMap<String, String>(), is);
+        assertNotNull(o);
+        TagVO2 t1 = null;
+        TagVO2 t2 = null;
+        if (!isArray) {
+            assertEquals(2, ((List)o).size());
+            t1 = (TagVO2)((List)o).get(0);
+            t2 = (TagVO2)((List)o).get(1);
+        } else {
+            assertEquals(2, ((Object[])o).length);
+            t1 = (TagVO2)((Object[])o)[0];
+            t2 = (TagVO2)((Object[])o)[1];
+        }
+        assertEquals("A", t1.getName());
+        assertEquals("B", t1.getGroup());
+        
+        assertEquals("C", t2.getName());
+        assertEquals("D", t2.getGroup());
+    }
+    
+    @Test
     public void testWriteToSingleQualifiedTag2() throws Exception {
         JSONProvider p = new JSONProvider();
         TagVO2 tag = createTag2("a", "b");
@@ -229,6 +311,7 @@
                   MediaType.APPLICATION_JSON_TYPE, new MetadataMap<String, Object>(), os);
         
         String s = os.toString();
+        System.out.println(s);
         assertEquals(
             "{\"ManyTags\":{\"tags\":{\"list\":[{\"group\":\"b\",\"name\":\"a\"}]}}}",
             s);

Added: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/CollectionsResource.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/CollectionsResource.java?rev=794680&view=auto
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/CollectionsResource.java (added)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/CollectionsResource.java Thu Jul 16 14:04:24 2009
@@ -0,0 +1,54 @@
+/**
+ * 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.cxf.jaxrs.resources;
+
+import java.util.List;
+
+import javax.ws.rs.GET;
+
+import javax.ws.rs.POST;
+
+public class CollectionsResource {
+    @GET
+    public List<Book> getBooks() {
+        return null;
+    }
+    
+    @GET
+    public List<TagVO2> getTags() {
+        return null;
+    }
+    
+    @GET
+    public Book[] getBooksArray() {
+        return null;
+    }
+    
+    @POST
+    public void setBooks(List<Book> books) {
+    }
+    
+    @POST
+    public void setTags(List<TagVO2> tags) {
+    }
+    
+    @POST
+    public void setTagsArray(TagVO2[] tags) {
+    }
+}

Propchange: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/CollectionsResource.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/resources/CollectionsResource.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Modified: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java (original)
+++ cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java Thu Jul 16 14:04:24 2009
@@ -137,6 +137,28 @@
         return doGetBook(url2.substring(index + 1));
     }
     
+    @POST
+    @Path("/collections")
+    @Produces("application/xml")
+    @Consumes("application/xml")
+    public List<Book> getBookCollection(List<Book> bs) throws Exception {
+        if (bs == null || bs.size() != 2) {
+            throw new RuntimeException();
+        }
+        return bs;
+    }
+    
+    @POST
+    @Path("/array")
+    @Produces("application/xml")
+    @Consumes("application/xml")
+    public Book[] getBookArray(Book[] bs) throws Exception {
+        if (bs == null || bs.length != 2) {
+            throw new RuntimeException();
+        }
+        return bs;
+    }
+    
     @GET
     @Path("/segment/{pathsegment}/")
     public Book getBookBySegment(@PathParam("pathsegment") PathSegment segment) throws Exception {

Modified: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java?rev=794680&r1=794679&r2=794680&view=diff
==============================================================================
--- cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java (original)
+++ cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java Thu Jul 16 14:04:24 2009
@@ -23,6 +23,7 @@
 import java.io.InputStream;
 import java.net.URL;
 import java.net.URLConnection;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -85,6 +86,46 @@
         assertEquals("CXF rocks", source.getValue("Book/name"));
     }
     
+    @Test 
+    public void testGetBookCollection() throws Exception {
+        BookStore store = JAXRSClientFactory.create("http://localhost:9080", BookStore.class);
+        Book b1 = new Book("CXF in Action", 123L);
+        Book b2 = new Book("CXF Rocks", 124L);
+        List<Book> books = new ArrayList<Book>();
+        books.add(b1);
+        books.add(b2);
+        List<Book> books2 = store.getBookCollection(books);
+        assertNotNull(books2);
+        assertNotSame(books, books2);
+        assertEquals(2, books2.size());
+        Book b11 = books.get(0);
+        assertEquals(123L, b11.getId());
+        assertEquals("CXF in Action", b11.getName());
+        Book b22 = books.get(1);
+        assertEquals(124L, b22.getId());
+        assertEquals("CXF Rocks", b22.getName());
+    }
+    
+    @Test 
+    public void testGetBookArray() throws Exception {
+        BookStore store = JAXRSClientFactory.create("http://localhost:9080", BookStore.class);
+        Book b1 = new Book("CXF in Action", 123L);
+        Book b2 = new Book("CXF Rocks", 124L);
+        Book[] books = new Book[2];
+        books[0] = b1;
+        books[1] = b2;
+        Book[] books2 = store.getBookArray(books);
+        assertNotNull(books2);
+        assertNotSame(books, books2);
+        assertEquals(2, books2.length);
+        Book b11 = books2[0];
+        assertEquals(123L, b11.getId());
+        assertEquals("CXF in Action", b11.getName());
+        Book b22 = books2[1];
+        assertEquals(124L, b22.getId());
+        assertEquals("CXF Rocks", b22.getName());
+    }
+    
     @Test
     public void testGetBookByURL() throws Exception {
         getAndCompareAsStrings("http://localhost:9080/bookstore/bookurl/http%3A%2F%2Ftest.com%2Frss%2F123",