Author: cziegeler
Date: Mon Sep 16 11:27:33 2013
New Revision: 1523593

URL: http://svn.apache.org/r1523593
Log:
SLING-3036 :  Feedback on SLING-2707: Support of chunked file upload. Apply 
patch from Shashank Gupta

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

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=1523593&r1=1523592&r2=1523593&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
 Mon Sep 16 11:27:33 2013
@@ -32,21 +32,3 @@
     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=1523593&r1=1523592&r2=1523593&view=diff
==============================================================================
--- sling/trunk/bundles/servlets/post/pom.xml (original)
+++ sling/trunk/bundles/servlets/post/pom.xml Mon Sep 16 11:27:33 2013
@@ -70,6 +70,12 @@
                         <Embed-Dependency>
                             
jackrabbit-jcr-commons;inline=org/apache/jackrabbit/util/ISO8601.class|org/apache/jackrabbit/util/Text.class
                         </Embed-Dependency>
+                         <Sling-Namespaces>
+                            sling=http://sling.apache.org/jcr/sling/1.0
+                        </Sling-Namespaces>
+                        <Sling-Nodetypes>
+                            SLING-INF/nodetypes/chunk.cnd,
+                        </Sling-Nodetypes>
                     </instructions>
                 </configuration>
             </plugin>
@@ -131,6 +137,12 @@
             <version>2.0.2-incubator</version>
             <scope>provided</scope>
         </dependency>
+         <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.json</artifactId>

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=1523593&r1=1523592&r2=1523593&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
 Mon Sep 16 11:27:33 2013
@@ -552,5 +552,11 @@ public interface SlingPostConstants {
      * @since 2.3.4
      */
     public static final String NT_SLING_CHUNK_OFFSET = "sling:offset";
+    
+    /**
+     * Constant for prefix for sling:chunk node name.
+     * @since 2.3.4
+     */
+    public static final String CHUNK_NODE_NAME = "chunk";
 
 }

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=1523593&r1=1523592&r2=1523593&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
 Mon Sep 16 11:27:33 2013
@@ -65,13 +65,7 @@ public class RequestProperty {
 
     private boolean patch = false;
 
-    private long offset;
-
-    private long length;
-
-    private boolean completed;
-
-    private boolean chunkUpload;
+    private Chunk chunk;
 
     public RequestProperty(String path) {
         assert path.startsWith("/");
@@ -309,55 +303,17 @@ public class RequestProperty {
     }
 
     /**
-     * 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.
+     *  Return true if request is chunk upload.
      */
-    public boolean isCompleted() {
-        return completed;
+    public boolean isChunkUpload() {
+        return chunk != null;
     }
 
-    /**
-     * Set complete flag
-     */
-    public void setCompleted(boolean complete) {
-        this.completed = complete;
+    public Chunk getChunk() {
+        return chunk;
     }
 
-    /**
-     *  Return true if request is chunk upload.
-     */
-    public boolean isChunkUpload() {
-        return chunkUpload;
+    public void setChunk(Chunk chunk) {
+        this.chunk = chunk;
     }
 }

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=1523593&r1=1523592&r2=1523593&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
 Mon Sep 16 11:27:33 2013
@@ -17,7 +17,6 @@
 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;
@@ -25,10 +24,14 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.SequenceInputStream;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
@@ -37,6 +40,7 @@ import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.NodeTypeManager;
 import javax.servlet.ServletContext;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.util.Text;
 import org.apache.sling.api.request.RequestParameter;
 import org.apache.sling.api.resource.ModifiableValueMap;
@@ -44,6 +48,8 @@ import org.apache.sling.api.resource.Per
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.servlets.post.Modification;
 import org.apache.sling.servlets.post.SlingPostConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Handles file uploads.
@@ -97,8 +103,8 @@ public class SlingFileUploadHandler {
     public static final String JCR_MIMETYPE = "jcr:mimeType";
     public static final String JCR_ENCODING = "jcr:encoding";
     public static final String JCR_DATA = "jcr:data";
-
-    private static final String CHUNK_NODE_NAME = "chunk";
+    
+    private final Logger log = LoggerFactory.getLogger(getClass());
 
     /**
      * The servlet context.
@@ -237,19 +243,20 @@ public class SlingFileUploadHandler {
      * 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
+     * merge all previous chunks and last 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();
+            long chunkOffset = prop.getChunk().getOffset();
             if (chunkOffset == 0) {
                 // first chunk
                 // check if another chunk upload is already in progress. throw
                 // exception
-                NodeIterator itr = res.getNodes(CHUNK_NODE_NAME + "*");
+                NodeIterator itr = 
res.getNodes(SlingPostConstants.CHUNK_NODE_NAME
+                    + "*");
                 if (itr.hasNext()) {
                     throw new RepositoryException(
                         "Chunk upload already in progress at {" + res.getPath()
@@ -270,7 +277,7 @@ public class SlingFileUploadHandler {
             }
             long currentLength = res.getProperty(
                 SlingPostConstants.NT_SLING_CHUNKS_LENGTH).getLong();
-            long totalLength = prop.getLength();
+            long totalLength = prop.getChunk().getLength();
             if (chunkOffset != currentLength) {
                 throw new RepositoryException("Chunk's offset {"
                     + chunkOffset
@@ -293,17 +300,17 @@ public class SlingFileUploadHandler {
                         totalLength);
                 }
             }
-            NodeIterator itr = res.getNodes(CHUNK_NODE_NAME + "_"
-                + String.valueOf(chunkOffset) + "*");
+            NodeIterator itr = res.getNodes(SlingPostConstants.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 nodeName = SlingPostConstants.CHUNK_NODE_NAME + "_"
                 + String.valueOf(chunkOffset) + "_"
                 + String.valueOf(chunkOffset + value.getSize() - 1);
             if (totalLength == (currentLength + value.getSize())
-                || prop.isCompleted()) {
+                || prop.getChunk().isCompleted()) {
                 File file = null;
                 InputStream fileIns = null;
                 try {
@@ -311,7 +318,8 @@ public class SlingFileUploadHandler {
                     fileIns = new FileInputStream(file);
                     changes.add(Modification.onModified(res.setProperty(
                         JCR_DATA, fileIns).getPath()));
-                    NodeIterator nodeItr = res.getNodes(CHUNK_NODE_NAME + "*");
+                    NodeIterator nodeItr = 
res.getNodes(SlingPostConstants.CHUNK_NODE_NAME
+                        + "*");
                     while (nodeItr.hasNext()) {
                         Node nodeRange = nodeItr.nextNode();
                         
changes.add(Modification.onDeleted(nodeRange.getPath()));
@@ -363,15 +371,17 @@ public class SlingFileUploadHandler {
             final InputStream lastChunkStream) throws PersistenceException,
             RepositoryException {
         OutputStream out = null;
+        SequenceInputStream  mergeStrm = null;
         File file = null;
         try {
             file = File.createTempFile("tmp-", "-mergechunk");
-            out = new BufferedOutputStream(new FileOutputStream(file),
-                16 * 1024);
-            String startPattern = CHUNK_NODE_NAME + "_" + "0_*";
+            out = new FileOutputStream(file);
+            String startPattern = SlingPostConstants.CHUNK_NODE_NAME + "_"
+                + "0_*";
             NodeIterator nodeItr = parentNode.getNodes(startPattern);
             InputStream ins = null;
             int i = 0;
+            Set<InputStream> inpStrmSet = new LinkedHashSet<InputStream>();
             while (nodeItr.hasNext()) {
                 if (nodeItr.getSize() > 1) {
                     throw new RepositoryException(
@@ -379,48 +389,26 @@ public class SlingFileUploadHandler {
                 }
                 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) {
-
-                        }
-                    }
-                }
+                inpStrmSet.add(rangeNode.getProperty(
+                    javax.jcr.Property.JCR_DATA).getBinary().getStream());
+                log.debug("added chunk {} to merge stream", 
rangeNode.getName());
                 String[] indexBounds = rangeNode.getName().substring(
-                    (CHUNK_NODE_NAME + "_").length()).split("_");
-                startPattern = CHUNK_NODE_NAME + "_"
+                    (SlingPostConstants.CHUNK_NODE_NAME + "_").length()).split(
+                    "_");
+                startPattern = SlingPostConstants.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();
-            }
+            inpStrmSet.add(lastChunkStream);
+            mergeStrm = new SequenceInputStream(
+                Collections.enumeration(inpStrmSet));
+            IOUtils.copyLarge(mergeStrm, out);
         } catch (IOException e) {
             throw new PersistenceException("excepiton occured", e);
         } finally {
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (IOException ignore) {
-
-                }
-            }
+            IOUtils.closeQuietly(out);
+            IOUtils.closeQuietly(mergeStrm);
 
         }
         return file;
@@ -430,30 +418,75 @@ public class SlingFileUploadHandler {
      * 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;
+        // parent node containing all chunks and has mixin sling:chunks applied
+        // on it.
+        Node chunkParent = null;
         Node jcrContentNode = null;
         if (hasChunks(node)) {
-            chunkNode = node;
+            chunkParent = node;
         } else if (node.hasNode(JCR_CONTENT)
             && hasChunks((jcrContentNode = node.getNode(JCR_CONTENT)))) {
-            chunkNode = jcrContentNode;
+            chunkParent = jcrContentNode;
 
         }
-        if (chunkNode != null) {
-            NodeIterator nodeItr = chunkNode.getNodes(CHUNK_NODE_NAME + "*");
+        if (chunkParent != null) {
+            NodeIterator nodeItr = 
chunkParent.getNodes(SlingPostConstants.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 
(chunkParent.hasProperty(SlingPostConstants.NT_SLING_FILE_LENGTH)) {
+                
chunkParent.getProperty(SlingPostConstants.NT_SLING_FILE_LENGTH).remove();
             }
-            if 
(chunkNode.hasProperty(SlingPostConstants.NT_SLING_CHUNKS_LENGTH)) {
-                chunkNode.getProperty(
+            if 
(chunkParent.hasProperty(SlingPostConstants.NT_SLING_CHUNKS_LENGTH)) {
+                chunkParent.getProperty(
                     SlingPostConstants.NT_SLING_CHUNKS_LENGTH).remove();
             }
-            chunkNode.removeMixin(SlingPostConstants.NT_SLING_CHUNK_MIXIN);
+            chunkParent.removeMixin(SlingPostConstants.NT_SLING_CHUNK_MIXIN);
+        }
+    }
+
+    /**
+     * Get the last {@link SlingPostConstants#NT_SLING_CHUNK_NODETYPE}
+     * {@link Node}.
+     * 
+     * @param node {@link Node} containing
+     *            {@link SlingPostConstants#NT_SLING_CHUNK_NODETYPE}
+     *            {@link Node}s
+     * @return the {@link SlingPostConstants#NT_SLING_CHUNK_NODETYPE} chunk
+     *         node.
+     * @throws RepositoryException
+     */
+    public Node getLastChunk(Node node) throws RepositoryException {
+        // parent node containing all chunks and has mixin sling:chunks applied
+        // on it.
+        Node chunkParent = null;
+        Node jcrContentNode = null;
+        if (hasChunks(node)) {
+            chunkParent = node;
+        } else if (node.hasNode(JCR_CONTENT)
+            && hasChunks((jcrContentNode = node.getNode(JCR_CONTENT)))) {
+            chunkParent = jcrContentNode;
+
+        }
+        String startPattern = SlingPostConstants.CHUNK_NODE_NAME + "_" + "0_*";
+        NodeIterator nodeItr = chunkParent.getNodes(startPattern);
+        Node chunkNode = null;
+        while (nodeItr.hasNext()) {
+            if (nodeItr.getSize() > 1) {
+                throw new RepositoryException(
+                    "more than one node found for pattern: " + startPattern);
+            }
+            chunkNode = nodeItr.nextNode();
+
+            String[] indexBounds = chunkNode.getName().substring(
+                (SlingPostConstants.CHUNK_NODE_NAME + 
"_").length()).split("_");
+            startPattern = SlingPostConstants.CHUNK_NODE_NAME + "_"
+                + String.valueOf(Long.valueOf(indexBounds[1]) + 1) + "_*";
+            nodeItr = chunkParent.getNodes(startPattern);
         }
+        return chunkNode;
     }
 
     /**

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=1523593&r1=1523592&r2=1523593&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
 Mon Sep 16 11:27:33 2013
@@ -46,6 +46,7 @@ import org.apache.sling.servlets.post.No
 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.Chunk;
 import org.apache.sling.servlets.post.impl.helper.DefaultNodeNameGenerator;
 import org.apache.sling.servlets.post.impl.helper.RequestProperty;
 
@@ -372,7 +373,12 @@ abstract class AbstractCreateOperation e
                         reqProperties, propPath,
                         SlingPostConstants.SUFFIX_OFFSET);
                 if (e.getValue().length == 1) {
-                    
prop.setOffsetValue(Long.parseLong(e.getValue()[0].toString()));
+                    Chunk chunk = prop.getChunk();
+                    if(chunk == null){
+                        chunk = new Chunk();
+                    }
+                    
chunk.setOffsetValue(Long.parseLong(e.getValue()[0].toString()));
+                    prop.setChunk(chunk);
                 }
                 continue;
             }
@@ -382,7 +388,12 @@ abstract class AbstractCreateOperation e
                         reqProperties, propPath,
                         SlingPostConstants.SUFFIX_COMPLETED);
                 if (e.getValue().length == 1) {
-                    
prop.setCompleted(Boolean.parseBoolean((e.getValue()[0].toString())));
+                    Chunk chunk = prop.getChunk();
+                    if(chunk == null){
+                        chunk = new Chunk();
+                    }
+                    
chunk.setCompleted(Boolean.parseBoolean((e.getValue()[0].toString())));
+                    prop.setChunk(chunk);
                 }
                 continue;
             }
@@ -392,7 +403,12 @@ abstract class AbstractCreateOperation e
                         reqProperties, propPath,
                         SlingPostConstants.SUFFIX_LENGTH);
                 if (e.getValue().length == 1) {
-                    prop.setLength(Long.parseLong(e.getValue()[0].toString()));
+                    Chunk chunk = prop.getChunk();
+                    if(chunk == null){
+                        chunk = new Chunk();
+                    }
+                    
chunk.setLength(Long.parseLong(e.getValue()[0].toString()));
+                    prop.setChunk(chunk);
                 }
                 continue;
             }


Reply via email to