incubator-sling-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cziege...@apache.org
Subject svn commit: r1519658 - in /sling/trunk: bundles/jcr/resource/src/main/resources/SLING-INF/nodetypes/ bundles/servlets/post/ bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/ bundles/servlets/post/src/main/java/org/apache/sling/servlet...
Date Tue, 03 Sep 2013 12:06:20 GMT
Author: cziegeler
Date: Tue Sep  3 12:06:20 2013
New Revision: 1519658

URL: http://svn.apache.org/r1519658
Log:
SLING-2707 : Support of chunked file upload into Sling. Apply patch from Shashank Gupta

Added:
    sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/
    sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java   (with props)
Modified:
    sling/trunk/bundles/jcr/resource/src/main/resources/SLING-INF/nodetypes/resource.cnd
    sling/trunk/bundles/servlets/post/pom.xml
    sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
    sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
    sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java
    sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java
    sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java
    sling/trunk/launchpad/builder/src/main/bundles/list.xml
    sling/trunk/testing/samples/integration-tests/pom.xml

Modified: sling/trunk/bundles/jcr/resource/src/main/resources/SLING-INF/nodetypes/resource.cnd
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/jcr/resource/src/main/resources/SLING-INF/nodetypes/resource.cnd?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/bundles/jcr/resource/src/main/resources/SLING-INF/nodetypes/resource.cnd (original)
+++ sling/trunk/bundles/jcr/resource/src/main/resources/SLING-INF/nodetypes/resource.cnd Tue Sep  3 12:06:20 2013
@@ -31,3 +31,22 @@
 [sling:ResourceSuperType]
     mixin
   - sling:resourceSuperType (string)
+
+//-----------------------------------------------------------------------------
+// node type to store chunk
+// offset: offset of chunk in file
+// jcr:data: binary of chunk
+[sling:chunk] > nt:hierarchyNode
+  primaryitem jcr:data
+  - sling:offset  (long) mandatory
+  - jcr:data (binary) mandatory
+
+ //-----------------------------------------------------------------------------
+ // Mixin type to identify that a node has chunks
+ // sling:fileLength : length of complete file
+ // sling:length: cumulative length of all uploaded chunks
+[sling:chunks]
+  mixin
+  - sling:fileLength (long)
+  - sling:length (long)
+  + * (sling:chunk) multiple  

Modified: sling/trunk/bundles/servlets/post/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/pom.xml?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/bundles/servlets/post/pom.xml (original)
+++ sling/trunk/bundles/servlets/post/pom.xml Tue Sep  3 12:06:20 2013
@@ -127,8 +127,8 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.jcr.resource</artifactId>
-            <version>2.0.6</version>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.0.2-incubator</version>
             <scope>provided</scope>
         </dependency>
         <dependency>

Modified: sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java (original)
+++ sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/SlingPostConstants.java Tue Sep  3 12:06:20 2013
@@ -491,4 +491,66 @@ public interface SlingPostConstants {
      */
     public static final String ATTR_SKIP_SESSION_HANDLING = "skip-session-handling";
 
+    /**
+     * Name of the request parameter indicating offset of the chunk in request.
+     * @since 2.3.4
+     */
+    public static final String SUFFIX_OFFSET = "@Offset";
+
+    /**
+     * Name of the request parameter indicating length of complete file.
+     * @since 2.3.4
+     */
+    public static final String SUFFIX_LENGTH = "@Length";
+
+    /**
+     * Name of the request parameter indicating request contains last chunk
+     * and as a result upload should be finished. It is useful in scenarios
+     * like file streaming where file size is not known in advance.
+     * @since 2.3.4
+     */
+    public static final String SUFFIX_COMPLETED = "@Completed";
+
+    /**
+     * Name of the request parameter indicating request operation is applicable
+     * to chunks.
+     * @since 2.3.4
+     */
+    public static final String RP_APPLY_TO_CHUNKS =  RP_PREFIX + "applyToChunks";
+
+    /**
+     * Constant for the sling:chunks mixin. Used to identify that node
+     * contains chunks.
+     * @since 2.3.4
+     */
+    public static final String NT_SLING_CHUNK_MIXIN = "sling:chunks";
+
+    /**
+     * Constant for the sling:fileLength property. The property stores file
+     * length.
+     * @since 2.3.4
+     */
+    public static final String NT_SLING_FILE_LENGTH = "sling:fileLength";
+
+    /**
+     * Constant for the sling:length property. The property stores
+     * cumulative length of all uploaded chunks.
+     * @since 2.3.4
+     */
+    public static final String NT_SLING_CHUNKS_LENGTH = "sling:length";
+
+    /**
+     * Constant for the sling:chunk node type. The node type is used
+     * to store chunk.
+     * @since 2.3.4
+     */
+    public static final String NT_SLING_CHUNK_NODETYPE = "sling:chunk";
+
+    /**
+     * Constant for the sling:offset property. The property stores start
+     * offset of chunk.
+     * @since 2.3.4
+     */
+    public static final String NT_SLING_CHUNK_OFFSET = "sling:offset";
+
 }

Modified: sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java (original)
+++ sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/RequestProperty.java Tue Sep  3 12:06:20 2013
@@ -65,6 +65,14 @@ public class RequestProperty {
 
     private boolean patch = false;
 
+    private long offset;
+
+    private long length;
+
+    private boolean completed;
+
+    private boolean chunkUpload;
+
     public RequestProperty(String path) {
         assert path.startsWith("/");
         this.path = ResourceUtil.normalize(path);
@@ -299,4 +307,57 @@ public class RequestProperty {
     public boolean isPatch() {
         return patch;
     }
-}
\ No newline at end of file
+
+    /**
+     * Return offset of the chunk.
+     */
+    public long getOffset() {
+        return offset;
+    }
+
+    /**
+     * Set offset value.
+     *
+     */
+    public void setOffsetValue(long offset) {
+        this.offset = offset;
+        this.chunkUpload = true;
+    }
+
+    /**
+     * Return length of the file parameter.
+     */
+    public long getLength() {
+        return length;
+    }
+
+    /**
+     * Set length of file parameter.
+     */
+    public void setLength(long length) {
+        this.length = length;
+    }
+
+    /**
+     * Return true if request contains last chunk as a result
+     * upload should be finished. It is useful in scenarios where
+     * file streaming where file size is not known in advance.
+     */
+    public boolean isCompleted() {
+        return completed;
+    }
+
+    /**
+     * Set complete flag
+     */
+    public void setCompleted(boolean complete) {
+        this.completed = complete;
+    }
+
+    /**
+     *  Return true if request is chunk upload.
+     */
+    public boolean isChunkUpload() {
+        return chunkUpload;
+    }
+}

Modified: sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java (original)
+++ sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/helper/SlingFileUploadHandler.java Tue Sep  3 12:06:20 2013
@@ -16,13 +16,22 @@
  */
 package org.apache.sling.servlets.post.impl.helper;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Calendar;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import javax.jcr.Node;
+import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.NodeTypeManager;
@@ -34,6 +43,7 @@ import org.apache.sling.api.resource.Mod
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.servlets.post.Modification;
+import org.apache.sling.servlets.post.SlingPostConstants;
 
 /**
  * Handles file uploads.
@@ -88,6 +98,8 @@ public class SlingFileUploadHandler {
     public static final String JCR_ENCODING = "jcr:encoding";
     public static final String JCR_DATA = "jcr:data";
 
+    private static final String CHUNK_NODE_NAME = "chunk";
+
     /**
      * The servlet context.
      */
@@ -160,9 +172,13 @@ public class SlingFileUploadHandler {
                 res.setProperty(JCR_MIMETYPE, contentType).getPath()
                 ));
         try {
-            changes.add(Modification.onModified(
-                    res.setProperty(JCR_DATA, value.getInputStream()).getPath()
-                    ));
+            // process chunk upload request separately
+            if (prop.isChunkUpload()) {
+                processChunk(resParent, res, prop, value, changes);
+            } else {
+                changes.add(Modification.onModified(res.setProperty(JCR_DATA,
+                        value.getInputStream()).getPath()));
+            }
         } catch (IOException e) {
             throw new RepositoryException("Error while retrieving inputstream from parameter value.", e);
         }
@@ -184,16 +200,22 @@ public class SlingFileUploadHandler {
         if ( typeHint == null ) {
             typeHint = NT_FILE;
         }
-
+        if(prop.isChunkUpload()){
+            // cannot process chunk upload if parent node doesn't
+            // exists. throw exception
+            throw new RepositoryException(
+                    "Cannot process chunk upload request. Parent resource ["
+                            + parentResource.getPath() + "] doesn't exists");
+        }
         // create properties
         final Map<String, Object> props = new HashMap<String, Object>();
         props.put("sling:resourceType", typeHint);
         props.put(JCR_LASTMODIFIED, Calendar.getInstance());
         props.put(JCR_MIMETYPE, contentType);
         try {
-            props.put(JCR_DATA, value.getInputStream());
+             props.put(JCR_DATA, value.getInputStream());
         } catch (final IOException e) {
-            throw new PersistenceException("Error while retrieving inputstream from parameter value.", e);
+             throw new PersistenceException("Error while retrieving inputstream from parameter value.", e);
         }
 
         // get or create resource
@@ -211,6 +233,241 @@ public class SlingFileUploadHandler {
             changes.add(Modification.onModified(result.getPath() + '/' + key));
         }
     }
+    /**
+     * Process chunk upload. For first and intermediate chunks request persists
+     * chunks at jcr:content/chunk_start_end/jcr:data or
+     * nt:resource/chunk_start_end/jcr:data. For last last chunk,
+     * merge all previous chunks and current chunk and replace binary at
+     * destination.
+     */
+    private void processChunk(final Resource resParent, final Node res,
+            final RequestProperty prop, RequestParameter value,
+            final List<Modification> changes) throws RepositoryException {
+        try {
+            long chunkOffset = prop.getOffset();
+            if (chunkOffset == 0) {
+                // first chunk
+                // check if another chunk upload is already in progress. throw
+                // exception
+                NodeIterator itr = res.getNodes(CHUNK_NODE_NAME + "*");
+                if (itr.hasNext()) {
+                    throw new RepositoryException(
+                        "Chunk upload already in progress at {" + res.getPath()
+                            + "}");
+                }
+                res.addMixin(SlingPostConstants.NT_SLING_CHUNK_MIXIN);
+                changes.add(Modification.onModified(res.setProperty(
+                    SlingPostConstants.NT_SLING_CHUNKS_LENGTH, 0).getPath()));
+                if (!res.hasProperty(JCR_DATA)) {
+                    // create a empty jcr:data property
+                    res.setProperty(JCR_DATA,
+                        new ByteArrayInputStream("".getBytes()));
+                }
+            }
+            if (!res.hasProperty(SlingPostConstants.NT_SLING_CHUNKS_LENGTH)) {
+                throw new RepositoryException("no chunk upload found at {"
+                    + res.getPath() + "}");
+            }
+            long currentLength = res.getProperty(
+                SlingPostConstants.NT_SLING_CHUNKS_LENGTH).getLong();
+            long totalLength = prop.getLength();
+            if (chunkOffset != currentLength) {
+                throw new RepositoryException("Chunk's offset {"
+                    + chunkOffset
+                    + "} doesn't match expected offset {"
+                    + res.getProperty(
+                        SlingPostConstants.NT_SLING_CHUNKS_LENGTH).getLong()
+                    + "}");
+            }
+            if (totalLength != 0) {
+                if (res.hasProperty(SlingPostConstants.NT_SLING_FILE_LENGTH)) {
+                    long expectedLength = res.getProperty(
+                        SlingPostConstants.NT_SLING_FILE_LENGTH).getLong();
+                    if (totalLength != expectedLength) {
+                        throw new RepositoryException("File length {"
+                            + totalLength + "} doesn't match expected length {"
+                            + expectedLength + "}");
+                    }
+                } else {
+                    res.setProperty(SlingPostConstants.NT_SLING_FILE_LENGTH,
+                        totalLength);
+                }
+            }
+            NodeIterator itr = res.getNodes(CHUNK_NODE_NAME + "_"
+                + String.valueOf(chunkOffset) + "*");
+            if (itr.hasNext()) {
+                throw new RepositoryException("Chunk already present at {"
+                    + itr.nextNode().getPath() + "}");
+            }
+            String nodeName = CHUNK_NODE_NAME + "_"
+                + String.valueOf(chunkOffset) + "_"
+                + String.valueOf(chunkOffset + value.getSize() - 1);
+            if (totalLength == (currentLength + value.getSize())
+                || prop.isCompleted()) {
+                File file = null;
+                InputStream fileIns = null;
+                try {
+                    file = mergeChunks(res, value.getInputStream());
+                    fileIns = new FileInputStream(file);
+                    changes.add(Modification.onModified(res.setProperty(
+                        JCR_DATA, fileIns).getPath()));
+                    NodeIterator nodeItr = res.getNodes(CHUNK_NODE_NAME + "*");
+                    while (nodeItr.hasNext()) {
+                        Node nodeRange = nodeItr.nextNode();
+                        changes.add(Modification.onDeleted(nodeRange.getPath()));
+                        nodeRange.remove();
+                    }
+                    if (res.hasProperty(SlingPostConstants.NT_SLING_FILE_LENGTH)) {
+                        javax.jcr.Property expLenProp = res.getProperty(SlingPostConstants.NT_SLING_FILE_LENGTH);
+                        changes.add(Modification.onDeleted(expLenProp.getPath()));
+                        expLenProp.remove();
+                    }
+                    if (res.hasProperty(SlingPostConstants.NT_SLING_CHUNKS_LENGTH)) {
+                        javax.jcr.Property currLenProp = res.getProperty(SlingPostConstants.NT_SLING_CHUNKS_LENGTH);
+                        changes.add(Modification.onDeleted(currLenProp.getPath()));
+                        currLenProp.remove();
+                    }
+                    res.removeMixin(SlingPostConstants.NT_SLING_CHUNK_MIXIN);
+                } finally {
+                    try {
+                        fileIns.close();
+                        file.delete();
+                    } catch (IOException ign) {
+
+                    }
+
+                }
+            } else {
+                Node rangeNode = res.addNode(nodeName,
+                    SlingPostConstants.NT_SLING_CHUNK_NODETYPE);
+                changes.add(Modification.onCreated(rangeNode.getPath()));
+                changes.add(Modification.onModified(rangeNode.setProperty(
+                    JCR_DATA, value.getInputStream()).getPath()));
+                changes.add(Modification.onModified(rangeNode.setProperty(
+                    SlingPostConstants.NT_SLING_CHUNK_OFFSET, chunkOffset).getPath()));
+                changes.add(Modification.onModified(res.setProperty(
+                    SlingPostConstants.NT_SLING_CHUNKS_LENGTH,
+                    currentLength + value.getSize()).getPath()));
+            }
+        } catch (IOException e) {
+            throw new RepositoryException(
+                "Error while retrieving inputstream from parameter value.", e);
+        }
+    }
+
+    /**
+     * Merge all previous chunks with last chunk's stream into a temporary file
+     * and return it.
+     */
+    private File mergeChunks(final Node parentNode,
+            final InputStream lastChunkStream) throws PersistenceException,
+            RepositoryException {
+        OutputStream out = null;
+        File file = null;
+        try {
+            file = File.createTempFile("tmp-", "-mergechunk");
+            out = new BufferedOutputStream(new FileOutputStream(file),
+                16 * 1024);
+            String startPattern = CHUNK_NODE_NAME + "_" + "0_*";
+            NodeIterator nodeItr = parentNode.getNodes(startPattern);
+            InputStream ins = null;
+            int i = 0;
+            while (nodeItr.hasNext()) {
+                if (nodeItr.getSize() > 1) {
+                    throw new RepositoryException(
+                        "more than one node found for pattern: " + startPattern);
+                }
+                Node rangeNode = nodeItr.nextNode();
+
+                try {
+                    InputStream in = rangeNode.getProperty(
+                        javax.jcr.Property.JCR_DATA).getBinary().getStream();
+                    ins = new BufferedInputStream(in, 16 * 1024);
+                    byte[] buf = new byte[16 * 1024];
+                    while ((i = ins.read(buf)) != -1) {
+                        out.write(buf, 0, i);
+                        out.flush();
+                    }
+
+                } finally {
+                    if (ins != null) {
+                        try {
+                            ins.close();
+                        } catch (IOException ignore) {
+
+                        }
+                    }
+                }
+                String[] indexBounds = rangeNode.getName().substring(
+                    (CHUNK_NODE_NAME + "_").length()).split("_");
+                startPattern = CHUNK_NODE_NAME + "_"
+                    + String.valueOf(Long.valueOf(indexBounds[1]) + 1) + "_*";
+                nodeItr = parentNode.getNodes(startPattern);
+            }
+
+            ins = new BufferedInputStream(lastChunkStream, 16 * 1024);
+            byte[] buf = new byte[16 * 1024];
+            while ((i = ins.read(buf)) != -1) {
+                out.write(buf, 0, i);
+                out.flush();
+            }
+        } catch (IOException e) {
+            throw new PersistenceException("excepiton occured", e);
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException ignore) {
+
+                }
+            }
+
+        }
+        return file;
+    }
+
+    /**
+     * Delete all chunks saved within a node. If no chunks exist, it is no-op.
+     */
+    public void deleteChunks(final Node node) throws RepositoryException {
+        Node chunkNode = null;
+        Node jcrContentNode = null;
+        if (hasChunks(node)) {
+            chunkNode = node;
+        } else if (node.hasNode(JCR_CONTENT)
+            && hasChunks((jcrContentNode = node.getNode(JCR_CONTENT)))) {
+            chunkNode = jcrContentNode;
+
+        }
+        if (chunkNode != null) {
+            NodeIterator nodeItr = chunkNode.getNodes(CHUNK_NODE_NAME + "*");
+            while (nodeItr.hasNext()) {
+                Node rangeNode = nodeItr.nextNode();
+                rangeNode.remove();
+            }
+            if (chunkNode.hasProperty(SlingPostConstants.NT_SLING_FILE_LENGTH)) {
+                chunkNode.getProperty(SlingPostConstants.NT_SLING_FILE_LENGTH).remove();
+            }
+            if (chunkNode.hasProperty(SlingPostConstants.NT_SLING_CHUNKS_LENGTH)) {
+                chunkNode.getProperty(
+                    SlingPostConstants.NT_SLING_CHUNKS_LENGTH).remove();
+            }
+            chunkNode.removeMixin(SlingPostConstants.NT_SLING_CHUNK_MIXIN);
+        }
+    }
+
+    /**
+     * Return true if node has chunks stored in it, otherwise false.
+     */
+    private boolean hasChunks(final Node node) throws RepositoryException {
+        for (NodeType nodeType : node.getMixinNodeTypes()) {
+            if (nodeType.getName().equals(
+                SlingPostConstants.NT_SLING_CHUNK_MIXIN)) {
+                return true;
+            }
+        }
+        return false;
+    }
 
     private static final String MT_APP_OCTET = "application/octet-stream";
 

Modified: sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java (original)
+++ sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/AbstractCreateOperation.java Tue Sep  3 12:06:20 2013
@@ -367,6 +367,35 @@ abstract class AbstractCreateOperation e
 
                 continue;
             }
+            if (propPath.endsWith(SlingPostConstants.SUFFIX_OFFSET)) {
+                final RequestProperty prop = getOrCreateRequestProperty(
+                        reqProperties, propPath,
+                        SlingPostConstants.SUFFIX_OFFSET);
+                if (e.getValue().length == 1) {
+                    prop.setOffsetValue(Long.parseLong(e.getValue()[0].toString()));
+                }
+                continue;
+            }
+
+            if (propPath.endsWith(SlingPostConstants.SUFFIX_COMPLETED)) {
+                final RequestProperty prop = getOrCreateRequestProperty(
+                        reqProperties, propPath,
+                        SlingPostConstants.SUFFIX_COMPLETED);
+                if (e.getValue().length == 1) {
+                    prop.setCompleted(Boolean.parseBoolean((e.getValue()[0].toString())));
+                }
+                continue;
+            }
+
+            if (propPath.endsWith(SlingPostConstants.SUFFIX_LENGTH)) {
+                final RequestProperty prop = getOrCreateRequestProperty(
+                        reqProperties, propPath,
+                        SlingPostConstants.SUFFIX_LENGTH);
+                if (e.getValue().length == 1) {
+                    prop.setLength(Long.parseLong(e.getValue()[0].toString()));
+                }
+                continue;
+            }
 
             // plain property, create from values
             final RequestProperty prop = getOrCreateRequestProperty(reqProperties,

Modified: sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java (original)
+++ sling/trunk/bundles/servlets/post/src/main/java/org/apache/sling/servlets/post/impl/operations/DeleteOperation.java Tue Sep  3 12:06:20 2013
@@ -28,65 +28,85 @@ import org.apache.sling.api.resource.Res
 import org.apache.sling.servlets.post.AbstractPostOperation;
 import org.apache.sling.servlets.post.Modification;
 import org.apache.sling.servlets.post.PostResponse;
+import org.apache.sling.servlets.post.SlingPostConstants;
 import org.apache.sling.servlets.post.VersioningConfiguration;
+import org.apache.sling.servlets.post.impl.helper.SlingFileUploadHandler;
 
 /**
  * The <code>DeleteOperation</code> class implements the
- * {@link org.apache.sling.servlets.post.SlingPostConstants#OPERATION_DELETE delete}
- * operation for the Sling default POST servlet.
+ * {@link org.apache.sling.servlets.post.SlingPostConstants#OPERATION_DELETE
+ * delete} operation for the Sling default POST servlet.
  */
 public class DeleteOperation extends AbstractPostOperation {
 
+    /**
+     * handler that deals with file upload
+     */
+    private final SlingFileUploadHandler uploadHandler;
+
+    public DeleteOperation() {
+        this.uploadHandler = new SlingFileUploadHandler();
+    }
+
     @Override
     protected void doRun(final SlingHttpServletRequest request,
-                    final PostResponse response,
-                    final List<Modification> changes)
-    throws RepositoryException {
+            final PostResponse response, final List<Modification> changes)
+            throws RepositoryException {
         final VersioningConfiguration versioningConfiguration = getVersioningConfiguration(request);
-
+        final boolean deleteChunks = isDeleteChunkRequest(request);
         final Iterator<Resource> res = getApplyToResources(request);
         if (res == null) {
-
             final Resource resource = request.getResource();
-            final Node node = resource.adaptTo(Node.class);
-            if ( node != null ) {
-                checkoutIfNecessary(node.getParent(), changes, versioningConfiguration);
+            deleteResource(resource, changes, versioningConfiguration,
+                deleteChunks);
+        } else {
+            while (res.hasNext()) {
+                final Resource resource = res.next();
+                deleteResource(resource, changes, versioningConfiguration,
+                    deleteChunks);
+            }
 
-                node.remove();
+        }
+    }
+
+    /**
+     * Delete chunks if
+     * {@link DeleteOperation#isDeleteChunkRequest(SlingHttpServletRequest)} is
+     * true otherwise delete resource.
+     */
+    private void deleteResource(final Resource resource,
+            final List<Modification> changes,
+            VersioningConfiguration versioningConfiguration,
+            boolean deleteChunks) throws RepositoryException {
+        final Node node = resource.adaptTo(Node.class);
+        if (node != null) {
+            if (deleteChunks) {
+                uploadHandler.deleteChunks(node);
             } else {
-                try {
-                    request.getResourceResolver().delete(resource);
-                } catch (final PersistenceException pe) {
-                    if ( pe.getCause() instanceof RepositoryException ) {
-                        throw (RepositoryException)pe.getCause();
-                    }
-                    throw new RepositoryException(pe);
-                }
+                checkoutIfNecessary(node.getParent(), changes,
+                    versioningConfiguration);
+                node.remove();
             }
-            changes.add(Modification.onDeleted(resource.getPath()));
 
         } else {
-
-            while (res.hasNext()) {
-                final Resource resource = res.next();
-                final Node node = resource.adaptTo(Node.class);
-                if ( node != null ) {
-                    checkoutIfNecessary(node.getParent(), changes, versioningConfiguration);
-                    node.remove();
-                } else {
-                    try {
-                        request.getResourceResolver().delete(resource);
-                    } catch (final PersistenceException pe) {
-                        if ( pe.getCause() instanceof RepositoryException ) {
-                            throw (RepositoryException)pe.getCause();
-                        }
-                        throw new RepositoryException(pe);
-                    }
+            try {
+                resource.getResourceResolver().delete(resource);
+            } catch (final PersistenceException pe) {
+                if (pe.getCause() instanceof RepositoryException) {
+                    throw (RepositoryException) pe.getCause();
                 }
-                changes.add(Modification.onDeleted(resource.getPath()));
+                throw new RepositoryException(pe);
             }
-
         }
+        changes.add(Modification.onDeleted(resource.getPath()));
+    }
+
+    /**
+     * Return true if request is to delete chunks. To return true, request will
+     * should parameter ":applyToChunks" and it should be true.
+     */
+    protected boolean isDeleteChunkRequest(SlingHttpServletRequest request) {
 
+        return Boolean.parseBoolean(request.getParameter(SlingPostConstants.RP_APPLY_TO_CHUNKS));
     }
-}
\ No newline at end of file
+}

Modified: sling/trunk/launchpad/builder/src/main/bundles/list.xml
URL: http://svn.apache.org/viewvc/sling/trunk/launchpad/builder/src/main/bundles/list.xml?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/launchpad/builder/src/main/bundles/list.xml (original)
+++ sling/trunk/launchpad/builder/src/main/bundles/list.xml Tue Sep  3 12:06:20 2013
@@ -146,7 +146,7 @@
         <bundle>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.servlets.post</artifactId>
-            <version>2.3.2</version>
+            <version>2.3.3-SNAPSHOT</version>
         </bundle>
         <bundle>
             <groupId>org.apache.sling</groupId>

Modified: sling/trunk/testing/samples/integration-tests/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/testing/samples/integration-tests/pom.xml?rev=1519658&r1=1519657&r2=1519658&view=diff
==============================================================================
--- sling/trunk/testing/samples/integration-tests/pom.xml (original)
+++ sling/trunk/testing/samples/integration-tests/pom.xml Tue Sep  3 12:06:20 2013
@@ -280,6 +280,12 @@
             <version>1.0.6</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.servlets.post</artifactId>
+            <version>2.3.3-SNAPSHOT</version>
+            <scope>provided</scope>
+        </dependency>
         
         <!-- sling testing tools bundles requires httpclient -->
         <dependency>
@@ -313,6 +319,11 @@
             <version>1.5.11</version>
         </dependency>
         <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>1.4</version>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
             <version>1.5.11</version>

Added: sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java?rev=1519658&view=auto
==============================================================================
--- sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java (added)
+++ sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java Tue Sep  3 12:06:20 2013
@@ -0,0 +1,557 @@
+/*
+ * 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.sling.testing.samples.integrationtests.serverside.sling.post;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import junit.framework.Assert;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.mime.MultipartEntity;
+import org.apache.http.entity.mime.content.ContentBody;
+import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.http.entity.mime.content.StringBody;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.servlets.post.SlingPostConstants;
+import org.apache.sling.testing.tools.sling.SlingTestBase;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SlingPostChunkUploadTest extends SlingTestBase {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String CHUNK_NODE_NAME = "chunk";
+
+    private static final String JCR_CONTENT = "jcr:content";
+
+    FileCutter fileCutter = new FileCutter();
+
+    String parentPath = "/tmp";
+
+    /**
+     * Test chunk upload without interruption.
+     */
+    @Test
+    public void testChunkUpload() {
+        try {
+            // create 1000 byte file
+            File file = createFile("helloworld", 100);
+            int chunkSize = 400;
+            String nodeName = file.getName();
+            uploadChunks(parentPath, file, nodeName, 0, chunkSize,
+                Integer.MAX_VALUE);
+
+            // retrieve the stream on get and validate its content with uploaded
+            // file
+            HttpResponse response = httpGet(parentPath + "/" + nodeName);
+            InputStream fis = new FileInputStream(file);
+            Assert.assertEquals("content stream doesn't match", true,
+                IOUtils.contentEquals(fis, new ByteArrayInputStream(
+                    getRequestExecutor().getContent().getBytes())));
+            fis.close();
+            // clean uploaded file from repository
+            Map<String, String> reqParams = new HashMap<String, String>();
+            reqParams.put(SlingPostConstants.RP_OPERATION, "delete");
+            response = uploadMultiPart(parentPath + "/" + nodeName, reqParams,
+                null, null);
+
+            // status should be 404
+            response = httpGet(parentPath + "/" + nodeName);
+            Assert.assertEquals("status should be 404 not found ", 404,
+                response.getStatusLine().getStatusCode());
+            file.delete();
+        } catch (Exception e) {
+            log.error("error:", e);
+            Assert.fail("exception caught: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Test chunk upload after interruption. Test the use of variable chunk
+     * size. After interruption, client retrieves chunk upload information and
+     * resume upload with variable chunk size.
+     */
+    @Test
+    public void testInterruptedChunkUpload() {
+        try {
+            // create 1700 bytes file
+            File file = createFile("helloworld", 170);
+            String nodeName = file.getName();
+            int chunkSize = 200;
+            // uplaod first chunk 200 bytes uploaded
+            uploadChunks(parentPath, file, nodeName, 0, chunkSize, 1);
+            JSONObject json = getChunkJson(parentPath + "/" + nodeName);
+            validate(json, 200, 1);
+
+            chunkSize = 300;
+            // upload next two chunks of 300 each.total 800 bytes
+            // uploaded
+            uploadChunks(parentPath, file, nodeName, 200, chunkSize, 2);
+            json = getChunkJson(parentPath + "/" + nodeName);
+            validate(json, 800, 3);
+
+            chunkSize = 400;
+            // upload two chunk of 400 each. total 1600 bytes and 5 chunks
+            // uploaded
+            uploadChunks(parentPath, file, nodeName,
+                json.getInt(SlingPostConstants.NT_SLING_CHUNKS_LENGTH),
+                chunkSize, 2);
+            json = getChunkJson(parentPath + "/" + nodeName);
+            validate(json, 1600, 5);
+
+            chunkSize = 500;
+            uploadChunks(parentPath, file, nodeName,
+                json.getInt(SlingPostConstants.NT_SLING_CHUNKS_LENGTH),
+                chunkSize, Integer.MAX_VALUE);
+
+            HttpResponse response = httpGet(parentPath + "/" + nodeName);
+            InputStream fis = new FileInputStream(file);
+            Assert.assertEquals("content stream doesn't match", true,
+                IOUtils.contentEquals(fis, new ByteArrayInputStream(
+                    getRequestExecutor().getContent().getBytes())));
+            fis.close();
+
+            // clean uploaded file from repository
+            Map<String, String> reqParams = new HashMap<String, String>();
+            reqParams.put(SlingPostConstants.RP_OPERATION, "delete");
+            response = uploadMultiPart(parentPath + "/" + nodeName, reqParams,
+                null, null);
+
+            // status should be 404
+            response = httpGet(parentPath + "/" + nodeName);
+            Assert.assertEquals("status should be 404 not found ", 404,
+                response.getStatusLine().getStatusCode());
+            file.delete();
+        } catch (Exception e) {
+            log.error("error:", e);
+            Assert.fail("exception caught: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Test two concurrent chunk upload. Second will fail. Test deletion of
+     * incomplete upload and test new upload on the same path.
+     */
+    @Test
+    public void testConcurrentChunkUpload() {
+        try {
+            // create 1700 bytes file
+            File file = createFile("helloworld", 170);
+
+            String nodeName = file.getName();
+            int chunkSize = 200;
+            // uplaod 3 chunk of 200 bytes uploaded
+            uploadChunks(parentPath, file, nodeName, 0, chunkSize, 3);
+            JSONObject json = getChunkJson(parentPath + "/" + file.getName());
+            validate(json, 600, 3);
+
+            // create 1000 bytes file
+            File secondFile = createFile("helloearth", 100);
+            chunkSize = 300;
+            // upload next two chunks of 300 each.total 800 bytes
+            // uploaded
+            try {
+                uploadChunks(parentPath, secondFile, nodeName, 0, chunkSize, 1);
+                Assert.fail("second upload should fail");
+            } catch (Exception ignore) {
+
+            }
+            try {
+                uploadChunks(parentPath, secondFile, nodeName, 200, chunkSize,
+                    2);
+                Assert.fail("second upload should fail");
+            } catch (Exception ignore) {
+
+            }
+            // clean uploaded file from repository
+            Map<String, String> reqParams = new HashMap<String, String>();
+            reqParams.put(SlingPostConstants.RP_OPERATION, "delete");
+            reqParams.put(":applyToChunks", "true");
+            HttpResponse response = uploadMultiPart(
+                parentPath + "/" + file.getName(), reqParams, null, null);
+            Assert.assertEquals("status should be 200 OK ", 200,
+                response.getStatusLine().getStatusCode());
+
+            chunkSize = 200;
+            uploadChunks(parentPath, secondFile, nodeName, 0, chunkSize,
+                Integer.MAX_VALUE);
+
+            response = httpGet(parentPath + "/" + nodeName);
+            InputStream fis = new FileInputStream(secondFile);
+            Assert.assertEquals("content stream doesn't match", true,
+                IOUtils.contentEquals(fis, new ByteArrayInputStream(
+                    getRequestExecutor().getContent().getBytes())));
+            fis.close();
+
+            // clean uploaded file from repository
+            reqParams = new HashMap<String, String>();
+            reqParams.put(SlingPostConstants.RP_OPERATION, "delete");
+            response = uploadMultiPart(parentPath + "/" + nodeName, reqParams,
+                null, null);
+
+            // status should be 404
+            response = httpGet(parentPath + "/" + nodeName);
+            Assert.assertEquals("status should be 404 not found ", 404,
+                response.getStatusLine().getStatusCode());
+            file.delete();
+            secondFile.delete();
+        } catch (Exception e) {
+            log.error("error:", e);
+            Assert.fail("exception caught: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Test chunk upload from midway
+     */
+    @Test
+    public void testMidwayChunkUpload() {
+        File file = null;
+        try {
+            // create 1700 bytes file
+            file = createFile("helloworld", 170);
+            int chunkSize = 200;
+            try {
+                uploadChunks(parentPath, file, file.getName(), 200, chunkSize,
+                    1);
+                Assert.fail("upload should fail");
+            } catch (Exception ignore) {
+
+            }
+
+        } catch (Exception e) {
+            log.error("error:", e);
+            Assert.fail("exception caught: " + e.getMessage());
+        } finally {
+            file.delete();
+        }
+    }
+
+    /**
+     * Test upload on a existing node. Test that binary content doesn't get
+     * updated until chunk upload finishes.
+     */
+    @Test
+    public void testChunkUploadOnExistingNode() {
+        try {
+            // create 1700 bytes file
+            File file = createFile("helloworld", 170);
+            String nodeName = file.getName();
+            InputStream fis = new FileInputStream(file);
+            uploadMultiPart(parentPath, null, fis, file.getName());
+            fis.close();
+            HttpResponse response = httpGet(parentPath + "/" + nodeName);
+            fis = new FileInputStream(file);
+            Assert.assertEquals("content stream doesn't match", true,
+                IOUtils.contentEquals(fis, new ByteArrayInputStream(
+                    getRequestExecutor().getContent().getBytes())));
+            fis.close();
+            // create 1000 bytes file
+            File secondFile = createFile("helloearth", 100);
+            int chunkSize = 200;
+            // uplaod 3 chunk of 200 bytes uploaded
+            uploadChunks(parentPath, secondFile, nodeName, 0, chunkSize, 3);
+            JSONObject json = getChunkJson(parentPath + "/" + nodeName);
+            validate(json, 600, 3);
+
+            response = httpGet(parentPath + "/" + nodeName);
+            fis = new FileInputStream(file);
+            Assert.assertEquals("content stream doesn't match", true,
+                IOUtils.contentEquals(fis, new ByteArrayInputStream(
+                    getRequestExecutor().getContent().getBytes())));
+            fis.close();
+
+            uploadChunks(parentPath, secondFile, nodeName, 600, chunkSize,
+                Integer.MAX_VALUE);
+            response = httpGet(parentPath + "/" + nodeName);
+            fis = new FileInputStream(secondFile);
+            Assert.assertEquals("content stream doesn't match", true,
+                IOUtils.contentEquals(fis, new ByteArrayInputStream(
+                    getRequestExecutor().getContent().getBytes())));
+            fis.close();
+
+            // clean uploaded file from repository
+            Map<String, String> reqParams = new HashMap<String, String>();
+            reqParams.put(SlingPostConstants.RP_OPERATION, "delete");
+            response = uploadMultiPart(parentPath + "/" + nodeName, reqParams,
+                null, null);
+
+            // status should be 404
+            response = httpGet(parentPath + "/" + nodeName);
+            Assert.assertEquals("status should be 404 not found ", 404,
+                response.getStatusLine().getStatusCode());
+            file.delete();
+            secondFile.delete();
+        } catch (Exception e) {
+            log.error("error:", e);
+            Assert.fail("exception caught: " + e.getMessage());
+        } finally {
+
+        }
+    }
+
+    /**
+     * Test use case where file size is not known in advance. File parameter
+     * "@Completed" indicates file completion.
+     */
+    @Test
+    public void testChunkUploadOnStreaming() {
+        try {
+            // create 1700 bytes file
+            File file = createFile("helloworld", 170);
+            String nodeName = file.getName();
+            InputStream fis = new FileInputStream(file);
+            int chunkSize = 200;
+            uploadPart(parentPath, file, file.getName(), 0, 0, chunkSize, false);
+            uploadPart(parentPath, file, file.getName(), 0, 200, chunkSize,
+                false);
+
+            uploadPart(parentPath, file, file.getName(), 0, 400, chunkSize,
+                true);
+            fis.close();
+
+            File secondFile = createFile("helloworld", 60);
+            HttpResponse response = httpGet(parentPath + "/" + nodeName);
+            fis = new FileInputStream(secondFile);
+            Assert.assertEquals("content stream doesn't match", true,
+                IOUtils.contentEquals(fis, new ByteArrayInputStream(
+                    getRequestExecutor().getContent().getBytes())));
+            fis.close();
+            // clean uploaded file from repository
+            Map<String, String> reqParams = new HashMap<String, String>();
+            reqParams.put(SlingPostConstants.RP_OPERATION, "delete");
+            response = uploadMultiPart(parentPath + "/" + nodeName, reqParams,
+                null, null);
+
+            // status should be 404
+            response = httpGet(parentPath + "/" + nodeName);
+            Assert.assertEquals("status should be 404 not found ", 404,
+                response.getStatusLine().getStatusCode());
+            file.delete();
+            secondFile.delete();
+        } catch (Exception e) {
+            log.error("error:", e);
+            Assert.fail("exception caught: " + e.getMessage());
+        } finally {
+        }
+    }
+
+    /**
+     * create temporary file of size
+     */
+    private File createFile(String baseString, long times) throws Exception {
+        OutputStream os = null;
+        File file = null;
+        try {
+            file = File.createTempFile("test", "chunkupload");
+            // create 1700 bytes file
+            String data = appendString(baseString, times);
+            os = new FileOutputStream(file);
+            IOUtils.write(data, os);
+            os.close();
+            if (!file.exists()) {
+                throw new Exception(file.getAbsolutePath() + "  not found");
+            }
+        } finally {
+            try {
+                os.close();
+            } catch (Exception ignore) {
+            }
+        }
+        return file;
+    }
+
+    /**
+     * To query chunk upload in json
+     */
+    private JSONObject getChunkJson(String path) throws Exception {
+        JSONObject json = null;
+        HttpResponse response = httpGet(path + ".3.json");
+        String jsonStr = getRequestExecutor().getContent();
+        json = new JSONObject(jsonStr);
+        if (json.has(JCR_CONTENT)) {
+            json = json.getJSONObject(JCR_CONTENT);
+        }
+        return json;
+    }
+
+    private void validate(JSONObject json, int bytesUploaded, int expectedChunks)
+            throws Exception {
+        Assert.assertEquals("bytesUploaded didn't match", bytesUploaded,
+            json.optInt(SlingPostConstants.NT_SLING_CHUNKS_LENGTH, 0));
+        int chunkCount = 0;
+        Iterator<String> itr = json.keys();
+        while (itr != null && itr.hasNext()) {
+            String key = itr.next();
+            if (key.startsWith(CHUNK_NODE_NAME)) {
+                chunkCount++;
+
+            }
+
+        }
+        Assert.assertEquals("chunksuploaded didn't match", expectedChunks,
+            chunkCount);
+    }
+
+    /**
+     * upload 'numOfChunks' number of chunks starting from offset with size
+     * equals to chunkSize or till end of file is reached.
+     */
+
+    private int uploadChunks(String path, File file, String nodeName,
+            int offSet, int chunkSize, int numOfChunks) throws Exception {
+        int length = new Long(file.length()).intValue();
+        int chunkNumber = 0;
+        while (offSet < length && chunkNumber < numOfChunks) {
+            if (offSet + chunkSize >= length) {
+                chunkSize = length - offSet;
+            }
+            uploadPart(path, file, nodeName, file.length(), offSet, chunkSize,
+                null);
+            offSet += chunkSize;
+            chunkNumber++;
+        }
+        return chunkNumber;
+    }
+
+    /**
+     * upload single chunk starting from offset of size chunkSize.
+     * 
+     * @param typeHint TODO
+     */
+    private HttpResponse uploadPart(String path, File file, String nodeName,
+            long length, long offSet, Integer chunkSize, Boolean isComplete)
+            throws Exception {
+        byte[] buf = fileCutter.cutFile(file, offSet, chunkSize);
+        log.debug(Thread.currentThread().getName() + ": uploading bytes from "
+            + offSet + " to " + (offSet + chunkSize - 1));
+        ByteArrayInputStream instream = new ByteArrayInputStream(buf);
+        Map<String, String> reqParams = new HashMap<String, String>();
+        reqParams.put(nodeName + SlingPostConstants.SUFFIX_OFFSET,
+            String.valueOf(offSet));
+        if (length > 0) {
+            reqParams.put(nodeName + SlingPostConstants.SUFFIX_LENGTH,
+                Long.toString(length));
+        }
+        if (isComplete != null) {
+            reqParams.put(nodeName + SlingPostConstants.SUFFIX_COMPLETED,
+                isComplete.toString());
+        }
+        return uploadMultiPart(path, reqParams, instream, nodeName);
+
+    }
+
+    /**
+     * send multipart post request to server.
+     */
+
+    private HttpResponse uploadMultiPart(String path,
+            Map<String, String> reqParams, InputStream ins, String fileName)
+            throws Exception {
+        Charset utf8 = Charset.availableCharsets().get("UTF-8");
+        MultipartEntity reqEntity = new MultipartEntity();
+        HttpPost httppost = new HttpPost(getRequestBuilder().buildUrl(path));
+        if (reqParams != null) {
+            for (Map.Entry<String, String> entry : reqParams.entrySet()) {
+                String key = entry.getKey();
+                String value = entry.getValue();
+                reqEntity.addPart(key, new StringBody(value, utf8));
+            }
+        }
+        if (ins != null) {
+            ContentBody contentBody = new InputStreamBody(ins, fileName);
+            reqEntity.addPart(fileName, contentBody);
+        }
+        httppost.setEntity(reqEntity);
+        HttpResponse response = getRequestExecutor().execute(
+            getRequestBuilder().buildOtherRequest(httppost).withCredentials(
+                getServerUsername(), getServerPassword())).getResponse();
+
+        int status = response.getStatusLine().getStatusCode();
+        if (status < 200 || status >= 300) {
+            log.debug("response status = " + status);
+            log.debug("output=" + getRequestExecutor().getContent());
+            throw new Exception(response.getStatusLine().getReasonPhrase());
+        }
+
+        return response;
+
+    }
+
+    /**
+     * Send http get request to server.
+     */
+    private HttpResponse httpGet(String path) throws Exception {
+        return getRequestExecutor().execute(
+            getRequestBuilder().buildGetRequest(path).withCredentials(
+                getServerUsername(), getServerPassword())).getResponse();
+
+    }
+
+    /**
+     * create a string of baseString * times
+     */
+    private String appendString(String baseString, long times) {
+        StringBuffer buf = new StringBuffer(baseString);
+        for (long i = 1; i < times; i++) {
+            buf.append(baseString);
+        }
+        return buf.toString();
+    }
+
+    /**
+     * File cutter utility class
+     */
+    private class FileCutter {
+
+        /**
+         * Cut file slice of length size or less starting from offSet. Less in
+         * case where offset + size < file.length()
+         */
+
+        public byte[] cutFile(File file, long offSet, int size)
+                throws IOException {
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(file);
+                fis.skip(offSet);
+                byte[] tmp = new byte[size];
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                int l = fis.read(tmp);
+                baos.write(tmp, 0, l);
+                return baos.toByteArray();
+            } finally {
+                fis.close();
+            }
+        }
+    }
+}

Propchange: sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/testing/samples/integration-tests/src/test/java/org/apache/sling/testing/samples/integrationtests/serverside/sling/post/SlingPostChunkUploadTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message