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