Author: awiner
Date: Thu Feb 26 19:29:46 2009
New Revision: 748277

URL: http://svn.apache.org/viewvc?rev=748277&view=rev
Log:
SHINDIG-938: Multipart/form-data support for Conent Upload using JsonRPC
- Patch from Sachin Shenoy
- Minor changes from me (mostly moving isMultipartForm() off JsonRpcServlet and 
onto MultipartFormParser)

Added:
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/CommonsFormDataItem.java
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParser.java
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/FormDataItem.java
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/MultipartFormParser.java
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/multipart/
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParserTest.java
Modified:
    incubator/shindig/trunk/java/common/pom.xml
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/BaseRequestItem.java
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RequestItem.java
    
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RpcHandler.java
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/BaseRequestItemTest.java
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
    
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/JsonRpcServletTest.java
    
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SocialRequestItem.java
    incubator/shindig/trunk/pom.xml

Modified: incubator/shindig/trunk/java/common/pom.xml
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/pom.xml?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- incubator/shindig/trunk/java/common/pom.xml (original)
+++ incubator/shindig/trunk/java/common/pom.xml Thu Feb 26 19:29:46 2009
@@ -85,6 +85,10 @@
       <artifactId>commons-codec</artifactId>
     </dependency>
     <dependency>
+      <groupId>commons-fileupload</groupId>
+      <artifactId>commons-fileupload</artifactId>
+    </dependency>
+    <dependency>
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
     </dependency>

Modified: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/BaseRequestItem.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/BaseRequestItem.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/BaseRequestItem.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/BaseRequestItem.java
 Thu Feb 26 19:29:46 2009
@@ -22,6 +22,7 @@
 import org.apache.shindig.protocol.conversion.BeanJsonConverter;
 import org.apache.shindig.protocol.model.FilterOperation;
 import org.apache.shindig.protocol.model.SortOrder;
+import org.apache.shindig.protocol.multipart.FormDataItem;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
@@ -47,6 +48,7 @@
   protected final SecurityToken token;
   final BeanConverter converter;
   final Map<String,Object> parameters;
+  final Map<String, FormDataItem> formItems;
   final BeanJsonConverter jsonConverter;
 
   public BaseRequestItem(Map<String, String[]> parameters,
@@ -66,9 +68,11 @@
       }
     }
     this.jsonConverter = jsonConverter;
+    this.formItems = null;
   }
 
   public BaseRequestItem(JSONObject parameters,
+      Map<String, FormDataItem> formItems,
       SecurityToken token,
       BeanConverter converter,
       BeanJsonConverter jsonConverter) {
@@ -83,6 +87,7 @@
       }
       this.token = token;
       this.converter = converter;
+      this.formItems = formItems;
     } catch (JSONException je) {
       throw new ProtocolException(ResponseError.INTERNAL_ERROR, 
je.getMessage(), je);
     }
@@ -272,4 +277,12 @@
       this.parameters.put(paramName, paramValue);
     }
   }
+  
+  public FormDataItem getFormMimePart(String partName) {
+    if (formItems != null) {
+      return formItems.get(partName);
+    } else {
+      return null;
+    }
+  }
 }

Modified: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
 Thu Feb 26 19:29:46 2009
@@ -24,6 +24,7 @@
 import org.apache.shindig.common.util.ImmediateFuture;
 import org.apache.shindig.protocol.conversion.BeanConverter;
 import org.apache.shindig.protocol.conversion.BeanJsonConverter;
+import org.apache.shindig.protocol.multipart.FormDataItem;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -280,10 +281,11 @@
       this.methodCaller = methodCaller;
     }
 
-    public Future<?> execute(JSONObject rpc, SecurityToken token, 
BeanConverter converter) {
+    public Future<?> execute(JSONObject rpc, Map<String, FormDataItem> 
formItems,
+        SecurityToken token, BeanConverter converter) {
       try {
         JSONObject params = rpc.has("params") ? (JSONObject)rpc.get("params") 
: new JSONObject();
-        RequestItem item = methodCaller.getRpcRequestItem(params, token, 
beanJsonConverter);
+        RequestItem item = methodCaller.getRpcRequestItem(params, formItems, 
token, beanJsonConverter);
 
         listener.executing(item);
         return methodCaller.call(handlerProvider.get(), item);
@@ -306,8 +308,9 @@
       this.rpc = rpc;
     }
 
-    public Future<?> execute(SecurityToken st, BeanConverter converter) {
-      return handler.execute(rpc, st, converter);
+    public Future<?> execute(Map<String, FormDataItem> formItems, 
SecurityToken st,
+        BeanConverter converter) {
+      return handler.execute(rpc, formItems, st, converter);
     }
   }
 
@@ -410,7 +413,7 @@
       restRequestItemConstructor = requestItemType.getConstructor(Map.class,
           SecurityToken.class, BeanConverter.class, BeanJsonConverter.class);
       rpcRequestItemConstructor = 
requestItemType.getConstructor(JSONObject.class,
-          SecurityToken.class, BeanConverter.class, BeanJsonConverter.class);
+          Map.class, SecurityToken.class, BeanConverter.class, 
BeanJsonConverter.class);
     }
 
     public RequestItem getRestRequestItem(Map<String, String[]> params, 
SecurityToken token,
@@ -418,15 +421,30 @@
       return getRequestItem(params, token, converter, jsonConverter, 
restRequestItemConstructor);
     }
     
-    public RequestItem getRpcRequestItem(JSONObject params, SecurityToken 
token, BeanJsonConverter converter) {
-      return getRequestItem(params, token, converter, converter, 
rpcRequestItemConstructor);
+    public RequestItem getRpcRequestItem(JSONObject params, Map<String, 
FormDataItem> formItems, 
+        SecurityToken token, BeanJsonConverter converter) {
+      return getRequestItem(params, formItems, token, converter, converter, 
rpcRequestItemConstructor);
+    }
+    
+    private RequestItem getRequestItem(Object params, Map<String, 
FormDataItem> formItems, 
+        SecurityToken token, BeanConverter converter, BeanJsonConverter 
jsonConverter,
+        Constructor<?> constructor) {
+      try {
+        return (RequestItem) constructor.newInstance(params, formItems, token, 
 converter,
+            jsonConverter);
+      } catch (InstantiationException e) {
+        throw new RuntimeException(e);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e);
+      }
     }
     
     private RequestItem getRequestItem(Object params, SecurityToken token, 
BeanConverter converter,
         BeanJsonConverter jsonConverter, Constructor<?> constructor) {
       try {
-        return (RequestItem) constructor.newInstance(params, token,  converter,
-            jsonConverter);
+        return (RequestItem) constructor.newInstance(params, token,  
converter, jsonConverter);
       } catch (InstantiationException e) {
         throw new RuntimeException(e);
       } catch (IllegalAccessException e) {
@@ -488,7 +506,8 @@
       this.error = error;
     }
 
-    public Future<?> execute(SecurityToken token, BeanConverter converter) {
+    public Future<?> execute(Map<String, FormDataItem> formItems, 
SecurityToken token,
+        BeanConverter converter) {
       return ImmediateFuture.errorInstance(error);
     }
   }

Modified: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/JsonRpcServlet.java
 Thu Feb 26 19:29:46 2009
@@ -19,9 +19,12 @@
 
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.common.util.JsonConversionUtil;
+import org.apache.shindig.protocol.multipart.FormDataItem;
+import org.apache.shindig.protocol.multipart.MultipartFormParser;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.inject.Inject;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
@@ -44,6 +47,20 @@
  */
 public class JsonRpcServlet extends ApiServlet {
 
+  /**
+   * In a multipart request, the form item with field name "request" will 
contain the
+   * actual request, per the proposed Opensocial 0.9 specification.
+   */
+  public static final String REQUEST_PARAM = "request";
+  private static final String CONTENT_TYPE_JSON = "application/json";
+  
+  private MultipartFormParser formParser;
+
+  @Inject
+  void setMultipartFormParser(MultipartFormParser formParser) {
+    this.formParser = formParser;
+  }
+  
   @Override
   protected void doGet(HttpServletRequest servletRequest, HttpServletResponse 
servletResponse)
       throws IOException {
@@ -56,7 +73,7 @@
     try {
       setCharacterEncodings(servletRequest, servletResponse);
       JSONObject request = JsonConversionUtil.fromRequest(servletRequest);
-      dispatch(request, servletRequest, servletResponse, token);
+      dispatch(request, null, servletRequest, servletResponse, token);
     } catch (JSONException je) {
       // FIXME
     }
@@ -72,26 +89,53 @@
     }
 
     setCharacterEncodings(servletRequest, servletResponse);
-    servletResponse.setContentType("application/json");
+    servletResponse.setContentType(CONTENT_TYPE_JSON);
 
     try {
-      String content = IOUtils.toString(servletRequest.getInputStream(),
-          servletRequest.getCharacterEncoding());
+      String content = null;
+      Map<String, FormDataItem> formItems = new HashMap<String, 
FormDataItem>();
+      
+      if (formParser.isMultipartContent(servletRequest)) {
+        for (FormDataItem item : formParser.parse(servletRequest)) {
+          if (item.isFormField() && content == null) {
+            // As per spec, in case of a multipart/form-data content, there 
will be one form field
+            // with field name as "request". It will contain the json request. 
Any further form
+            // field or file item will not be parsed out, but will be exposed 
via getFormItem
+            // method of RequestItem. Here we are lenient where a mime part 
which has content type
+            // application/json will be considered as request.
+            if (REQUEST_PARAM.equals(item.getFieldName()) ||
+                CONTENT_TYPE_JSON.equals(item.getContentType())) {
+              content = IOUtils.toString(item.getInputStream());
+            }
+          } else {
+            formItems.put(item.getFieldName(), item);
+          }
+        }
+        
+        if (content == null) {
+          content = "";
+        }
+      } else {
+        content = IOUtils.toString(servletRequest.getInputStream(),
+            servletRequest.getCharacterEncoding());
+      }
+
       if ((content.indexOf('[') != -1) && content.indexOf('[') < 
content.indexOf('{')) {
         // Is a batch
         JSONArray batch = new JSONArray(content);
-        dispatchBatch(batch, servletRequest, servletResponse, token);
+        dispatchBatch(batch, formItems, servletRequest, servletResponse, 
token);
       } else {
         JSONObject request = new JSONObject(content);
-        dispatch(request, servletRequest, servletResponse, token);
+        dispatch(request, formItems, servletRequest, servletResponse, token);
       }
     } catch (JSONException je) {
       sendBadRequest(je, servletResponse);
     }
   }
 
-  protected void dispatchBatch(JSONArray batch, HttpServletRequest 
servletRequest,
-      HttpServletResponse servletResponse, SecurityToken token) throws 
JSONException, IOException {
+  protected void dispatchBatch(JSONArray batch, Map<String, FormDataItem> 
formItems ,
+      HttpServletRequest servletRequest, HttpServletResponse servletResponse,
+      SecurityToken token) throws JSONException, IOException {
     // Use linked hash map to preserve order
     List<Future<?>> responses = 
Lists.newArrayListWithExpectedSize(batch.length());
 
@@ -101,7 +145,7 @@
     // into single requests.
     for (int i = 0; i < batch.length(); i++) {
       JSONObject batchObj = batch.getJSONObject(i);
-      responses.add(getHandler(batchObj, servletRequest).execute(token, 
jsonConverter));
+      responses.add(getHandler(batchObj, servletRequest).execute(formItems, 
token, jsonConverter));
     }
 
     // Resolve each Future into a response.
@@ -119,15 +163,16 @@
     jsonConverter.append(servletResponse.getWriter(), result);
   }
 
-  protected void dispatch(JSONObject request, HttpServletRequest 
servletRequest,
-      HttpServletResponse servletResponse, SecurityToken token) throws 
JSONException, IOException {
+  protected void dispatch(JSONObject request, Map<String, FormDataItem> 
formItems,
+      HttpServletRequest servletRequest, HttpServletResponse servletResponse,
+      SecurityToken token) throws JSONException, IOException {
     String key = null;
     if (request.has("id")) {
       key = request.getString("id");
     }
 
     // getRpcHandler never returns null
-    Future<?> future = getHandler(request, servletRequest).execute(token, 
jsonConverter);
+    Future<?> future = getHandler(request, servletRequest).execute(formItems, 
token, jsonConverter);
 
     // Resolve each Future into a response.
     // TODO: should use shared deadline across each request

Modified: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RequestItem.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RequestItem.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RequestItem.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RequestItem.java
 Thu Feb 26 19:29:46 2009
@@ -20,6 +20,7 @@
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.protocol.model.FilterOperation;
 import org.apache.shindig.protocol.model.SortOrder;
+import org.apache.shindig.protocol.multipart.FormDataItem;
 
 import java.util.Date;
 import java.util.List;
@@ -77,4 +78,6 @@
   String getParameter(String paramName, String defaultValue);
 
   List<String> getListParameter(String paramName);
+  
+  FormDataItem getFormMimePart(String partName);
 }

Modified: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RpcHandler.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RpcHandler.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RpcHandler.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/RpcHandler.java
 Thu Feb 26 19:29:46 2009
@@ -20,7 +20,9 @@
 
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.protocol.conversion.BeanConverter;
+import org.apache.shindig.protocol.multipart.FormDataItem;
 
+import java.util.Map;
 import java.util.concurrent.Future;
 
 /**
@@ -30,7 +32,7 @@
 
   /**
    * Handle the request and return a Future from which the response object
-   * can be retrieved
+   * can be retrieved.
    */
-  Future<?> execute(SecurityToken st, BeanConverter converter);
+  Future<?> execute(Map<String, FormDataItem> formItems, SecurityToken st, 
BeanConverter converter);
 }

Added: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/CommonsFormDataItem.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/CommonsFormDataItem.java?rev=748277&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/CommonsFormDataItem.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/CommonsFormDataItem.java
 Thu Feb 26 19:29:46 2009
@@ -0,0 +1,62 @@
+/*
+ * 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.shindig.protocol.multipart;
+
+import org.apache.commons.fileupload.FileItem;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Implementation of FormDataItem using Apache commons FileItem.
+ */
+class CommonsFormDataItem implements FormDataItem {
+  private final FileItem fileItem;
+  
+  CommonsFormDataItem(FileItem fileItem) {
+    this.fileItem = fileItem;
+  }
+  
+  public byte[] get() {
+    return fileItem.get();
+  }
+
+  public String getContentType() {
+    return fileItem.getContentType();
+  }
+
+  public String getFieldName() {
+    return fileItem.getFieldName();
+  }
+
+  public InputStream getInputStream() throws IOException {
+    return fileItem.getInputStream();
+  }
+
+  public String getName() {
+    return fileItem.getName();
+  }
+
+  public long getSize() {
+    return fileItem.getSize();
+  }
+
+  public boolean isFormField() {
+    return fileItem.isFormField();
+  }
+}

Added: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParser.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParser.java?rev=748277&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParser.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParser.java
 Thu Feb 26 19:29:46 2009
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.shindig.protocol.multipart;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+
+import java.io.IOException;
+import java.net.UnknownServiceException;
+import java.util.Collection;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Implementation of MultipartFormParser using Apache Commons file upload.
+ */
+public class DefaultMultipartFormParser implements MultipartFormParser {
+  private static final String MULTIPART = "multipart/";
+
+  public Collection<FormDataItem> parse(HttpServletRequest servletRequest)
+      throws IOException  {
+    FileItemFactory factory = new DiskFileItemFactory();
+    ServletFileUpload upload = new ServletFileUpload(factory);
+
+    try {
+      @SuppressWarnings("unchecked")
+      List<FileItem> fileItems = upload.parseRequest(servletRequest);
+      return convertToFormData(fileItems);
+    } catch (FileUploadException e) {
+      UnknownServiceException use = new UnknownServiceException("File upload 
error.");
+      use.initCause(e);
+      throw use; 
+    }
+  }
+  
+  private Collection<FormDataItem> convertToFormData(List<FileItem> fileItems) 
{
+    List<FormDataItem> formDataItems = 
+        Lists.newArrayListWithCapacity(fileItems.size());
+    for (FileItem item : fileItems) {
+      formDataItems.add(new CommonsFormDataItem(item));
+    }
+    
+    return formDataItems;
+  }
+
+  public boolean isMultipartContent(HttpServletRequest request) {
+    if (!"POST".equals(request.getMethod())) {
+      return false;
+    }
+    String contentType = request.getContentType();
+    if (contentType == null) {
+      return false;
+    }
+    if (contentType.toLowerCase().startsWith(MULTIPART)) {
+      return true;
+    }
+    return false;
+  }
+}

Added: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/FormDataItem.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/FormDataItem.java?rev=748277&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/FormDataItem.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/FormDataItem.java
 Thu Feb 26 19:29:46 2009
@@ -0,0 +1,81 @@
+/*
+ * 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.shindig.protocol.multipart;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Interface to represent an field item in multipart/form-data.
+ */
+public interface FormDataItem {
+
+  /**
+   * Returns the Content type of the field item.
+   * 
+   * @return content type
+   */
+  String getContentType();
+
+  /**
+   * The size of the content stored in this field item.
+   * 
+   * @return size of the content
+   */
+  long getSize();
+  
+  /**
+   * Returns an InputStream from which the content of the field item can be
+   * read.
+   * 
+   * @return InputStream to the content of the field item.
+   * @throws IOException
+   */
+  InputStream getInputStream() throws IOException;
+
+  /**
+   * Returns the content of the field item.
+   * 
+   * @return content of the field item
+   */
+  byte[] get();
+
+  /**
+   * Name of the uploaded file, if the item represents file upload.
+   * This will be only valid when {...@link #isFormField()} returns false.
+   * 
+   * @return name of the uploaded file
+   */
+  String getName();
+  
+  /**
+   * Field name of this field item. Can be used to identify a field by name but
+   * as per RFC this need not be unique.
+   * 
+   * @return name of the field
+   */
+  String getFieldName();
+
+  /**
+   * Used to identify if the field item represents a file upload or a regular
+   * form field.
+   * 
+   * @return true if it is a regular form field
+   */
+  boolean isFormField();
+}

Added: 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/MultipartFormParser.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/MultipartFormParser.java?rev=748277&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/MultipartFormParser.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/multipart/MultipartFormParser.java
 Thu Feb 26 19:29:46 2009
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.shindig.protocol.multipart;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * Class providing a facade over multipart form handling.
+ */
+...@implementedby(DefaultMultipartFormParser.class)
+public interface MultipartFormParser {
+  /** Parse a request into a list of data items */
+  Collection<FormDataItem> parse(HttpServletRequest request) throws 
IOException;
+  
+  /**
+   * @return true if the request requires multipart parsing.
+   */
+  boolean isMultipartContent(HttpServletRequest request);
+}

Modified: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/BaseRequestItemTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/BaseRequestItemTest.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/BaseRequestItemTest.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/BaseRequestItemTest.java
 Thu Feb 26 19:29:46 2009
@@ -106,7 +106,7 @@
             "userId:john.doe," +
             "groupId:@self," +
             "fields:[huey,dewey,louie]" +
-            "}"), FAKE_TOKEN, converter, converter);
+            "}"), null, FAKE_TOKEN, converter, converter);
     assertEquals(Lists.newArrayList("huey", "dewey", "louie"), 
request.getListParameter("fields"));
   }
 

Modified: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
 Thu Feb 26 19:29:46 2009
@@ -82,7 +82,7 @@
     JSONObject rpc = new JSONObject("{method : makebelieve.get}");
     RpcHandler rpcHandler = registry.getRpcHandler(rpc);
     try {
-      Future<?> future = rpcHandler.execute(null, null);
+      Future<?> future = rpcHandler.execute(null, null, null);
       future.get();
       fail("Expect exception for missing method");
     } catch (ExecutionException t) {
@@ -124,7 +124,7 @@
   public void testRpcWithInputClassThatIsntRequestItem() throws Exception {
     JSONObject rpc = new JSONObject("{ method : test.echo, params: {value: 
'Bob' }}");
     RpcHandler handler = registry.getRpcHandler(rpc);
-    Future<?> future = handler.execute(null, converter);
+    Future<?> future = handler.execute(null, null, converter);
     assertEquals(future.get(), TestHandler.ECHO_PREFIX + "Bob");
   }
   
@@ -138,7 +138,7 @@
   public void testNoArgumentClass() throws Exception {
     JSONObject rpc = new JSONObject("{ method : test.noArg }");
     RpcHandler handler = registry.getRpcHandler(rpc);
-    Future<?> future = handler.execute(null, converter);
+    Future<?> future = handler.execute(null, null, converter);
     assertEquals(future.get(), TestHandler.NO_ARG_RESPONSE);
   }
 
@@ -146,7 +146,7 @@
     // Test calling a handler method which does not return a future
     JSONObject rpc = new JSONObject("{ method : test.exception }");
     RpcHandler handler = registry.getRpcHandler(rpc);
-    Future<?> future = handler.execute(null, null);
+    Future<?> future = handler.execute(null, null, null);
     try {
       future.get();
       fail("Service method did not produce NullPointerException from Future");
@@ -159,7 +159,7 @@
     // Test calling a handler method which does not return a future
     JSONObject rpc = new JSONObject("{ method : test.futureException }");
     RpcHandler handler = registry.getRpcHandler(rpc);
-    Future<?> future = handler.execute(null, null);
+    Future<?> future = handler.execute(null, null, null);
     try {
       future.get();
       fail("Service method did not produce ExecutionException from Future");

Modified: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/JsonRpcServletTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/JsonRpcServletTest.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/JsonRpcServletTest.java
 (original)
+++ 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/JsonRpcServletTest.java
 Thu Feb 26 19:29:46 2009
@@ -21,26 +21,35 @@
 import org.apache.shindig.common.testing.FakeGadgetToken;
 import org.apache.shindig.protocol.conversion.BeanConverter;
 import org.apache.shindig.protocol.conversion.BeanJsonConverter;
+import org.apache.shindig.protocol.multipart.FormDataItem;
+import org.apache.shindig.protocol.multipart.MultipartFormParser;
 
-import com.google.common.collect.ImmutableMap;
-import com.google.inject.Guice;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.classextension.EasyMock.reset;
 
 import org.easymock.IMocksControl;
 import org.easymock.classextension.EasyMock;
 
-import junit.framework.TestCase;
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 
 import javax.servlet.ServletInputStream;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import junit.framework.TestCase;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Guice;
+
 /**
  *
  */
@@ -49,10 +58,15 @@
   private static final FakeGadgetToken FAKE_GADGET_TOKEN = new 
FakeGadgetToken()
       .setOwnerId("john.doe").setViewerId("john.doe");
 
+  private static final String IMAGE_FIELDNAME = "profile-photo";
+  private static final byte[] IMAGE_DATA = "image data".getBytes();
+  private static final String IMAGE_TYPE = "image/jpeg";
+  
   private HttpServletRequest req;
   private HttpServletResponse res;
   private JsonRpcServlet servlet;
-
+  private MultipartFormParser multipartFormParser;
+  
   private BeanJsonConverter jsonConverter;
   private BeanConverter xmlConverter;
   protected BeanConverter atomConverter;
@@ -71,6 +85,10 @@
     xmlConverter = mockControl.createMock(BeanConverter.class);
     atomConverter = mockControl.createMock(BeanConverter.class);
 
+    multipartFormParser = mockControl.createMock(MultipartFormParser.class);
+    
EasyMock.expect(multipartFormParser.isMultipartContent(req)).andStubReturn(false);
+    servlet.setMultipartFormParser(multipartFormParser);
+    
     HandlerRegistry registry = new DefaultHandlerRegistry(null,
         Collections.<Object>singleton(handler), jsonConverter,
         new HandlerExecutionListener.NoOpHandlerExecutionListener());
@@ -93,8 +111,8 @@
   public void testMethodRecognition() throws Exception {
     setupRequest("{method:test.get,id:id,params:{userId:5,groupId:@self}}");
 
-    EasyMock.expect(res.getWriter()).andReturn(writer);
-    EasyMock.expectLastCall();
+    expect(res.getWriter()).andReturn(writer);
+    expectLastCall();
 
     mockControl.replay();
     servlet.service(req, res);
@@ -103,11 +121,90 @@
     JsonAssert.assertJsonEquals("{id: 'id', data: {foo:'bar'}}", getOutput());
   }
 
+  public void testPostMultipartFormData() throws Exception {
+    reset(multipartFormParser);
+    
+    handler.setMock(new TestHandler() {
+      @Override
+      public Object get(RequestItem req) {
+        FormDataItem item = req.getFormMimePart(IMAGE_FIELDNAME);
+        return ImmutableMap.of("image-data", new String(item.get()),
+            "image-type", item.getContentType(),
+            "image-ref", req.getParameter("image-ref"));
+      }
+    });
+    expect(req.getMethod()).andStubReturn("POST");
+    expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
+    expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
+    res.setCharacterEncoding("UTF-8");
+    res.setContentType("application/json");
+    
+    List<FormDataItem> formItems = new ArrayList<FormDataItem>();
+    String request = "{method:test.get,id:id,params:" +
+        "{userId:5,groupId:@self,image-ref:@" + IMAGE_FIELDNAME + "}}";
+    formItems.add(mockFormDataItem(JsonRpcServlet.REQUEST_PARAM, 
"application/json", 
+        request.getBytes(), true));
+    formItems.add(mockFormDataItem(IMAGE_FIELDNAME, IMAGE_TYPE, IMAGE_DATA, 
false));
+    expect(multipartFormParser.isMultipartContent(req)).andReturn(true);
+    expect(multipartFormParser.parse(req)).andReturn(formItems);
+    expect(res.getWriter()).andReturn(writer);
+    expectLastCall();
+
+    mockControl.replay();
+    servlet.service(req, res);
+    mockControl.verify();
+
+    JsonAssert.assertJsonEquals("{id: 'id', data: {image-data:'" + new 
String(IMAGE_DATA) + 
+        "', image-type:'" + IMAGE_TYPE + "', image-ref:'@" + IMAGE_FIELDNAME + 
"'}}", getOutput());
+  }
+  
+  /** 
+   * Tests whether mime part with content type appliction/json is picked up as
+   * request even when its fieldname is not "request".
+   */
+  public void testPostMultipartFormDataWithNoExplicitRequestField() throws 
Exception {  
+    reset(multipartFormParser);
+
+    handler.setMock(new TestHandler() {
+      @Override
+      public Object get(RequestItem req) {
+        FormDataItem item = req.getFormMimePart(IMAGE_FIELDNAME);
+        return ImmutableMap.of("image-data", new String(item.get()),
+            "image-type", item.getContentType(),
+            "image-ref", req.getParameter("image-ref"));
+      }
+    });
+    expect(req.getMethod()).andStubReturn("POST");
+    expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
+    expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
+    res.setCharacterEncoding("UTF-8");
+    res.setContentType("application/json");
+    
+    List<FormDataItem> formItems = new ArrayList<FormDataItem>();
+    String request = "{method:test.get,id:id,params:" +
+        "{userId:5,groupId:@self,image-ref:@" + IMAGE_FIELDNAME + "}}";
+    formItems.add(mockFormDataItem(IMAGE_FIELDNAME, IMAGE_TYPE, IMAGE_DATA, 
false));
+    formItems.add(mockFormDataItem("json", "application/json", 
+        request.getBytes(), true));
+    expect(multipartFormParser.isMultipartContent(req)).andReturn(true);
+    expect(multipartFormParser.parse(req)).andReturn(formItems);
+    expect(res.getWriter()).andReturn(writer);
+    expectLastCall();
+
+    mockControl.replay();
+    servlet.service(req, res);
+    mockControl.verify();
+
+    JsonAssert.assertJsonEquals("{id: 'id', data: {image-data:'" + new 
String(IMAGE_DATA) + 
+        "', image-type:'" + IMAGE_TYPE + "', image-ref:'@" + IMAGE_FIELDNAME + 
"'}}", getOutput());
+  }
+
+  
   public void testInvalidService() throws Exception {
     setupRequest("{method:junk.get,id:id,params:{userId:5,groupId:@self}}");
 
-    EasyMock.expect(res.getWriter()).andReturn(writer);
-    EasyMock.expectLastCall();
+    expect(res.getWriter()).andReturn(writer);
+    expectLastCall();
 
     mockControl.replay();
     servlet.service(req, res);
@@ -126,12 +223,13 @@
   public void testFailedRequest() throws Exception {
     setupRequest("{id:id,method:test.futureException}");
 
-    EasyMock.expect(res.getWriter()).andReturn(writer);
-    EasyMock.expectLastCall();
+    expect(res.getWriter()).andReturn(writer);
+    expectLastCall();
 
     mockControl.replay();
     servlet.service(req, res);
-
+    mockControl.verify();
+    
     JsonAssert.assertJsonEquals(
         "{id:id,error:{message:'badRequest: FAILURE_MESSAGE',code:400}}", 
getOutput());
   }
@@ -139,26 +237,27 @@
   public void testBasicBatch() throws Exception {
     setupRequest("[{method:test.get,id:'1'},{method:test.get,id:'2'}]");
 
-    EasyMock.expect(res.getWriter()).andReturn(writer);
-    EasyMock.expectLastCall();
+    expect(res.getWriter()).andReturn(writer);
+    expectLastCall();
 
     mockControl.replay();
     servlet.service(req, res);
+    mockControl.verify();
 
     
JsonAssert.assertJsonEquals("[{id:'1',data:{foo:'bar'}},{id:'2',data:{foo:'bar'}}]",
         getOutput());
   }
 
   public void testGetExecution() throws Exception {
-    EasyMock.expect(req.getParameterMap()).andStubReturn(
+    expect(req.getParameterMap()).andStubReturn(
         ImmutableMap.of("method", new String[]{"test.get"}, "id", new 
String[]{"1"}));
-    EasyMock.expect(req.getMethod()).andStubReturn("GET");
-    
EasyMock.expect(req.getAttribute(EasyMock.isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
-    EasyMock.expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
+    expect(req.getMethod()).andStubReturn("GET");
+    expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
+    expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
     res.setCharacterEncoding("UTF-8");
 
-    EasyMock.expect(res.getWriter()).andReturn(writer);
-    EasyMock.expectLastCall();
+    expect(res.getWriter()).andReturn(writer);
+    expectLastCall();
 
     mockControl.replay();
     servlet.service(req, res);
@@ -176,12 +275,26 @@
       }
     };
 
-    EasyMock.expect(req.getInputStream()).andStubReturn(stream);
-    EasyMock.expect(req.getMethod()).andStubReturn("POST");
-    
EasyMock.expect(req.getAttribute(EasyMock.isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
-    EasyMock.expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
+    expect(req.getInputStream()).andStubReturn(stream);
+    expect(req.getMethod()).andStubReturn("POST");
+    expect(req.getAttribute(isA(String.class))).andReturn(FAKE_GADGET_TOKEN);
+    expect(req.getCharacterEncoding()).andStubReturn("UTF-8");
+    expect(req.getContentType()).andStubReturn("application/json");
     res.setCharacterEncoding("UTF-8");
     res.setContentType("application/json");
   }
 
+  private FormDataItem mockFormDataItem(String fieldName, String contentType, 
byte content[],
+      boolean isFormField) throws IOException {
+    InputStream in = new ByteArrayInputStream(content);
+    FormDataItem formDataItem = mockControl.createMock(FormDataItem.class);
+    expect(formDataItem.getContentType()).andStubReturn(contentType);
+    expect(formDataItem.getSize()).andStubReturn((long) content.length);
+    expect(formDataItem.get()).andStubReturn(content);
+    expect(formDataItem.getFieldName()).andStubReturn(fieldName);
+    expect(formDataItem.isFormField()).andStubReturn(isFormField);
+    expect(formDataItem.getInputStream()).andStubReturn(in);
+    return formDataItem;
+  }
+
 }

Added: 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParserTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParserTest.java?rev=748277&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParserTest.java
 (added)
+++ 
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/multipart/DefaultMultipartFormParserTest.java
 Thu Feb 26 19:29:46 2009
@@ -0,0 +1,237 @@
+/*
+ * 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.shindig.protocol.multipart;
+
+import org.apache.shindig.common.testing.FakeHttpServletRequest;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import junit.framework.TestCase;
+
+import com.google.common.collect.Lists;
+
+public class DefaultMultipartFormParserTest extends TestCase {
+
+  private static final String REQUEST_FIELDNAME = "request";
+  private static final String REQUEST_DATA = "{name: 'HelloWorld'}";
+  
+  private static final String ALBUM_IMAGE_FIELDNAME = "album-image";
+  private static final String ALBUM_IMAGE_FILENAME = "album-image.jpg";
+  private static final String ALBUM_IMAGE_DATA = "album image data";
+  private static final String ALBUM_IMAGE_TYPE = "image/jpeg";
+  
+  private static final String PROFILE_IMAGE_FIELDNAME = "profile-image";
+  private static final String PROFILE_IMAGE_FILENAME = "profile-image.jpg";
+  private static final String PROFILE_IMAGE_DATA = "profile image data";
+  private static final String PROFILE_IMAGE_TYPE = "image/png";
+  
+  private MultipartFormParser multipartFormParser;
+  private HttpServletRequest request;
+  
+  @Override protected void setUp() throws Exception {
+    multipartFormParser = new DefaultMultipartFormParser();
+  }
+
+  /**
+   * Test that requests must be both POST and have a multipart
+   * content type.
+   */
+  public void testIsMultipartContent() {
+    FakeHttpServletRequest request = new FakeHttpServletRequest();
+
+    request.setMethod("GET");    
+    assertFalse(multipartFormParser.isMultipartContent(request));
+
+    request.setMethod("POST");
+    assertFalse(multipartFormParser.isMultipartContent(request));
+    
+    request.setContentType("multipart/form-data");
+    assertTrue(multipartFormParser.isMultipartContent(request));    
+
+    request.setMethod("GET");    
+    assertFalse(multipartFormParser.isMultipartContent(request));
+}
+  
+  /**
+   * Helper class to create the multipart/form-data body of the POST request.
+   */
+  private static class MultipartFormBuilder {
+    private final String boundary;
+    private final StringBuilder packet = new StringBuilder();
+    private static final String BOUNDARY = "--abcdefgh";
+    
+    public MultipartFormBuilder() {
+      this(BOUNDARY);
+    }
+    
+    public MultipartFormBuilder(String boundary) {
+      this.boundary = boundary;
+    }
+    
+    public String getContentType() {
+      return "multipart/form-data; boundary=" + boundary;
+    }
+    
+    public byte[] build() {
+      write("--");
+      write(boundary);
+      write("--");
+      return packet.toString().getBytes();
+    }
+    
+    public void addFileItem(String fieldName, String fileName, String content,
+        String contentType) {
+      writeBoundary();
+
+      write("Content-Disposition: form-data; name=\"" + fieldName + "\"; 
filename=\"" +
+          fileName + "\"");
+      write("\r\n");
+      write("Content-Type: " + contentType);
+      write("\r\n\r\n");
+      write(content);
+      write("\r\n");
+    }
+
+    public void addFormField(String fieldName, String content) {
+      addFormField(fieldName, content, null);
+    }
+
+    public void addFormField(String fieldName, String content, String 
contentType) {
+      writeBoundary();
+
+      write("Content-Disposition: form-data; name=\"" + fieldName + "\"");
+      if (contentType != null) {
+        write("\r\n");
+        write("Content-Type: " + contentType);
+      }
+      write("\r\n\r\n");
+      write(content);
+      write("\r\n");
+    }
+
+    private void writeBoundary() {
+      write("--");
+      write(boundary);
+      write("\r\n");
+    }
+
+    private void write(String content) {
+      packet.append(content);
+    }
+  }
+
+  private void setupRequest(byte[] postData, String contentType) throws 
IOException {
+    FakeHttpServletRequest fakeReq = new 
FakeHttpServletRequest("/social/rest", "", "");
+    fakeReq.setPostData(postData);  
+    fakeReq.setContentType(contentType);
+    request = fakeReq;
+  }
+
+  public void testSingleFileItem() throws Exception {
+    MultipartFormBuilder builder = new MultipartFormBuilder();
+    builder.addFileItem(ALBUM_IMAGE_FIELDNAME, ALBUM_IMAGE_FILENAME, 
ALBUM_IMAGE_DATA,
+        ALBUM_IMAGE_TYPE);
+    setupRequest(builder.build(), builder.getContentType());
+
+    List<FormDataItem> formItems =
+      Lists.newArrayList(multipartFormParser.parse(request));
+    
+    assertEquals(1, formItems.size());
+    FormDataItem formItem = formItems.get(0);
+    assertFalse(formItem.isFormField());
+    assertEquals(ALBUM_IMAGE_FIELDNAME, formItem.getFieldName());
+    assertEquals(ALBUM_IMAGE_FILENAME, formItem.getName());
+    assertEquals(ALBUM_IMAGE_TYPE, formItem.getContentType());
+    assertEquals(ALBUM_IMAGE_DATA, new String(formItem.get()));  
+  }
+  
+  public void testSingleRequest() throws Exception {
+    MultipartFormBuilder builder = new MultipartFormBuilder();
+    builder.addFormField(REQUEST_FIELDNAME, REQUEST_DATA);
+    setupRequest(builder.build(), builder.getContentType());
+
+    List<FormDataItem> formItems =
+      Lists.newArrayList(multipartFormParser.parse(request));
+    
+    assertEquals(1, formItems.size());
+    FormDataItem formItem = formItems.get(0);
+    assertTrue(formItem.isFormField());
+    assertEquals(REQUEST_FIELDNAME, formItem.getFieldName());
+    assertEquals(REQUEST_DATA, new String(formItem.get()));
+  }
+
+  public void testSingleFileItemAndRequest() throws Exception {
+    MultipartFormBuilder builder = new MultipartFormBuilder();
+    builder.addFileItem(ALBUM_IMAGE_FIELDNAME, ALBUM_IMAGE_FILENAME, 
ALBUM_IMAGE_DATA,
+        ALBUM_IMAGE_TYPE);
+    builder.addFormField(REQUEST_FIELDNAME, REQUEST_DATA);
+    setupRequest(builder.build(), builder.getContentType());
+
+    List<FormDataItem> formItems =
+      Lists.newArrayList(multipartFormParser.parse(request));
+    
+    assertEquals(2, formItems.size());
+    FormDataItem formItem = formItems.get(0);
+    assertFalse(formItem.isFormField());
+    assertEquals(ALBUM_IMAGE_FIELDNAME, formItem.getFieldName());
+    assertEquals(ALBUM_IMAGE_FILENAME, formItem.getName());
+    assertEquals(ALBUM_IMAGE_TYPE, formItem.getContentType());
+    assertEquals(ALBUM_IMAGE_DATA, new String(formItem.get()));  
+
+    formItem = formItems.get(1);
+    assertTrue(formItem.isFormField());
+    assertEquals(REQUEST_FIELDNAME, formItem.getFieldName());
+    assertEquals(REQUEST_DATA, new String(formItem.get()));
+  }
+
+  public void testMultipleFileItemAndRequest() throws Exception {
+    MultipartFormBuilder builder = new MultipartFormBuilder();
+    builder.addFileItem(ALBUM_IMAGE_FIELDNAME, ALBUM_IMAGE_FILENAME, 
ALBUM_IMAGE_DATA,
+        ALBUM_IMAGE_TYPE);
+    builder.addFormField(REQUEST_FIELDNAME, REQUEST_DATA);
+    builder.addFileItem(PROFILE_IMAGE_FIELDNAME, PROFILE_IMAGE_FILENAME, 
PROFILE_IMAGE_DATA,
+        PROFILE_IMAGE_TYPE);
+    setupRequest(builder.build(), builder.getContentType());
+
+    List<FormDataItem> formItems =
+      Lists.newArrayList(multipartFormParser.parse(request));
+    
+    assertEquals(3, formItems.size());
+    FormDataItem formItem = formItems.get(0);
+    assertFalse(formItem.isFormField());
+    assertEquals(ALBUM_IMAGE_FIELDNAME, formItem.getFieldName());
+    assertEquals(ALBUM_IMAGE_FILENAME, formItem.getName());
+    assertEquals(ALBUM_IMAGE_TYPE, formItem.getContentType());
+    assertEquals(ALBUM_IMAGE_DATA, new String(formItem.get()));  
+
+    formItem = formItems.get(1);
+    assertTrue(formItem.isFormField());
+    assertEquals(REQUEST_FIELDNAME, formItem.getFieldName());
+    assertEquals(REQUEST_DATA, new String(formItem.get()));
+    
+    formItem = formItems.get(2);
+    assertFalse(formItem.isFormField());
+    assertEquals(PROFILE_IMAGE_FIELDNAME, formItem.getFieldName());
+    assertEquals(PROFILE_IMAGE_FILENAME, formItem.getName());
+    assertEquals(PROFILE_IMAGE_TYPE, formItem.getContentType());
+    assertEquals(PROFILE_IMAGE_DATA, new String(formItem.get()));  
+  }
+}

Modified: 
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SocialRequestItem.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SocialRequestItem.java?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SocialRequestItem.java
 (original)
+++ 
incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SocialRequestItem.java
 Thu Feb 26 19:29:46 2009
@@ -21,18 +21,19 @@
 import org.apache.shindig.protocol.BaseRequestItem;
 import org.apache.shindig.protocol.conversion.BeanConverter;
 import org.apache.shindig.protocol.conversion.BeanJsonConverter;
+import org.apache.shindig.protocol.multipart.FormDataItem;
 import org.apache.shindig.social.opensocial.spi.GroupId;
 import org.apache.shindig.social.opensocial.spi.PersonService;
 import org.apache.shindig.social.opensocial.spi.UserId;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import org.json.JSONObject;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
 /**
  * Subclass with social specific extensions
  */
@@ -41,12 +42,14 @@
   String USER_ID = "userId";
   String GROUP_ID = "groupId";
 
-  public SocialRequestItem(Map<String, String[]> parameters, SecurityToken 
token, BeanConverter converter, BeanJsonConverter jsonConverter) {
+  public SocialRequestItem(Map<String, String[]> parameters, 
+      SecurityToken token, BeanConverter converter, BeanJsonConverter 
jsonConverter) {
     super(parameters, token, converter, jsonConverter);
   }
 
-  public SocialRequestItem(JSONObject parameters, SecurityToken token, 
BeanConverter converter, BeanJsonConverter jsonConverter) {
-    super(parameters, token, converter, jsonConverter);
+  public SocialRequestItem(JSONObject parameters, Map<String, FormDataItem> 
formItems,
+      SecurityToken token, BeanConverter converter, BeanJsonConverter 
jsonConverter) {
+    super(parameters, formItems, token, converter, jsonConverter);
   }
 
   public Set<UserId> getUsers() {

Modified: incubator/shindig/trunk/pom.xml
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/pom.xml?rev=748277&r1=748276&r2=748277&view=diff
==============================================================================
--- incubator/shindig/trunk/pom.xml (original)
+++ incubator/shindig/trunk/pom.xml Thu Feb 26 19:29:46 2009
@@ -1240,6 +1240,11 @@
         <version>1.3</version>
       </dependency>
       <dependency>
+        <groupId>commons-fileupload</groupId>
+        <artifactId>commons-fileupload</artifactId>
+        <version>1.2</version>
+      </dependency>
+      <dependency>
         <groupId>org.json</groupId>
         <artifactId>json</artifactId>
         <version>20070829</version>


Reply via email to