Hi,

I'm implementing Content
Upload<http://opensocial-resources.googlecode.com/svn/spec/1.0/Core-API-Server.xml#Content-Upload>spec
and I have a few question to be sure that I'm understanding it
correctly:

I wrote an extend exaplantion in a post with title "Uploading and storing
files data", but it can be summarized in that Uploaded Content is a param
for some other action. I mean, if we want to create an Activity that
contains an image, the we call the create activity service with the usual
params of the activity and also with a file as a param. Uploading a file
param is not easy, so this spec is about how to handle that.

If that interpretation is right, then uploading content is an extension on
what type of params can be used to create/update any entity. Therefore we
need a general way to handle files that could be used by potentially any
service.

So, I'm right now trying to implement the RPC part of the spec, since it can
be used from the browser and all the multipart-form handling code is already
there in Shindig.

Just to start with something I've modified AppDataHandler to handle binary
files (I know that the spec says that App Data only handles Strings values,
but I need something like for myself, I'll port this to meet the spec). So
now the AppDataHandler.create(...) method is able to handle files if they
are in the SocialRequestItem. It saves the file using a new
service ContentUploadService. Then replace the key that is mapped to that
file, e.g:

If the there is a values map with:

"data" : {
   "image":"@field:image1"
}

Then it searches for a FormMimePart with name "image1", saves the content to
a new file, gets this file's ID and changes that value with it:

"data" : {
   "image":"<fileID>"
}

Then continues as before.

I'm not sure if that's the best place, I mean that Handles handle the file
storage and then resolving the reference to the file. The good side of this
is that each handler would be able to decide what to do with file params.
The bad side is that it could be resolved when the raw content of the
request is being converted, so it would be transparent to handlers. Any
suggestion on this?

Another thing is that I'm not sure about how to get that file, I mean to see
it in the browser, AFAIU the file ID could be actually an URL that can be
used to get the file. Is this right?

So WDYT? I'm on the right way?

BTW, I'm attaching a patch with the changes.

Thanks,

Gabriel
### Eclipse Workspace Patch 1.0
#P shindig-social-api
Index: 
src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java
===================================================================
--- 
src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java  
    (revision 963850)
+++ 
src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java  
    (working copy)
@@ -17,29 +17,36 @@
  */
 package org.apache.shindig.social.opensocial.service;
 
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
 import org.apache.shindig.protocol.HandlerPreconditions;
 import org.apache.shindig.protocol.Operation;
 import org.apache.shindig.protocol.ProtocolException;
 import org.apache.shindig.protocol.Service;
+import org.apache.shindig.protocol.multipart.FormDataItem;
 import org.apache.shindig.social.opensocial.spi.AppDataService;
+import org.apache.shindig.social.opensocial.spi.ContentUploadService;
 import org.apache.shindig.social.opensocial.spi.UserId;
 
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Future;
-
-import javax.servlet.http.HttpServletResponse;
-
 import com.google.inject.Inject;
 
 @Service(name = "appdata", path = "/{userId}+/{groupId}/{appId}")
 public class AppDataHandler {
 
   private final AppDataService service;
+  private final ContentUploadService contentService;
 
   @Inject
-  public AppDataHandler(AppDataService service) {
+  public AppDataHandler(AppDataService service, ContentUploadService 
contentService) {
     this.service = service;
+    this.contentService = contentService;
   }
 
   /**
@@ -103,6 +110,38 @@
       }
     }
 
+    // Some entries could have a reference to a file that needs to be saved.
+    for (Map.Entry<String, String> entry : values.entrySet()) {
+      if (entry.getValue() instanceof String) {
+
+        String value = entry.getValue();
+
+        // If the value is a reference to another field
+        if (value.startsWith("@field:")) {
+          String fieldName = value.substring("@field:".length());
+          FormDataItem dataItem = request.getFormMimePart(fieldName);
+
+          // It could be a reference to a file being uploaded
+          if (dataItem != null && !dataItem.isFormField()) {
+            try {
+              // If so, save the file and change the reference to the actual 
file ID
+              Future<String> fileId = 
contentService.write(IOUtils.toByteArray(dataItem.getInputStream()));
+              values.put(entry.getKey(), fileId.get());
+            } catch (IOException e) {
+              throw new 
ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                  "Error uploading file in field: " + fieldName);
+            } catch (InterruptedException e) {
+              throw new 
ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                  "Error uploading file in field: " + fieldName);
+            } catch (ExecutionException e) {
+              throw new 
ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                  "Error uploading file in field: " + fieldName);
+            }
+          }
+        }
+      }
+    }
+    
     return service.updatePersonData(userIds.iterator().next(), 
request.getGroup(),
         request.getAppId(), request.getFields(), values, request.getToken());
   }
Index: src/main/java/org/apache/shindig/social/sample/SampleModule.java
===================================================================
--- src/main/java/org/apache/shindig/social/sample/SampleModule.java    
(revision 963850)
+++ src/main/java/org/apache/shindig/social/sample/SampleModule.java    
(working copy)
@@ -20,9 +20,11 @@
 import org.apache.shindig.social.opensocial.oauth.OAuthDataStore;
 import org.apache.shindig.social.opensocial.spi.ActivityService;
 import org.apache.shindig.social.opensocial.spi.AppDataService;
+import org.apache.shindig.social.opensocial.spi.ContentUploadService;
 import org.apache.shindig.social.opensocial.spi.MessageService;
 import org.apache.shindig.social.opensocial.spi.PersonService;
 import org.apache.shindig.social.sample.oauth.SampleOAuthDataStore;
+import org.apache.shindig.social.sample.spi.FileDbService;
 import org.apache.shindig.social.sample.spi.JsonDbOpensocialService;
 
 import com.google.inject.AbstractModule;
@@ -44,6 +46,7 @@
     bind(AppDataService.class).to(JsonDbOpensocialService.class);
     bind(PersonService.class).to(JsonDbOpensocialService.class);
     bind(MessageService.class).to(JsonDbOpensocialService.class);
+    bind(ContentUploadService.class).to(FileDbService.class);
     
     bind(OAuthDataStore.class).to(SampleOAuthDataStore.class);
   }
Index: src/main/java/org/apache/shindig/social/sample/spi/FileDbService.java
===================================================================
--- src/main/java/org/apache/shindig/social/sample/spi/FileDbService.java       
(revision 0)
+++ src/main/java/org/apache/shindig/social/sample/spi/FileDbService.java       
(revision 0)
@@ -0,0 +1,68 @@
+package org.apache.shindig.social.sample.spi;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.Future;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.shindig.common.util.ImmediateFuture;
+import org.apache.shindig.social.opensocial.spi.ContentUploadService;
+
+import com.google.common.collect.Maps;
+
+//TODO-GG concurrency
+public class FileDbService implements ContentUploadService {
+
+  private Map<String, String> fileIdNameMap;
+  private String tmpDir;
+
+  public FileDbService() {
+    fileIdNameMap = Maps.newHashMap();
+    //TODO-GG real java tmp dir, or a fixed one?
+    tmpDir = "/tmp/";
+  }
+  
+  public Future<byte[]> read(String fileId) {
+    FileInputStream fis = null;
+    
+    try {
+      fis = new FileInputStream(fileIdNameMap.get(fileId));
+      return ImmediateFuture.newInstance(IOUtils.toByteArray(fis));
+    } catch (FileNotFoundException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    }
+    return null;
+  }
+
+  public Future<String> write(byte[] data) {
+    FileOutputStream fos = null;
+    String fileId = null;
+    try {
+      fileId = getNextFileId();
+      String fileName = tmpDir + fileId;
+      fos = new FileOutputStream(fileName);
+      IOUtils.write(data, fos);
+      fileIdNameMap.put(fileId, fileName);
+    } catch (FileNotFoundException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } finally {
+      IOUtils.closeQuietly(fos);
+    }
+    return ImmediateFuture.newInstance(fileId);
+  }
+
+  private String getNextFileId() {
+    return "file-" + fileIdNameMap.size();
+  }
+}
Index: 
src/main/java/org/apache/shindig/social/opensocial/spi/ContentUploadService.java
===================================================================
--- 
src/main/java/org/apache/shindig/social/opensocial/spi/ContentUploadService.java
    (revision 0)
+++ 
src/main/java/org/apache/shindig/social/opensocial/spi/ContentUploadService.java
    (revision 0)
@@ -0,0 +1,10 @@
+package org.apache.shindig.social.opensocial.spi;
+
+import java.util.concurrent.Future;
+
+public interface ContentUploadService {
+
+       Future<String> write(byte[] data);
+       
+       Future<byte[]> read(String fileId);
+}

Reply via email to