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/09/07 22:08:25 UTC

svn commit: r812265 - in /cxf/branches/2.2.x-fixes: ./ rt/core/src/main/java/org/apache/cxf/attachment/ rt/core/src/main/java/org/apache/cxf/interceptor/ rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/ rt/frontend/jaxrs/src/main/java/org/a...

Author: sergeyb
Date: Mon Sep  7 20:08:23 2009
New Revision: 812265

URL: http://svn.apache.org/viewvc?rev=812265&view=rev
Log:
Merged revisions 812232 via svnmerge from 
https://svn.apache.org/repos/asf/cxf/trunk

........
  r812232 | sergeyb | 2009-09-07 18:25:40 +0100 (Mon, 07 Sep 2009) | 1 line
  
  [CXF-2270] : support for writing attachments for JAXRS
........

Added:
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java
      - copied unchanged from r812232, cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java
    cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java.jpg
      - copied unchanged from r812232, cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java.jpg
Modified:
    cxf/branches/2.2.x-fixes/   (props changed)
    cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java
    cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
    cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
    cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
    cxf/branches/2.2.x-fixes/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
    cxf/branches/2.2.x-fixes/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java
    cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
    cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java

Propchange: cxf/branches/2.2.x-fixes/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Mon Sep  7 20:08:23 2009
@@ -1 +1 @@
-/cxf/trunk:782728-782730,783097,783294,783396,784059,784181-784184,784893,784895,785279-785282,785468,785621,785624,785651,785734,785866,786142,786271-786272,786395,786512,786514,786582-786583,786638,786647,786850,787200,787269,787277-787279,787290-787291,787305,787323,787366,787849,788030,788060,788187,788444,788451,788703,788752,788774,788819-788820,789013,789371,789387,789420,789527-789530,789704-789705,789788,789811,789896-789901,790074,790094,790134,790188,790294,790553,790637-790644,790868,791301,791354,791538,791753,791947,792007,792096,792183,792261-792265,792271,792604,792683-792685,792975,792985,793059,793570,794297,794396,794680,794728,794771,794778-794780,794892,795044,795104,795160,795583,795907,796022-796023,796352,796593,796741,796780,796994-796997,797117,797159,797192,797194,797231-797233,797442,797505,797517,797534,797581-797583,797587,797640,797651,797699,797882-797883,798344-798346,798363,798461,798479,798533,798551,798557,798561-798562,798570,798573,79858
 4,798654,798748-798749,798816,798891,798929-798930,799245,799267,799439,799448,799637,799723-799724,799792,800453,800497-800498,801380-801381,801447,801962,802892,803056,803129,803174,803419,803460,803493,803689,804002,804175,804276,805784,805907,805909,806020-806021,806023,806405-806406,806576,806602-806604,806620,806627,806631,806633,806638,806687,806876,806922,806979-806982,807181,807205,807295,807748,807807,808035,808069,808085,808107,808464,808488,808731,808885,808925,809082-809083,809162,809190,809417-809626,809631,809663,809706,809738,809962,810090,810143,810349,810376,810513,810526,810927,811013,811148-811149,811175,811183,811387,811467-811468,811680,811696
+/cxf/trunk:782728-782730,783097,783294,783396,784059,784181-784184,784893,784895,785279-785282,785468,785621,785624,785651,785734,785866,786142,786271-786272,786395,786512,786514,786582-786583,786638,786647,786850,787200,787269,787277-787279,787290-787291,787305,787323,787366,787849,788030,788060,788187,788444,788451,788703,788752,788774,788819-788820,789013,789371,789387,789420,789527-789530,789704-789705,789788,789811,789896-789901,790074,790094,790134,790188,790294,790553,790637-790644,790868,791301,791354,791538,791753,791947,792007,792096,792183,792261-792265,792271,792604,792683-792685,792975,792985,793059,793570,794297,794396,794680,794728,794771,794778-794780,794892,795044,795104,795160,795583,795907,796022-796023,796352,796593,796741,796780,796994-796997,797117,797159,797192,797194,797231-797233,797442,797505,797517,797534,797581-797583,797587,797640,797651,797699,797882-797883,798344-798346,798363,798461,798479,798533,798551,798557,798561-798562,798570,798573,79858
 4,798654,798748-798749,798816,798891,798929-798930,799245,799267,799439,799448,799637,799723-799724,799792,800453,800497-800498,801380-801381,801447,801962,802892,803056,803129,803174,803419,803460,803493,803689,804002,804175,804276,805784,805907,805909,806020-806021,806023,806405-806406,806576,806602-806604,806620,806627,806631,806633,806638,806687,806876,806922,806979-806982,807181,807205,807295,807748,807807,808035,808069,808085,808107,808464,808488,808731,808885,808925,809082-809083,809162,809190,809417-809626,809631,809663,809706,809738,809962,810090,810143,810349,810376,810513,810526,810927,811013,811148-811149,811175,811183,811387,811467-811468,811680,811696,812232

Propchange: cxf/branches/2.2.x-fixes/
------------------------------------------------------------------------------
Binary property 'svnmerge-integrated' - no diff available.

Modified: cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java (original)
+++ cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java Mon Sep  7 20:08:23 2009
@@ -24,6 +24,11 @@
 import java.io.StringWriter;
 import java.io.Writer;
 import java.net.URLDecoder;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.cxf.message.Attachment;
 import org.apache.cxf.message.Message;
@@ -31,17 +36,26 @@
 
 public class AttachmentSerializer {
 
-    private static final String BODY_ATTACHMENT_ID = "root.message@cxf.apache.org";
     private Message message;
     private String bodyBoundary;
     private OutputStream out;
     private String encoding;
+    
+    private String multipartType;
+    private Map<String, List<String>> rootHeaders = Collections.emptyMap();
     private boolean xop = true;
     
     public AttachmentSerializer(Message messageParam) {
         message = messageParam;
     }
 
+    public AttachmentSerializer(Message messageParam, String multipartType, 
+                                Map<String, List<String>> headers) {
+        message = messageParam;
+        this.multipartType = multipartType;
+        this.rootHeaders = headers;
+    }
+    
     /**
      * Serialize the beginning of the attachment which includes the MIME 
      * beginning and headers for the root message.
@@ -73,19 +87,27 @@
         }        
         
         // Set transport mime type
+        String requestMimeType = multipartType == null ? "multipart/related" : multipartType;
+        
         StringBuilder ct = new StringBuilder();
-        ct.append("multipart/related; ");
-        if (xop) {
-            ct.append("type=\"application/xop+xml\"; ");
-        } else {
-            ct.append("type=\"").append(bodyCt).append("\"; ");
+        ct.append(requestMimeType);
+        if (requestMimeType.indexOf("type=") == -1) {
+            ct.append("; ");
+            if (xop) {
+                ct.append("type=\"application/xop+xml\"");
+            } else {
+                ct.append("type=\"").append(bodyCt).append("\"");
+            }    
         }
+        ct.append("; ");
+        
+        String rootContentId = getHeaderValue("Content-ID", AttachmentUtil.BODY_ATTACHMENT_ID);
         
         ct.append("boundary=\"")
             .append(bodyBoundary)
             .append("\"; ")
             .append("start=\"<")
-            .append(BODY_ATTACHMENT_ID)
+            .append(rootContentId)
             .append(">\"; ")
             .append("start-info=\"")
             .append(bodyCt)
@@ -106,17 +128,38 @@
         writer.write(bodyBoundary);
         
         StringBuilder mimeBodyCt = new StringBuilder();
-        mimeBodyCt.append("application/xop+xml; charset=")
-            .append(encoding)
-            .append("; type=\"")
-            .append(bodyCt)
-            .append("\";");
+        String bodyType = getHeaderValue("Content-Type", null);
+        if (bodyType == null) {
+            mimeBodyCt.append("application/xop+xml; charset=")
+                .append(encoding)
+                .append("; type=\"")
+                .append(bodyCt)
+                .append("\";");
+        } else {
+            mimeBodyCt.append(bodyType);
+        }
         
-        writeHeaders(mimeBodyCt.toString(), BODY_ATTACHMENT_ID, writer);
+        writeHeaders(mimeBodyCt.toString(), rootContentId, rootHeaders, writer);
         out.write(writer.getBuffer().toString().getBytes(encoding));
     }
 
-    private void writeHeaders(String contentType, String attachmentId, Writer writer) throws IOException {
+    private String getHeaderValue(String name, String defaultValue) {
+        List<String> value = rootHeaders.get(name);
+        if (value == null || value.isEmpty()) {
+            return defaultValue;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < value.size(); i++) {
+            sb.append(value.get(i));
+            if (i + 1 < value.size()) {
+                sb.append(',');
+            }
+        }
+        return sb.toString();
+    }
+    
+    private static void writeHeaders(String contentType, String attachmentId, 
+                                     Map<String, List<String>> headers, Writer writer) throws IOException {
         writer.write("\r\n");
         writer.write("Content-Type: ");
         writer.write(contentType);
@@ -133,6 +176,24 @@
             writer.write(URLDecoder.decode(attachmentId, "UTF-8"));
             writer.write(">\r\n");
         }
+        // headers like Content-Disposition need to be serialized
+        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
+            String name = entry.getKey();
+            if ("Content-Type".equalsIgnoreCase(name) || "Content-ID".equalsIgnoreCase(name)
+                || "Content-Transfer-Encoding".equalsIgnoreCase(name)) {
+                continue;
+            }
+            writer.write(name + ": ");
+            List<String> values = entry.getValue();
+            for (int i = 0; i < values.size(); i++) {
+                writer.write(values.get(i));
+                if (i + 1 < values.size()) {
+                    writer.write(",");
+                }
+            }
+            writer.write("\r\n");
+        }
+        
         writer.write("\r\n");
     }
 
@@ -147,7 +208,21 @@
                 writer.write("\r\n");
                 writer.write("--");
                 writer.write(bodyBoundary);
-                writeHeaders(a.getDataHandler().getContentType(), a.getId(), writer);
+                
+                Map<String, List<String>> headers = null;
+                Iterator<String> it = a.getHeaderNames();
+                if (it.hasNext()) {
+                    headers = new LinkedHashMap<String, List<String>>();
+                    while (it.hasNext()) {
+                        String key = it.next();
+                        headers.put(key, Collections.singletonList(a.getHeader(key)));
+                    }
+                } else {
+                    headers = Collections.emptyMap();
+                }
+                
+                writeHeaders(a.getDataHandler().getContentType(), a.getId(),
+                             headers, writer);
                 out.write(writer.getBuffer().toString().getBytes(encoding));
                 
                 a.getDataHandler().writeTo(out);

Modified: cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java (original)
+++ cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java Mon Sep  7 20:08:23 2009
@@ -44,6 +44,9 @@
 import org.apache.cxf.message.Attachment;
 
 public final class AttachmentUtil {
+    
+    public static final String BODY_ATTACHMENT_ID = "root.message@cxf.apache.org";
+    
     private static volatile int counter;
     private static final String ATT_UUID = UUID.randomUUID().toString();
     
@@ -99,7 +102,7 @@
         return "uuid:" + result.toString();
     }
 
-    public static String getAttchmentPartHeader(Attachment att) {
+    public static String getAttachmentPartHeader(Attachment att) {
         StringBuffer buffer = new StringBuffer(200);
         buffer.append(HttpHeaderHelper.getHeaderKey(HttpHeaderHelper.CONTENT_TYPE) + ": "
                 + att.getDataHandler().getContentType() + ";\r\n");

Modified: cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java (original)
+++ cxf/branches/2.2.x-fixes/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java Mon Sep  7 20:08:23 2009
@@ -20,6 +20,9 @@
 package org.apache.cxf.interceptor;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.ResourceBundle;
 
 import org.apache.cxf.attachment.AttachmentSerializer;
@@ -54,7 +57,8 @@
             return;
         }
 
-        AttachmentSerializer serializer = new AttachmentSerializer(message);
+        AttachmentSerializer serializer = 
+            new AttachmentSerializer(message, getMultipartType(), getRootHeaders());
         serializer.setXop(mtomEnabled);
         
         try {
@@ -67,6 +71,14 @@
         // Add a final interceptor to write attachements
         message.getInterceptorChain().add(ending);   
     }
+   
+    protected String getMultipartType() {
+        return "multipart/related";
+    }
+    
+    protected Map<String, List<String>> getRootHeaders() {
+        return Collections.emptyMap();
+    }
     
     public class AttachmentOutEndingInterceptor extends AbstractPhaseInterceptor<Message> {
         public AttachmentOutEndingInterceptor() {
@@ -85,4 +97,6 @@
         }
 
     }
+    
+    
 }

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java Mon Sep  7 20:08:23 2009
@@ -352,7 +352,7 @@
                 mbw.writeTo(o, cls, type, anns, contentType, headers, os);
                 os.flush();
             } catch (Exception ex) {
-                throw new WebApplicationException();
+                throw new WebApplicationException(ex);
             }
              
         } else {
@@ -395,7 +395,7 @@
                 return mbr.readFrom(cls, type, anns, contentType, 
                        new MetadataMap<String, Object>(r.getMetadata(), true, true), inputStream);
             } catch (Exception ex) {
-                throw new WebApplicationException();
+                throw new WebApplicationException(ex);
             }
              
         } else if (cls == Response.class) {

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java Mon Sep  7 20:08:23 2009
@@ -21,8 +21,10 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
 import java.net.HttpURLConnection;
 import java.net.URI;
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -40,6 +42,7 @@
 
 import org.apache.cxf.Bus;
 import org.apache.cxf.bus.spring.SpringBusFactory;
+import org.apache.cxf.helpers.CastUtils;
 import org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor;
 import org.apache.cxf.interceptor.Fault;
 import org.apache.cxf.jaxrs.ext.form.Form;
@@ -200,7 +203,7 @@
      *         error message if client or server error occured
      */
     public Response invoke(String httpMethod, Object body) {
-        return doInvoke(httpMethod, body, InputStream.class);
+        return doInvoke(httpMethod, body, InputStream.class, InputStream.class);
     }
     
     /**
@@ -260,7 +263,7 @@
      */
     public Response form(Map<String, List<Object>> values) {
         type(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
-        return doInvoke("POST", values, InputStream.class);
+        return doInvoke("POST", values, InputStream.class, InputStream.class);
     }
     
     /**
@@ -270,7 +273,7 @@
      */
     public Response form(Form form) {
         type(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
-        return doInvoke("POST", form.getData(), InputStream.class);
+        return doInvoke("POST", form.getData(), InputStream.class, InputStream.class);
     }
     
     /**
@@ -283,7 +286,7 @@
      */
     public <T> T invoke(String httpMethod, Object body, Class<T> responseClass) {
         
-        Response r = doInvoke(httpMethod, body, responseClass);
+        Response r = doInvoke(httpMethod, body, responseClass, responseClass);
         
         if (r.getStatus() >= 400 && responseClass != null) {
             throw new WebApplicationException(r);
@@ -293,6 +296,25 @@
     }
     
     /**
+     * Does HTTP invocation and returns a collection of typed objects 
+     * @param httpMethod HTTP method 
+     * @param body request body, can be null
+     * @param memberClass expected type of collection member class
+     * @return typed collection
+     */
+    public <T> Collection<T> invokeAndGetCollection(String httpMethod, Object body, 
+                                                    Class<T> memberClass) {
+        
+        Response r = doInvoke(httpMethod, body, Collection.class, memberClass);
+        
+        if (r.getStatus() >= 400) {
+            throw new WebApplicationException(r);
+        }
+        
+        return CastUtils.cast((Collection)r.getEntity(), memberClass);
+    }
+    
+    /**
      * Does HTTP POST invocation and returns typed response object
      * @param body request body, can be null
      * @param responseClass expected type of response object
@@ -304,6 +326,26 @@
     }
     
     /**
+     * Does HTTP POST invocation and returns a collection of typed objects 
+     * @param body request body, can be null
+     * @param memberClass expected type of collection member class
+     * @return typed collection
+     */
+    public <T> Collection<T> postAndGetCollection(Object body, Class<T> memberClass) {
+        return invokeAndGetCollection("POST", body, memberClass);
+    }
+    
+    /**
+     * Does HTTP GET invocation and returns a collection of typed objects 
+     * @param body request body, can be null
+     * @param memberClass expected type of collection member class
+     * @return typed collection
+     */
+    public <T> Collection<T> getCollection(Class<T> memberClass) {
+        return invokeAndGetCollection("GET", null, memberClass);
+    }
+    
+    /**
      * Does HTTP GET invocation and returns typed response object
      * @param body request body, can be null
      * @param responseClass expected type of response object
@@ -492,7 +534,7 @@
         return (WebClient)super.reset();
     }
     
-    protected Response doInvoke(String httpMethod, Object body, Class<?> responseClass) {
+    protected Response doInvoke(String httpMethod, Object body, Class<?> responseClass, Type genericType) {
         
         MultivaluedMap<String, String> headers = getHeaders();
         if (body != null) {
@@ -507,7 +549,7 @@
         }
         resetResponse();
         try {
-            return doChainedInvocation(httpMethod, headers, body, responseClass);
+            return doChainedInvocation(httpMethod, headers, body, responseClass, genericType);
         } finally {
             clearTemplates();
         }
@@ -515,7 +557,7 @@
     }
 
     protected Response doChainedInvocation(String httpMethod, 
-        MultivaluedMap<String, String> headers, Object body, Class<?> responseClass) {
+        MultivaluedMap<String, String> headers, Object body, Class<?> responseClass, Type genericType) {
         
         Message m = createMessage(httpMethod, headers, getCurrentURI());
         m.put(URITemplate.TEMPLATE_PARAMETERS, templates);
@@ -533,15 +575,16 @@
         
         // TODO : this needs to be done in an inbound chain instead
         HttpURLConnection connect = (HttpURLConnection)m.get(HTTPConduit.KEY_HTTP_CONNECTION);
-        return handleResponse(connect, m, responseClass);
+        return handleResponse(connect, m, responseClass, genericType);
     }
     
-    protected Response handleResponse(HttpURLConnection conn, Message outMessage, Class<?> responseClass) {
+    protected Response handleResponse(HttpURLConnection conn, Message outMessage, 
+                                      Class<?> responseClass, Type genericType) {
         try {
             ResponseBuilder rb = setResponseBuilder(conn, outMessage.getExchange()).clone();
             Response currentResponse = rb.clone().build();
             
-            Object entity = readBody(currentResponse, conn, outMessage, responseClass, responseClass,
+            Object entity = readBody(currentResponse, conn, outMessage, responseClass, genericType,
                                      new Annotation[]{});
             rb.entity(entity);
             

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java Mon Sep  7 20:08:23 2009
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Type;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -39,10 +40,14 @@
 import javax.ws.rs.ext.ContextResolver;
 import javax.ws.rs.ext.Providers;
 
+import org.apache.cxf.attachment.AttachmentImpl;
 import org.apache.cxf.attachment.AttachmentUtil;
+import org.apache.cxf.endpoint.Endpoint;
+import org.apache.cxf.interceptor.AttachmentOutInterceptor;
 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
 import org.apache.cxf.jaxrs.interceptor.AttachmentInputInterceptor;
+import org.apache.cxf.jaxrs.interceptor.AttachmentOutputInterceptor;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
 import org.apache.cxf.message.Message;
 
@@ -120,27 +125,69 @@
     }
 
     public void put(Object key, Object value) {
+        if (MultipartBody.OUTBOUND_MESSAGE_ATTACHMENTS.equals(key.toString())) {
+            convertToAttachments(value);
+        }
         m.put(key.toString(), value);
     }
 
+    private void convertToAttachments(Object value) {
+        List<?> handlers = (List)value;
+        List<org.apache.cxf.message.Attachment> atts = 
+            new ArrayList<org.apache.cxf.message.Attachment>();
+        
+        for (int i = 1; i < handlers.size(); i++) {
+            Attachment handler = (Attachment)handlers.get(i);
+            AttachmentImpl att = new AttachmentImpl(handler.getContentId(), handler.getDataHandler());
+            for (String key : handler.getHeaders().keySet()) {
+                att.setHeader(key, att.getHeader(key));
+            }
+            att.setXOP(false);
+            atts.add(att);
+        }
+        Message outMessage = getOutMessage();
+        outMessage.setAttachments(atts);
+        outMessage.put(AttachmentOutInterceptor.WRITE_ATTACHMENTS, "true");
+        Attachment root = (Attachment)handlers.get(0);
+        AttachmentOutputInterceptor attInterceptor =          
+            new AttachmentOutputInterceptor(outMessage.get(Message.CONTENT_TYPE).toString(),
+                                            root.getHeaders());
+        
+        outMessage.put(Message.CONTENT_TYPE, root.getContentType().toString());
+        attInterceptor.handleMessage(outMessage);
+    }
+    
+    private Message getOutMessage() {
+        
+        Message message = m.getExchange().getOutMessage();
+        if (message == null) {
+            Endpoint ep = m.getExchange().get(Endpoint.class);
+            message = ep.getBinding().createMessage();
+            m.getExchange().setOutMessage(message);
+        }
+        
+        return message;
+    }
+    
     private MultipartBody createAttachments(String propertyName) {
-        Object o = m.get(propertyName);
+        Message inMessage = m.getExchange().getInMessage();
+        Object o = inMessage.get(propertyName);
         if (o != null) {
             return (MultipartBody)o;
         }
-        new AttachmentInputInterceptor().handleMessage(m);
+        new AttachmentInputInterceptor().handleMessage(inMessage);
         
         List<Attachment> newAttachments = new LinkedList<Attachment>();
         try {
             Attachment first = new Attachment(AttachmentUtil.createAttachment(
-                                     m.getContent(InputStream.class), 
-                                     (InternetHeaders)m.get(InternetHeaders.class.getName())));
+                                     inMessage.getContent(InputStream.class), 
+                                     (InternetHeaders)inMessage.get(InternetHeaders.class.getName())));
             newAttachments.add(first);
         } catch (IOException ex) {
             throw new WebApplicationException(500);
         }
         
-        Collection<org.apache.cxf.message.Attachment> childAttachments = m.getAttachments();
+        Collection<org.apache.cxf.message.Attachment> childAttachments = inMessage.getAttachments();
         if (childAttachments == null) {
             childAttachments = Collections.emptyList();
         }
@@ -149,7 +196,7 @@
             newAttachments.add(new Attachment(a));
         }
         MultipartBody body = new MultipartBody(newAttachments, getHttpHeaders().getMediaType(), false);
-        m.put(propertyName, body);
+        inMessage.put(propertyName, body);
         return body;
     }
        

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java Mon Sep  7 20:08:23 2009
@@ -32,22 +32,26 @@
 public class Attachment {
 
     private DataHandler handler;
-    private MultivaluedMap<String, String> headers = new MetadataMap<String, String>();
-    private String contentId;
+    private MultivaluedMap<String, String> headers = 
+        new MetadataMap<String, String>(false, true);
+    private Object object;
     
     public Attachment(org.apache.cxf.message.Attachment a) {
         handler = a.getDataHandler();
-        contentId = a.getId();
         for (Iterator<String> i = a.getHeaderNames(); i.hasNext();) {
             String name = i.next();
+            if ("Content-ID".equalsIgnoreCase(name)) {
+                continue;
+            }
             headers.add(name, a.getHeader(name));
         }
+        headers.putSingle("Content-ID", a.getId());
     }
     
     public Attachment(String id, DataHandler dh, MultivaluedMap<String, String> headers) {
         handler = dh;
-        contentId = id;
-        this.headers = new MetadataMap<String, String>(headers);
+        this.headers = new MetadataMap<String, String>(headers, false, true);
+        this.headers.putSingle("Content-ID", id);
     }
     
     public Attachment(String id, DataSource ds, MultivaluedMap<String, String> headers) {
@@ -60,6 +64,19 @@
              headers);
     }
     
+    public Attachment(String id, String mediaType, Object object) {
+        this.object = object;
+        headers.putSingle("Content-ID", id);
+        headers.putSingle("Content-Type", mediaType);
+    }
+    
+    public Attachment(String id, InputStream is, ContentDisposition cd) {
+        handler = new DataHandler(new InputStreamDataSource(is, "application/octet-stream"));
+        headers.putSingle("Content-Disposition", cd.toString());
+        headers.putSingle("Content-ID", id);
+        headers.putSingle("Content-Type", "application/octet-stream");
+    }
+    
     public ContentDisposition getContentDisposition() {
         String header = getHeader("Content-Disposition");
         
@@ -67,11 +84,11 @@
     }
 
     public String getContentId() {
-        return contentId;
+        return headers.getFirst("Content-ID");
     }
 
     public MediaType getContentType() {
-        String value = handler.getContentType();
+        String value = handler != null ? handler.getContentType() : headers.getFirst("Content-Type");
         return value == null ? MediaType.TEXT_PLAIN_TYPE : MediaType.valueOf(value);
     }
 
@@ -79,14 +96,27 @@
         return handler;
     }
 
+    public Object getObject() {
+        return object;
+    }
+    
     public String getHeader(String name) {
-        String header = headers.getFirst(name);
-        return header == null ? headers.getFirst(name.toLowerCase()) : header; 
+        List<String> header = headers.get(name);
+        if (header == null || header.size() == 0) {
+            return null;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < header.size(); i++) {
+            sb.append(header.get(i));
+            if (i + 1 < header.size()) {
+                sb.append(',');
+            }
+        }
+        return sb.toString();
     }
     
     public List<String> getHeaderAsList(String name) {
-        List<String> header = headers.get(name);
-        return header == null ? headers.get(name.toLowerCase()) : header;
+        return headers.get(name);
     }
 
     public MultivaluedMap<String, String> getHeaders() {
@@ -95,7 +125,7 @@
     
     @Override
     public int hashCode() {
-        return contentId.hashCode() + 37 * headers.hashCode(); 
+        return headers.hashCode(); 
     }
     
     @Override
@@ -105,7 +135,7 @@
         }
         
         Attachment other = (Attachment)o;
-        return contentId.equals(other.contentId) && headers.equals(other.headers);
+        return headers.equals(other.headers);
     }
     
 

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java Mon Sep  7 20:08:23 2009
@@ -56,4 +56,17 @@
         return map;
     }
     
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < values.size(); i++) {
+            if (values.get(i).length() == 0) {
+                continue;
+            }
+            sb.append(values.get(i));
+            if (i + 1 < values.size()) {
+                sb.append(';');
+            }
+        }
+        return sb.toString();
+    }
 }

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java Mon Sep  7 20:08:23 2009
@@ -24,7 +24,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-@Target({ElementType.PARAMETER, ElementType.FIELD })
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD })
 @Retention(RetentionPolicy.RUNTIME)
 public @interface Multipart {
     String value() default "root";

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java Mon Sep  7 20:08:23 2009
@@ -44,6 +44,13 @@
         this(atts, MULTIPART_RELATED_TYPE, outbound);
     }
     
+    public MultipartBody(Attachment att) {
+        atts = new ArrayList<Attachment>();
+        atts.add(att);
+        outbound = true;
+        this.mt = MULTIPART_RELATED_TYPE;
+    }
+    
     public MultipartBody(List<Attachment> atts) {
         this(atts, MULTIPART_RELATED_TYPE, false);
     }

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java Mon Sep  7 20:08:23 2009
@@ -173,6 +173,7 @@
                 ori = JAXRSUtils.findTargetMethod(resource, values.getFirst(URITemplate.FINAL_MATCH_GROUP), 
                                             httpMethod, values, requestContentType, acceptContentTypes);
                 message.getExchange().put(OperationResourceInfo.class, ori);
+                message.put(URITemplate.TEMPLATE_PARAMETERS, values);
             } catch (WebApplicationException ex) {
                 if (ex.getResponse() != null && ex.getResponse().getStatus() == 405 
                     && "OPTIONS".equalsIgnoreCase(httpMethod)) {

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java Mon Sep  7 20:08:23 2009
@@ -47,11 +47,11 @@
 import org.apache.cxf.jaxrs.utils.HttpUtils;
 import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
 
-@Produces("application/x-www-form-urlencoded")
+@Produces({"application/x-www-form-urlencoded", "multipart/form-data" })
 @Consumes({"application/x-www-form-urlencoded", "multipart/form-data" })
 @Provider
 public class FormEncodingProvider implements 
-    MessageBodyReader<Object>, MessageBodyWriter<MultivaluedMap<String, String>> {
+    MessageBodyReader<Object>, MessageBodyWriter<Object> {
         
     private FormValidator validator;
     @Context private MessageContext mc;
@@ -72,9 +72,7 @@
     
     public boolean isReadable(Class<?> type, Type genericType, 
                               Annotation[] annotations, MediaType mt) {
-        return MultivaluedMap.class.isAssignableFrom(type)
-               || mt.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)
-                  && MultipartBody.class.isAssignableFrom(type);
+        return isSupported(type, genericType, annotations, mt);
     }
 
     public Object readFrom(
@@ -133,7 +131,7 @@
         }
     }
 
-    public long getSize(MultivaluedMap<String, String> t, Class<?> type, 
+    public long getSize(Object t, Class<?> type, 
                         Type genericType, Annotation[] annotations, 
                         MediaType mediaType) {
         return -1;
@@ -141,26 +139,43 @@
 
     public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, 
                                MediaType mediaType) {
-        return MultivaluedMap.class.isAssignableFrom(type);
+        return isSupported(type, genericType, annotations, mediaType);
     }
 
-    public void writeTo(MultivaluedMap<String, String> map, Class<?> c, Type t, Annotation[] anns, 
+    private boolean isSupported(Class<?> type, Type genericType, Annotation[] annotations, 
+                                MediaType mt) {
+        return MultivaluedMap.class.isAssignableFrom(type)
+            || mt.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)
+            && MultipartBody.class.isAssignableFrom(type);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public void writeTo(Object obj, Class<?> c, Type t, Annotation[] anns, 
                         MediaType mt, MultivaluedMap<String, Object> headers, OutputStream os) 
         throws IOException, WebApplicationException {
-        boolean encoded = AnnotationUtils.getAnnotation(anns, Encoded.class) != null;
-        for (Iterator<Map.Entry<String, List<String>>> it = map.entrySet().iterator(); it.hasNext();) {
-            Map.Entry<String, List<String>> entry = it.next();
-            for (Iterator<String> entryIterator = entry.getValue().iterator(); entryIterator.hasNext();) {
-                String value = entryIterator.next();
-                os.write(entry.getKey().getBytes("UTF-8"));
-                os.write('=');
-                String data = encoded ? value : HttpUtils.urlEncode(value);
-                os.write(data.getBytes("UTF-8"));
-                if (entryIterator.hasNext() || it.hasNext()) {
-                    os.write('&');
+        
+        if (mt.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)) {
+            MultipartBody body = (MultipartBody)obj;
+            MultipartProvider provider = new MultipartProvider();
+            provider.setMessageContext(mc);
+            provider.writeTo(body, body.getClass(), body.getClass(), anns, mt, headers, os);
+        } else {
+            MultivaluedMap<String, String> map = (MultivaluedMap<String, String>)obj;
+            boolean encoded = AnnotationUtils.getAnnotation(anns, Encoded.class) != null;
+            for (Iterator<Map.Entry<String, List<String>>> it = map.entrySet().iterator(); it.hasNext();) {
+                Map.Entry<String, List<String>> entry = it.next();
+                for (Iterator<String> entryIterator = entry.getValue().iterator(); entryIterator.hasNext();) {
+                    String value = entryIterator.next();
+                    os.write(entry.getKey().getBytes("UTF-8"));
+                    os.write('=');
+                    String data = encoded ? value : HttpUtils.urlEncode(value);
+                    os.write(data.getBytes("UTF-8"));
+                    if (entryIterator.hasNext() || it.hasNext()) {
+                        os.write('&');
+                    }
                 }
+    
             }
-
         }
     }
 

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties Mon Sep  7 20:08:23 2009
@@ -21,4 +21,5 @@
 JAXB_EXCEPTION=JAXBException occurred : {0}
 UNSUPPORTED_ENCODING=Unsupported encoding : {0}, defaulting to UTF-8
 NO_COLLECTION_ROOT=No collection name is provided
+NO_MSG_WRITER =.No message body writer found for class : {0}.
 

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java Mon Sep  7 20:08:23 2009
@@ -21,36 +21,62 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.logging.Logger;
 
 import javax.activation.DataHandler;
 import javax.activation.DataSource;
 import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
 import javax.ws.rs.ext.Provider;
 
+import org.apache.cxf.attachment.AttachmentUtil;
+import org.apache.cxf.attachment.ByteDataSource;
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.jaxrs.ext.MessageContext;
 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource;
+import org.apache.cxf.jaxrs.ext.multipart.Multipart;
 import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
 import org.apache.cxf.jaxrs.utils.InjectionUtils;
 import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
 
 @Provider
 @Consumes({"multipart/related", "multipart/mixed", "multipart/alternative" })
-public class MultipartProvider implements MessageBodyReader<Object> {
+@Produces({"multipart/related", "multipart/mixed", "multipart/alternative" })
+public class MultipartProvider 
+    implements MessageBodyReader<Object>, MessageBodyWriter<Object> {
+    
+    private static final Logger LOG = LogUtils.getL7dLogger(MultipartProvider.class);
+    private static final ResourceBundle BUNDLE = BundleUtils.getBundle(MultipartProvider.class);
 
     @Context
     private MessageContext mc;
     private String attachmentDir;
     private String attachmentThreshold;
 
+    public void setMessageContext(MessageContext context) {
+        this.mc = context;
+    }
+    
     public void setAttachmentDirectory(String dir) {
         attachmentDir = dir;
     }
@@ -61,6 +87,11 @@
     
     public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, 
                               MediaType mt) {
+        return isSupported(type, genericType, annotations, mt);
+    }
+    
+    private boolean isSupported(Class<?> type, Type genericType, Annotation[] annotations, 
+                                MediaType mt) {
         if (DataHandler.class.isAssignableFrom(type) || DataSource.class.isAssignableFrom(type)
             || Attachment.class.isAssignableFrom(type) || MultipartBody.class.isAssignableFrom(type)
             || mediaTypeSupported(mt)) {
@@ -76,12 +107,13 @@
         List<Attachment> infos = 
             AttachmentUtils.getAttachments(mc, attachmentDir, attachmentThreshold);
         
-        if (List.class.isAssignableFrom(c)) {
+        if (Collection.class.isAssignableFrom(c)) {
             Class<?> actual = InjectionUtils.getActualType(t);
-            if (actual.isAssignableFrom(Attachment.class)) {
+            actual = actual != null ? actual : Object.class;
+            if (Attachment.class.isAssignableFrom(actual)) {
                 return infos;
             }
-            List<Object> objects = new ArrayList<Object>();
+            Collection<Object> objects = new ArrayList<Object>();
             for (Attachment a : infos) {
                 objects.add(fromAttachment(a, actual, actual, anns));
             }
@@ -122,4 +154,182 @@
         return mt.getType().equals("multipart") && (mt.getSubtype().equals("related") 
             || mt.getSubtype().equals("mixed") || mt.getSubtype().equals("alternative"));
     }
+
+    public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, 
+                        MediaType mediaType) {
+        return -1;
+    }
+
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations,
+                               MediaType mt) {
+        return isSupported(type, genericType, annotations, mt);
+    }
+
+    
+    public void writeTo(Object obj, Class<?> type, Type genericType, Annotation[] anns, MediaType mt,
+                        MultivaluedMap<String, Object> headers, OutputStream os) 
+        throws IOException, WebApplicationException {
+        
+        List<Attachment> handlers = convertToDataHandlers(obj, type, genericType, anns, mt);
+        mc.put(MultipartBody.OUTBOUND_MESSAGE_ATTACHMENTS, handlers);
+        handlers.get(0).getDataHandler().writeTo(os);
+    }
+    
+    @SuppressWarnings("unchecked")
+    private List<Attachment> convertToDataHandlers(Object obj,
+                                                   Class<?> type, Type genericType,                          
+                                                   Annotation[] anns, MediaType mt) {
+        if (Map.class.isAssignableFrom(obj.getClass())) {
+            Map<Object, Object> objects = (Map)obj;
+            List<Attachment> handlers = new ArrayList<Attachment>(objects.size());
+            int i = 0;
+            for (Iterator<Map.Entry<Object, Object>> iter = objects.entrySet().iterator(); 
+                iter.hasNext();) {
+                Map.Entry entry = iter.next();
+                Object value = entry.getValue();
+                Attachment handler = createDataHandler(value, value.getClass(), value.getClass(), 
+                                                       new Annotation[]{},
+                                                       entry.getKey().toString(),
+                                                       i++);
+                handlers.add(handler);
+            }
+            return handlers;
+        } else {
+            String rootMediaType = getRootMediaType(anns, mt); 
+            if (List.class.isAssignableFrom(obj.getClass())) {
+                return getAttachments((List)obj, rootMediaType);
+            } else {
+                if (MultipartBody.class.isAssignableFrom(type)) {
+                    List<Attachment> atts = ((MultipartBody)obj).getAllAttachments();
+                    // these attachments may have no DataHandlers, but objects only
+                    return getAttachments(atts, rootMediaType);
+                }
+                Attachment handler = createDataHandler(obj,
+                                                       type, genericType, anns,
+                                                       rootMediaType, 1);
+                return Collections.singletonList(handler);
+            }
+        }
+    }
+    
+    private List<Attachment> getAttachments(List<?> objects, String rootMediaType) {
+        List<Attachment> handlers = new ArrayList<Attachment>(objects.size());
+        for (int i = 0; i < objects.size(); i++) {
+            Object value = objects.get(i);
+            Attachment handler = createDataHandler(value,
+                                           value.getClass(), value.getClass(), new Annotation[]{},
+                                           rootMediaType, i);
+            handlers.add(handler);
+        }
+        return handlers;
+    }
+    
+    private Attachment createDataHandler(Object obj, 
+                                         Class<?> cls, Type genericType,
+                                         Annotation[] anns,
+                                         String mimeType, int id) {
+        DataHandler dh = null;
+        if (InputStream.class.isAssignableFrom(obj.getClass())) {
+            dh = createInputStreamDH((InputStream)obj, mimeType);
+        } else if (DataHandler.class.isAssignableFrom(obj.getClass())) {
+            dh = (DataHandler)obj;
+        } else if (DataSource.class.isAssignableFrom(obj.getClass())) {
+            dh = new DataHandler((DataSource)obj);
+        } else if (Attachment.class.isAssignableFrom(obj.getClass())) {
+            Attachment att = (Attachment)obj;
+            if (att.getObject() == null) {
+                return att;
+            }
+            dh = getHandlerForObject(att.getObject(), att.getObject().getClass(), 
+                                     att.getObject().getClass(), new Annotation[]{}, 
+                                     att.getContentType().toString(), id);
+            return new Attachment(att.getContentId(), dh, att.getHeaders());
+        } else if (byte[].class.isAssignableFrom(obj.getClass())) {
+            ByteDataSource source = new ByteDataSource((byte[])obj);
+            source.setContentType(mimeType);
+            dh = new DataHandler(source);
+        } else {
+            dh = getHandlerForObject(obj, cls, genericType, anns, mimeType, id);
+        }
+        String contentId = id == 0 ? AttachmentUtil.BODY_ATTACHMENT_ID : Integer.toString(id);
+        return new Attachment(contentId, dh, new MetadataMap<String, String>());
+    }
+    
+    @SuppressWarnings("unchecked")
+    private DataHandler getHandlerForObject(Object obj, 
+                                            Class<?> cls, Type genericType,
+                                            Annotation[] anns,
+                                            String mimeType, int id) {
+        MediaType mt = MediaType.valueOf(mimeType);
+        MessageBodyWriter<Object> r = 
+            (MessageBodyWriter)mc.getProviders().getMessageBodyWriter(cls, genericType, anns, mt);
+        if (r == null) {
+            org.apache.cxf.common.i18n.Message message = 
+                new org.apache.cxf.common.i18n.Message("NO_MSG_WRITER",
+                                                   BUNDLE,
+                                                   cls);
+            LOG.severe(message.toString());
+            throw new WebApplicationException(500);
+        }
+        return new MessageBodyWriterDataHandler(r, obj, cls, genericType, anns, mt);
+    }
+    
+    private DataHandler createInputStreamDH(InputStream is, String mimeType) {
+        return new DataHandler(new InputStreamDataSource(is, mimeType));
+    }
+    
+    private String getRootMediaType(Annotation[] anns, MediaType mt) {
+        String mimeType = mt.getParameters().get("type");
+        if (mimeType != null) {
+            return mimeType;
+        }
+        Multipart id = AnnotationUtils.getAnnotation(anns, Multipart.class);
+        if (id != null && !MediaType.WILDCARD.equals(id.type())) {
+            mimeType = id.type();
+        }
+        if (mimeType == null) {
+            mimeType = MediaType.APPLICATION_OCTET_STREAM;
+        }
+        return mimeType;
+    }
+    
+    private static class MessageBodyWriterDataHandler extends DataHandler {
+        private MessageBodyWriter<Object> writer;
+        private Object obj;
+        private Class<?> cls;
+        private Type genericType;
+        private Annotation[] anns;
+        private MediaType contentType;
+        public MessageBodyWriterDataHandler(MessageBodyWriter<Object> writer,
+                                            Object obj,
+                                            Class<?> cls,
+                                            Type genericType,
+                                            Annotation[] anns,
+                                            MediaType contentType) {
+            super(new ByteDataSource("1".getBytes()));
+            this.writer = writer;
+            this.obj = obj;
+            this.cls = cls;
+            this.genericType = genericType;
+            this.anns = anns;
+            this.contentType = contentType;
+        }
+        
+        @Override
+        public void writeTo(OutputStream os) {
+            try {
+                writer.writeTo(obj, cls, genericType, anns, contentType, 
+                               new MetadataMap<String, Object>(), os);
+            } catch (IOException ex) {
+                throw new WebApplicationException();
+            }
+        }
+        
+        @Override
+        public String getContentType() {
+            return contentType.toString();
+        }
+        
+        // TODO : throw UnsupportedOperationException for all other DataHandler methods
+    }
 }

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java Mon Sep  7 20:08:23 2009
@@ -198,7 +198,7 @@
         }
         if (!ParameterizedType.class.isAssignableFrom(genericType.getClass())) {
             Class<?> cls =  (Class<?>)genericType;
-            return cls.isArray() ? cls.getComponentType() : null;
+            return cls.isArray() ? cls.getComponentType() : cls;
         }
         ParameterizedType paramType = (ParameterizedType)genericType;
         Type[] types = paramType.getActualTypeArguments();

Modified: cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java (original)
+++ cxf/branches/2.2.x-fixes/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java Mon Sep  7 20:08:23 2009
@@ -699,7 +699,7 @@
         if (UriInfo.class.isAssignableFrom(clazz)) {
             o = createUriInfo(contextMessage);
         } else if (HttpHeaders.class.isAssignableFrom(clazz)) {
-            o = new HttpHeadersImpl(contextMessage);
+            o = createHttpHeaders(contextMessage);
         } else if (Request.class.isAssignableFrom(clazz)) {
             o = new RequestImpl(contextMessage);
         } else if (SecurityContext.class.isAssignableFrom(clazz)) {
@@ -726,6 +726,14 @@
         return new UriInfoImpl(m, templateParams);
     }
     
+    @SuppressWarnings("unchecked")
+    private static HttpHeaders createHttpHeaders(Message m) {
+        if (MessageUtils.isRequestor(m)) {
+            m = m.getExchange() != null ? m.getExchange().getOutMessage() : m;
+        }
+        return new HttpHeadersImpl(m);
+    }
+    
     public static ContextResolver<?> createContextResolver(Type genericType, Message m) {
         if (genericType instanceof ParameterizedType) {
             return ProviderFactory.getInstance(m).createContextResolver(

Modified: cxf/branches/2.2.x-fixes/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java (original)
+++ cxf/branches/2.2.x-fixes/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java Mon Sep  7 20:08:23 2009
@@ -857,6 +857,9 @@
         Map<String, List<String>> headers = getSetProtocolHeaders(message);
         for (String header : headers.keySet()) {
             List<String> headerList = headers.get(header);
+            if (HttpHeaderHelper.CONTENT_TYPE.equalsIgnoreCase(header)) {
+                continue;
+            }
             if (HttpHeaderHelper.COOKIE.equalsIgnoreCase(header)) {
                 for (String s : headerList) {
                     connection.addRequestProperty(HttpHeaderHelper.COOKIE, s);

Modified: cxf/branches/2.2.x-fixes/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java (original)
+++ cxf/branches/2.2.x-fixes/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java Mon Sep  7 20:08:23 2009
@@ -48,6 +48,7 @@
 import org.apache.cxf.ws.addressing.EndpointReferenceType;
 import org.easymock.classextension.EasyMock;
 import org.easymock.classextension.IMocksControl;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -138,6 +139,7 @@
         control = EasyMock.createNiceControl();
         HTTPConduit conduit = setUpConduit(true, false, false);
         Message message = new MessageImpl();
+        message.put("Content-Type", "text/xml;charset=utf8");
         setUpHeaders(message);
         conduit.prepare(message);
         verifySentMessage(conduit, message, true);
@@ -564,7 +566,7 @@
             connection.setRequestProperty(EasyMock.eq("Authorization"),
                                           EasyMock.eq("Basic Qko6dmFsdWU="));            
             EasyMock.expectLastCall();
-            connection.setRequestProperty(EasyMock.eq("content-type"),
+            connection.setRequestProperty(EasyMock.eq("Content-Type"),
                                           EasyMock.eq("text/xml;charset=utf8"));
             EasyMock.expectLastCall();
             connection.setRequestProperty(EasyMock.eq("Accept"),

Modified: cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java (original)
+++ cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java Mon Sep  7 20:08:23 2009
@@ -20,6 +20,17 @@
 package org.apache.cxf.systest.jaxrs;
 
 import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
 
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
@@ -27,7 +38,13 @@
 import org.apache.commons.httpclient.methods.RequestEntity;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+import org.apache.cxf.jaxrs.provider.JSONProvider;
 import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.transport.http.HTTPConduit;
 
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -178,10 +195,147 @@
         doAddBook(address, "attachmentData", 200);               
     }
     
+    @Test
+    public void testAddBookWebClient() {
+        InputStream is1 = 
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/add_book.txt");
+        String address = "http://localhost:9085/bookstore/books/jaxb";
+        WebClient client = WebClient.create(address);
+        client.type("multipart/related;type=text/xml").accept("text/xml");
+        Book book = client.post(is1, Book.class);
+        assertEquals("CXF in Action - 2", book.getName());
+    }
+    
+    @Test
+    public void testAddBookJaxbJsonImageWebClient() throws Exception {
+        String address = "http://localhost:9085/bookstore/books/jaxbjsonimage";
+        WebClient client = WebClient.create(address);
+        client.type("multipart/mixed").accept("multipart/mixed");
+        
+        Book jaxb = new Book("jaxb", 1L);
+        Book json = new Book("json", 2L);
+        InputStream is1 = 
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg");
+        Map<String, Object> objects = new LinkedHashMap<String, Object>();
+        objects.put(MediaType.APPLICATION_XML, jaxb);
+        objects.put(MediaType.APPLICATION_JSON, json);
+        objects.put(MediaType.APPLICATION_OCTET_STREAM, is1);
+        Collection<Attachment> coll = client.postAndGetCollection(objects, Attachment.class);
+        List<Attachment> result = new ArrayList<Attachment>(coll);
+        Book jaxb2 = readBookFromInputStream(result.get(0).getDataHandler().getInputStream());
+        assertEquals("jaxb", jaxb2.getName());
+        assertEquals(1L, jaxb2.getId());
+        Book json2 = readJSONBookFromInputStream(result.get(1).getDataHandler().getInputStream());
+        assertEquals("json", json2.getName());
+        assertEquals(2L, json2.getId());
+        InputStream is2 = (InputStream)result.get(2).getDataHandler().getInputStream();
+        byte[] image1 = IOUtils.readBytesFromStream(
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg"));
+        byte[] image2 = IOUtils.readBytesFromStream(is2);
+        assertTrue(Arrays.equals(image1, image2));
+    }
+    
+    @Test
+    public void testAddBookJaxbJsonImageAttachments() throws Exception {
+        String address = "http://localhost:9085/bookstore/books/jaxbimagejson";
+        WebClient client = WebClient.create(address);
+        client.type("multipart/mixed").accept("multipart/mixed");
+        
+        Book jaxb = new Book("jaxb", 1L);
+        Book json = new Book("json", 2L);
+        InputStream is1 = 
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg");
+        List<Attachment> objects = new ArrayList<Attachment>();
+        objects.add(new Attachment("theroot", MediaType.APPLICATION_XML, jaxb));
+        objects.add(new Attachment("thejson", MediaType.APPLICATION_JSON, json));
+        objects.add(new Attachment("theimage", MediaType.APPLICATION_OCTET_STREAM, is1));
+        Collection<Attachment> coll = client.postAndGetCollection(objects, Attachment.class);
+        List<Attachment> result = new ArrayList<Attachment>(coll);
+        Book jaxb2 = readBookFromInputStream(result.get(0).getDataHandler().getInputStream());
+        assertEquals("jaxb", jaxb2.getName());
+        assertEquals(1L, jaxb2.getId());
+        Book json2 = readJSONBookFromInputStream(result.get(1).getDataHandler().getInputStream());
+        assertEquals("json", json2.getName());
+        assertEquals(2L, json2.getId());
+        InputStream is2 = (InputStream)result.get(2).getDataHandler().getInputStream();
+        byte[] image1 = IOUtils.readBytesFromStream(
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg"));
+        byte[] image2 = IOUtils.readBytesFromStream(is2);
+        assertTrue(Arrays.equals(image1, image2));
+    }
+    
+    @Test
+    public void testAddGetJaxbBooksWebClient() throws Exception {
+        String address = "http://localhost:9085/bookstore/books/jaxbonly";
+        WebClient client = WebClient.create(address);
+        HTTPConduit conduit = WebClient.getConfig(client).getHttpConduit();
+        conduit.getClient().setReceiveTimeout(1000000);
+        conduit.getClient().setConnectionTimeout(1000000);
+        client.type("multipart/mixed;type=application/xml").accept("multipart/mixed");
+        
+        Book b = new Book("jaxb", 1L);
+        Book b2 = new Book("jaxb2", 2L);
+        List<Book> books = new ArrayList<Book>();
+        books.add(b);
+        books.add(b2);
+        Collection<Book> coll = client.postAndGetCollection(books, Book.class);
+        List<Book> result = new ArrayList<Book>(coll);
+        Book jaxb = result.get(0);
+        assertEquals("jaxb", jaxb.getName());
+        assertEquals(1L, jaxb.getId());
+        Book jaxb2 = result.get(1);
+        assertEquals("jaxb2", jaxb2.getName());
+        assertEquals(2L, jaxb2.getId());
+    }
+    
+    @Test
+    public void testAddGetImageWebClient() throws Exception {
+        InputStream is1 = 
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg");
+        String address = "http://localhost:9085/bookstore/books/image";
+        WebClient client = WebClient.create(address);
+        HTTPConduit conduit = WebClient.getConfig(client).getHttpConduit();
+        conduit.getClient().setReceiveTimeout(1000000);
+        conduit.getClient().setConnectionTimeout(1000000);
+        client.type("multipart/mixed").accept("multipart/mixed");
+        InputStream is2 = client.post(is1, InputStream.class);
+        byte[] image1 = IOUtils.readBytesFromStream(
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg"));
+        byte[] image2 = IOUtils.readBytesFromStream(is2);
+        assertTrue(Arrays.equals(image1, image2));
+        
+    }
+    
+    @Test
+    public void testUploadImageFromForm() throws Exception {
+        InputStream is1 = 
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg");
+        String address = "http://localhost:9085/bookstore/books/formimage";
+        WebClient client = WebClient.create(address);
+        HTTPConduit conduit = WebClient.getConfig(client).getHttpConduit();
+        conduit.getClient().setReceiveTimeout(1000000);
+        conduit.getClient().setConnectionTimeout(1000000);
+        client.type("multipart/form-data").accept("multipart/form-data");
+        
+        ContentDisposition cd = new ContentDisposition("attachment;filename=java.jpg");
+        Attachment att = new Attachment("image", is1, cd);
+        
+        MultipartBody body = new MultipartBody(att);
+        MultipartBody body2 = client.post(body, MultipartBody.class);
+        InputStream is2 = body2.getRootAttachment().getDataHandler().getInputStream();
+        byte[] image1 = IOUtils.readBytesFromStream(
+            getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/java.jpg"));
+        byte[] image2 = IOUtils.readBytesFromStream(is2);
+        assertTrue(Arrays.equals(image1, image2));
+        ContentDisposition cd2 = body2.getRootAttachment().getContentDisposition();
+        assertEquals("attachment;filename=java.jpg", cd2.toString());
+        assertEquals("java.jpg", cd.getParameter("filename"));
+    }
+    
     private void doAddBook(String address, String resourceName, int status) throws Exception {
         doAddBook("multipart/related", address, resourceName, status);
     }
-    
+
     private void doAddBook(String type, String address, String resourceName, int status) throws Exception {
         PostMethod post = new PostMethod(address);
         
@@ -215,4 +369,17 @@
         return bos.getOut().toString();        
     }
 
+    private Book readBookFromInputStream(InputStream is) throws Exception {
+        JAXBContext c = JAXBContext.newInstance(new Class[]{Book.class});
+        Unmarshaller u = c.createUnmarshaller();
+        return (Book)u.unmarshal(is);
+    }
+    
+    @SuppressWarnings("unchecked")
+    private Book readJSONBookFromInputStream(InputStream is) throws Exception {
+        JSONProvider provider = new JSONProvider();
+        return (Book)provider.readFrom((Class)Book.class, Book.class, new Annotation[]{}, 
+                                 MediaType.APPLICATION_JSON_TYPE, null, is);
+        
+    }
 }

Modified: cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java
URL: http://svn.apache.org/viewvc/cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java?rev=812265&r1=812264&r2=812265&view=diff
==============================================================================
--- cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java (original)
+++ cxf/branches/2.2.x-fixes/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java Mon Sep  7 20:08:23 2009
@@ -20,8 +20,11 @@
 package org.apache.cxf.systest.jaxrs;
 
 
+import java.io.ByteArrayInputStream;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -56,6 +59,52 @@
     }
     
     @POST
+    @Path("/books/image")
+    @Consumes("multipart/mixed")
+    @Produces("multipart/mixed")
+    public byte[] addBookImage(byte[] image) throws Exception {
+        return image;
+    }
+    
+    @POST
+    @Path("/books/formimage")
+    @Consumes("multipart/form-data")
+    @Produces("multipart/form-data")
+    public MultipartBody addBookFormImage(MultipartBody image) throws Exception {
+        return image;
+    }
+    
+    @POST
+    @Path("/books/jaxbjsonimage")
+    @Consumes("multipart/mixed")
+    @Produces("multipart/mixed")
+    public Map<String, Object> addBookJaxbJsonImage(@Multipart("root.message@cxf.apache.org") Book jaxb, 
+                                                    @Multipart("1") Book json, 
+                                                    @Multipart("2") byte[] image) throws Exception {
+        Map<String, Object> objects = new LinkedHashMap<String, Object>();
+        objects.put("application/xml", jaxb);
+        objects.put("application/json", json);
+        objects.put("application/octet-stream", new ByteArrayInputStream(image));
+        return objects;
+        
+    }
+    
+    @POST
+    @Path("/books/jaxbimagejson")
+    @Consumes("multipart/mixed")
+    @Produces("multipart/mixed")
+    public Map<String, Object> addBookJaxbJsonImage2(@Multipart("theroot") Book jaxb, 
+                                                     @Multipart("thejson") Book json, 
+                                                     @Multipart("theimage") byte[] image) throws Exception {
+        Map<String, Object> objects = new LinkedHashMap<String, Object>();
+        objects.put("application/xml", jaxb);
+        objects.put("application/json", json);
+        objects.put("application/octet-stream", new ByteArrayInputStream(image));
+        return objects;
+        
+    }
+    
+    @POST
     @Path("/books/stream")
     @Produces("text/xml")
     public Response addBookFromStream(StreamSource source) throws Exception {
@@ -149,6 +198,16 @@
     }
     
     @POST
+    @Path("/books/jaxbonly")
+    @Consumes("multipart/mixed")
+    @Produces("multipart/mixed;type=text/xml")
+    public List<Book> addBooks(List<Book> books) {
+        List<Book> books2 = new ArrayList<Book>();
+        books2.addAll(books);
+        return books2;
+    }
+    
+    @POST
     @Path("/books/jaxbjson")
     @Produces("text/xml")
     public Response addBookJaxbJson(