Return-Path: Delivered-To: apmail-cxf-commits-archive@www.apache.org Received: (qmail 70476 invoked from network); 7 Sep 2009 17:26:09 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 7 Sep 2009 17:26:09 -0000 Received: (qmail 81128 invoked by uid 500); 7 Sep 2009 17:26:09 -0000 Delivered-To: apmail-cxf-commits-archive@cxf.apache.org Received: (qmail 81041 invoked by uid 500); 7 Sep 2009 17:26:09 -0000 Mailing-List: contact commits-help@cxf.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cxf.apache.org Delivered-To: mailing list commits@cxf.apache.org Received: (qmail 81032 invoked by uid 99); 7 Sep 2009 17:26:09 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 07 Sep 2009 17:26:09 +0000 X-ASF-Spam-Status: No, hits=-1998.5 required=10.0 tests=ALL_TRUSTED,WEIRD_PORT X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 07 Sep 2009 17:26:03 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 90A172388897; Mon, 7 Sep 2009 17:25:42 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r812232 - in /cxf/trunk: 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/apache/cxf/jaxrs/ex... Date: Mon, 07 Sep 2009 17:25:42 -0000 To: commits@cxf.apache.org From: sergeyb@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20090907172542.90A172388897@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: sergeyb Date: Mon Sep 7 17:25:40 2009 New Revision: 812232 URL: http://svn.apache.org/viewvc?rev=812232&view=rev Log: [CXF-2270] : support for writing attachments for JAXRS Added: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java (with props) cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java.jpg (with props) Modified: cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java cxf/trunk/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/Messages.properties cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java Modified: cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java (original) +++ cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentSerializer.java Mon Sep 7 17:25:40 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> rootHeaders = Collections.emptyMap(); private boolean xop = true; public AttachmentSerializer(Message messageParam) { message = messageParam; } + public AttachmentSerializer(Message messageParam, String multipartType, + Map> 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 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> 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> 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 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> headers = null; + Iterator it = a.getHeaderNames(); + if (it.hasNext()) { + headers = new LinkedHashMap>(); + 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/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java (original) +++ cxf/trunk/rt/core/src/main/java/org/apache/cxf/attachment/AttachmentUtil.java Mon Sep 7 17:25:40 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/trunk/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java (original) +++ cxf/trunk/rt/core/src/main/java/org/apache/cxf/interceptor/AttachmentOutInterceptor.java Mon Sep 7 17:25:40 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> getRootHeaders() { + return Collections.emptyMap(); + } public class AttachmentOutEndingInterceptor extends AbstractPhaseInterceptor { public AttachmentOutEndingInterceptor() { @@ -85,4 +97,6 @@ } } + + } Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java Mon Sep 7 17:25:40 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(r.getMetadata(), true, true), inputStream); } catch (Exception ex) { - throw new WebApplicationException(); + throw new WebApplicationException(ex); } } else if (cls == Response.class) { Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java Mon Sep 7 17:25:40 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> 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 invoke(String httpMethod, Object body, Class 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 Collection invokeAndGetCollection(String httpMethod, Object body, + Class 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 Collection postAndGetCollection(Object body, Class 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 Collection getCollection(Class 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 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 headers, Object body, Class responseClass) { + MultivaluedMap 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/MessageContextImpl.java Mon Sep 7 17:25:40 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 atts = + new ArrayList(); + + 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 newAttachments = new LinkedList(); 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 childAttachments = m.getAttachments(); + Collection 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Attachment.java Mon Sep 7 17:25:40 2009 @@ -32,22 +32,26 @@ public class Attachment { private DataHandler handler; - private MultivaluedMap headers = new MetadataMap(); - private String contentId; + private MultivaluedMap headers = + new MetadataMap(false, true); + private Object object; public Attachment(org.apache.cxf.message.Attachment a) { handler = a.getDataHandler(); - contentId = a.getId(); for (Iterator 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 headers) { handler = dh; - contentId = id; - this.headers = new MetadataMap(headers); + this.headers = new MetadataMap(headers, false, true); + this.headers.putSingle("Content-ID", id); } public Attachment(String id, DataSource ds, MultivaluedMap 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 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 getHeaderAsList(String name) { - List header = headers.get(name); - return header == null ? headers.get(name.toLowerCase()) : header; + return headers.get(name); } public MultivaluedMap 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/ContentDisposition.java Mon Sep 7 17:25:40 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/Multipart.java Mon Sep 7 17:25:40 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/ext/multipart/MultipartBody.java Mon Sep 7 17:25:40 2009 @@ -44,6 +44,13 @@ this(atts, MULTIPART_RELATED_TYPE, outbound); } + public MultipartBody(Attachment att) { + atts = new ArrayList(); + atts.add(att); + outbound = true; + this.mt = MULTIPART_RELATED_TYPE; + } + public MultipartBody(List atts) { this(atts, MULTIPART_RELATED_TYPE, false); } Added: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java?rev=812232&view=auto ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java (added) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java Mon Sep 7 17:25:40 2009 @@ -0,0 +1,46 @@ +/** + * 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.interceptor; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.cxf.interceptor.AttachmentOutInterceptor; + +public class AttachmentOutputInterceptor extends AttachmentOutInterceptor { + + private String multipartType; + private Map> rootHeaders; + + public AttachmentOutputInterceptor(String multipartType, + Map> headers) { + this.multipartType = multipartType; + this.rootHeaders = headers; + } + + protected String getMultipartType() { + return multipartType; + } + + protected Map> getRootHeaders() { + return Collections.unmodifiableMap(rootHeaders); + } + +} Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/AttachmentOutputInterceptor.java ------------------------------------------------------------------------------ svn:keywords = Rev Date Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java Mon Sep 7 17:25:40 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/FormEncodingProvider.java Mon Sep 7 17:25:40 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, MessageBodyWriter> { + MessageBodyReader, MessageBodyWriter { 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 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 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 headers, OutputStream os) throws IOException, WebApplicationException { - boolean encoded = AnnotationUtils.getAnnotation(anns, Encoded.class) != null; - for (Iterator>> it = map.entrySet().iterator(); it.hasNext();) { - Map.Entry> entry = it.next(); - for (Iterator 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 map = (MultivaluedMap)obj; + boolean encoded = AnnotationUtils.getAnnotation(anns, Encoded.class) != null; + for (Iterator>> it = map.entrySet().iterator(); it.hasNext();) { + Map.Entry> entry = it.next(); + for (Iterator 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/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=812232&r1=812231&r2=812232&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 Mon Sep 7 17:25:40 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/MultipartProvider.java Mon Sep 7 17:25:40 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 { +@Produces({"multipart/related", "multipart/mixed", "multipart/alternative" }) +public class MultipartProvider + implements MessageBodyReader, MessageBodyWriter { + + 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 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 objects = new ArrayList(); + Collection objects = new ArrayList(); 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 headers, OutputStream os) + throws IOException, WebApplicationException { + + List handlers = convertToDataHandlers(obj, type, genericType, anns, mt); + mc.put(MultipartBody.OUTBOUND_MESSAGE_ATTACHMENTS, handlers); + handlers.get(0).getDataHandler().writeTo(os); + } + + @SuppressWarnings("unchecked") + private List convertToDataHandlers(Object obj, + Class type, Type genericType, + Annotation[] anns, MediaType mt) { + if (Map.class.isAssignableFrom(obj.getClass())) { + Map objects = (Map)obj; + List handlers = new ArrayList(objects.size()); + int i = 0; + for (Iterator> 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 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 getAttachments(List objects, String rootMediaType) { + List handlers = new ArrayList(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()); + } + + @SuppressWarnings("unchecked") + private DataHandler getHandlerForObject(Object obj, + Class cls, Type genericType, + Annotation[] anns, + String mimeType, int id) { + MediaType mt = MediaType.valueOf(mimeType); + MessageBodyWriter 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 writer; + private Object obj; + private Class cls; + private Type genericType; + private Annotation[] anns; + private MediaType contentType; + public MessageBodyWriterDataHandler(MessageBodyWriter 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(), os); + } catch (IOException ex) { + throw new WebApplicationException(); + } + } + + @Override + public String getContentType() { + return contentType.toString(); + } + + // TODO : throw UnsupportedOperationException for all other DataHandler methods + } } Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java Mon Sep 7 17:25:40 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/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java (original) +++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java Mon Sep 7 17:25:40 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/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java (original) +++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java Mon Sep 7 17:25:40 2009 @@ -857,6 +857,9 @@ Map> headers = getSetProtocolHeaders(message); for (String header : headers.keySet()) { List 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/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java (original) +++ cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitURLEasyMockTest.java Mon Sep 7 17:25:40 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/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java (original) +++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSMultipartTest.java Mon Sep 7 17:25:40 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 objects = new LinkedHashMap(); + objects.put(MediaType.APPLICATION_XML, jaxb); + objects.put(MediaType.APPLICATION_JSON, json); + objects.put(MediaType.APPLICATION_OCTET_STREAM, is1); + Collection coll = client.postAndGetCollection(objects, Attachment.class); + List result = new ArrayList(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 objects = new ArrayList(); + 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 coll = client.postAndGetCollection(objects, Attachment.class); + List result = new ArrayList(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 books = new ArrayList(); + books.add(b); + books.add(b2); + Collection coll = client.postAndGetCollection(books, Book.class); + List result = new ArrayList(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/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java?rev=812232&r1=812231&r2=812232&view=diff ============================================================================== --- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java (original) +++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/MultipartStore.java Mon Sep 7 17:25:40 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 addBookJaxbJsonImage(@Multipart("root.message@cxf.apache.org") Book jaxb, + @Multipart("1") Book json, + @Multipart("2") byte[] image) throws Exception { + Map objects = new LinkedHashMap(); + 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 addBookJaxbJsonImage2(@Multipart("theroot") Book jaxb, + @Multipart("thejson") Book json, + @Multipart("theimage") byte[] image) throws Exception { + Map objects = new LinkedHashMap(); + 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 addBooks(List books) { + List books2 = new ArrayList(); + books2.addAll(books); + return books2; + } + + @POST @Path("/books/jaxbjson") @Produces("text/xml") public Response addBookJaxbJson( Added: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java.jpg URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java.jpg?rev=812232&view=auto ============================================================================== Binary file - no diff available. Propchange: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/resources/java.jpg ------------------------------------------------------------------------------ svn:mime-type = image/jpeg