/*
* (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed 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.
*
* Contributors:
* dmetzler
* ataillefer
* Gabriel Barata
*/
package org.nuxeo.ecm.restapi.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.io.IOUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nuxeo.ecm.automation.core.operations.blob.CreateBlob;
import org.nuxeo.ecm.automation.server.jaxrs.batch.BatchManager;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.PathRef;
import org.nuxeo.ecm.core.test.annotations.Granularity;
import org.nuxeo.ecm.core.test.annotations.RepositoryConfig;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.test.runner.Features;
import org.nuxeo.runtime.test.runner.FeaturesRunner;
import org.nuxeo.runtime.test.runner.Jetty;
import org.nuxeo.runtime.test.runner.LocalDeploy;
import org.nuxeo.runtime.transaction.TransactionHelper;
import org.nuxeo.transientstore.test.TransientStoreFeature;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.multipart.BodyPart;
import com.sun.jersey.multipart.FormDataMultiPart;
import com.sun.jersey.multipart.file.StreamDataBodyPart;
/**
* @since 7.10
*/
@RunWith(FeaturesRunner.class)
@Features({ TransientStoreFeature.class, RestServerFeature.class })
@Jetty(port = 18090)
@RepositoryConfig(cleanup = Granularity.METHOD, init = RestServerInit.class)
@LocalDeploy("org.nuxeo.ecm.platform.restapi.test:multiblob-doctype.xml")
public class BatchUploadFixture extends BaseTest {
@Inject
CoreSession session;
/**
* Tests the deprecated /batch/upload endpoint.
*
* @deprecated since 7.4
* @see org.nuxeo.ecm.automation.server.jaxrs.batch.BatchResource#doPost(HttpServletRequest)
* @see #itCanUseBatchUpload()
*/
@Deprecated
@Test
public void itCanUseBatchResource() throws Exception {
String filename = "testfile";
String data = "batchUploadedData";
// upload the file in automation
Map<String, String> headers = new HashMap<String, String>();
headers.put("X-File-Idx", "0");
headers.put("X-File-Name", filename);
ClientResponse response = getResponse(RequestType.POST, "automation/batch/upload", data, headers);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
assertNotNull(batchId);
// create the doc which references the given blob
String json = "{";
json += "\"entity-type\":\"document\" ,";
json += "\"name\":\"testBatchUploadDoc\" ,";
json += "\"type\":\"MultiBlobDoc\" ,";
json += "\"properties\" : {";
json += "\"mb:blobs\" : [ ";
json += "{ \"filename\" : \"" + filename + "\" , \"content\" : { \"upload-batch\": \"" + batchId
+ "\", \"upload-fileId\": \"0\" } }";
json += "]}}";
response = getResponse(RequestType.POST, "path/", json);
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
DocumentModel doc = session.getDocument(new PathRef("/testBatchUploadDoc"));
Blob blob = (Blob) doc.getPropertyValue("mb:blobs/0/content");
assertNotNull(blob);
assertEquals(data, blob.getString());
}
/**
* Tests the /upload endpoints.
*
* @since 7.4
*/
@Test
public void itCanUseBatchUpload() throws IOException {
itCanUseBatchUpload(false);
}
/**
* Tests the /upload endpoints with the "X-Batch-No-Drop" header set to true.
*
* @since 8.4
*/
@Test
public void itCanUseBatchUploadNoDrop() throws IOException {
itCanUseBatchUpload(true);
}
private void itCanUseBatchUpload(boolean noDrop) throws IOException {
// Get batch id, used as a session id
ClientResponse response = getResponse(RequestType.POST, "upload");
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
assertNotNull(batchId);
// Upload a file not in multipart
String fileName1 = URLEncoder.encode("Fichier accentué 1.txt", "UTF-8");
String mimeType = "text/plain";
String data1 = "Contenu accentué du premier fichier";
String fileSize1 = String.valueOf(data1.getBytes().length);
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/octet-stream");
headers.put("X-Upload-Type", "normal");
headers.put("X-File-Name", fileName1);
headers.put("X-File-Size", fileSize1);
headers.put("X-File-Type", mimeType);
response = getResponse(RequestType.POST, "upload/" + batchId + "/0", data1, headers);
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
assertEquals(fileSize1, node.get("uploadedSize").getValueAsText());
// Upload a file in multipart
String fileName2 = "Fichier accentué 2.txt";
String data2 = "Contenu accentué du deuxième fichier";
String fileSize2 = String.valueOf(data2.getBytes().length);
headers = new HashMap<String, String>();
headers.put("X-File-Size", fileSize2);
FormDataMultiPart form = new FormDataMultiPart();
BodyPart fdp = new StreamDataBodyPart(fileName2, new ByteArrayInputStream(data2.getBytes()));
form.bodyPart(fdp);
response = getResponse(RequestType.POST, "upload/" + batchId + "/1", form, headers);
form.close();
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
String strResponse = IOUtils.toString(response.getEntityInputStream(), "UTF-8");
assertTrue(strResponse.startsWith("<html>") && strResponse.endsWith("</html>"));
strResponse = strResponse.substring(6, strResponse.length() - 7);
node = mapper.readTree(strResponse);
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("1", node.get("fileIdx").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
assertEquals(fileSize2, node.get("uploadedSize").getValueAsText());
// Get batch info
response = getResponse(RequestType.GET, "upload/" + batchId);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
ArrayNode nodes = (ArrayNode) mapper.readTree(response.getEntityInputStream());
assertEquals(2, nodes.size());
node = nodes.get(0);
assertEquals("Fichier accentué 1.txt", node.get("name").getValueAsText());
assertEquals(fileSize1, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
node = nodes.get(1);
assertEquals("Fichier accentué 2.txt", node.get("name").getValueAsText());
assertEquals(fileSize2, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
// Get file infos
response = getResponse(RequestType.GET, "upload/" + batchId + "/0");
assertEquals(Status.OK.getStatusCode(), response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("Fichier accentué 1.txt", node.get("name").getValueAsText());
assertEquals(fileSize1, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
response = getResponse(RequestType.GET, "upload/" + batchId + "/1");
assertEquals(Status.OK.getStatusCode(), response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("Fichier accentué 2.txt", node.get("name").getValueAsText());
assertEquals(fileSize2, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
// Get file upload statuses by doing an empty POST
response = getResponse(RequestType.POST, "upload/" + batchId + "/0");
assertEquals(201, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
// TODO NXP-18247: Put actual uploaded size in response
// assertEquals(fileSize1, node.get("uploadedSize").getValueAsText());
response = getResponse(RequestType.POST, "upload/" + batchId + "/1");
assertEquals(201, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("1", node.get("fileIdx").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
// TODO NXP-18247: Put actual uploaded size in response
// assertEquals(fileSize2, node.get("uploadedSize").getValueAsText());
// Create a doc which references the uploaded blobs using the Document path endpoint
String json = "{";
json += "\"entity-type\":\"document\" ,";
json += "\"name\":\"testBatchUploadDoc\" ,";
json += "\"type\":\"MultiBlobDoc\" ,";
json += "\"properties\" : {";
json += "\"mb:blobs\" : [ ";
json += "{ \"content\" : { \"upload-batch\": \"" + batchId + "\", \"upload-fileId\": \"0\" } },";
json += "{ \"content\" : { \"upload-batch\": \"" + batchId + "\", \"upload-fileId\": \"1\" } }";
json += "]}}";
if (noDrop) {
headers = new HashMap<>();
headers.put("X-Batch-No-Drop", "true");
response = getResponse(RequestType.POST, "path/", json, headers);
} else {
response = getResponse(RequestType.POST, "path/", json);
}
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
DocumentModel doc = session.getDocument(new PathRef("/testBatchUploadDoc"));
Blob blob = (Blob) doc.getPropertyValue("mb:blobs/0/content");
assertNotNull(blob);
assertEquals("Fichier accentué 1.txt", blob.getFilename());
assertEquals("text/plain", blob.getMimeType());
assertEquals(data1, blob.getString());
blob = (Blob) doc.getPropertyValue("mb:blobs/1/content");
assertNotNull(blob);
assertEquals("Fichier accentué 2.txt", blob.getFilename());
assertEquals("application/octet-stream", blob.getMimeType());
assertEquals(data2, blob.getString());
if (noDrop) {
assertBatchExists(batchId);
}
}
/**
* Tests the use of /upload + /upload/{batchId}/{fileIdx}/execute.
*
* @since 7.4
*/
@Test
public void testBatchExecute() throws IOException {
testBatchExecute(false);
}
/**
* Tests the use of /upload + /upload/{batchId}/{fileIdx}/execute with the "X-Batch-No-Drop" header set to true.
*
* @since 8.4
*/
@Test
public void testBatchExecuteNoDrop() throws IOException {
testBatchExecute(true);
}
private void testBatchExecute(boolean noDrop) throws IOException {
// Get batch id, used as a session id
ClientResponse response = getResponse(RequestType.POST, "upload");
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
// Upload file
String fileName = URLEncoder.encode("Fichier accentué.txt", "UTF-8");
String mimeType = "text/plain";
String data = "Contenu accentué";
String fileSize = String.valueOf(data.getBytes().length);
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/octet-stream");
headers.put("X-Upload-Type", "normal");
headers.put("X-File-Name", fileName);
headers.put("X-File-Size", fileSize);
headers.put("X-File-Type", mimeType);
getResponse(RequestType.POST, "upload/" + batchId + "/0", data, headers);
// Create a doc and attach the uploaded blob to it using the /upload/{batchId}/{fileIdx}/execute endpoint
DocumentModel file = session.createDocumentModel("/", "testBatchExecuteDoc", "File");
file = session.createDocument(file);
TransactionHelper.commitOrRollbackTransaction();
TransactionHelper.startTransaction();
String json = "{\"params\":{";
json += "\"document\":\"" + file.getPathAsString() + "\"";
json += "}}";
if (noDrop) {
headers = new HashMap<>();
headers.put("X-Batch-No-Drop", "true");
response = getResponse(RequestType.POSTREQUEST, "upload/" + batchId + "/0/execute/Blob.Attach", json,
headers);
} else {
response = getResponse(RequestType.POSTREQUEST, "upload/" + batchId + "/0/execute/Blob.Attach", json);
}
assertEquals(Status.OK.getStatusCode(), response.getStatus());
DocumentModel doc = session.getDocument(new PathRef("/testBatchExecuteDoc"));
Blob blob = (Blob) doc.getPropertyValue("file:content");
assertNotNull(blob);
assertEquals("Fichier accentué.txt", blob.getFilename());
assertEquals("text/plain", blob.getMimeType());
assertEquals(data, blob.getString());
if (noDrop) {
assertBatchExists(batchId);
}
}
/**
* @since 8.1O
*/
@Test
public void testBatchExecuteAutomationServerBindings() throws IOException {
ClientResponse response = getResponse(RequestType.POST, "upload");
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
File file = Framework.createTempFile("nx-test-blob-", ".tmp");
try {
service = getServiceFor("user1", "user1");
CreateBlob.skipProtocolCheck = true;
String json = "{\"params\":{";
json += "\"file\":\"" + file.toURI().toURL() + "\"";
json += "}}";
response = getResponse(RequestType.POSTREQUEST, "upload/" + batchId + "/execute/Blob.CreateFromURL", json);
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
service = getServiceFor("Administrator", "Administrator");
response = getResponse(RequestType.POSTREQUEST, "upload/" + batchId + "/execute/Blob.CreateFromURL", json);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
} finally {
CreateBlob.skipProtocolCheck = false;
file.delete();
}
}
/**
* Tests upload using file chunks.
*
* @since 7.4
*/
@Test
public void testChunkedUpload() throws IOException {
// Get batch id, used as a session id
ClientResponse response = getResponse(RequestType.POST, "upload");
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
assertNotNull(batchId);
// Upload chunks in desorder
String fileName = URLEncoder.encode("Fichier accentué.txt", "UTF-8");
String mimeType = "text/plain";
String fileContent = "Contenu accentué composé de 3 chunks";
String fileSize = String.valueOf(fileContent.getBytes().length);
String chunk1 = "Contenu accentu";
String chunkLength1 = String.valueOf(chunk1.getBytes().length);
String chunk2 = "é composé de ";
String chunkLength2 = String.valueOf(chunk2.getBytes().length);
String chunk3 = "3 chunks";
String chunkLength3 = String.valueOf(chunk3.getBytes().length);
// Chunk 1
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/octet-stream");
headers.put("X-Upload-Type", "chunked");
headers.put("X-Upload-Chunk-Index", "0");
headers.put("X-Upload-Chunk-Count", "3");
headers.put("X-File-Name", fileName);
headers.put("X-File-Size", fileSize);
headers.put("X-File-Type", mimeType);
response = getResponse(RequestType.POST, "upload/" + batchId + "/0", chunk1, headers);
assertEquals(308, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
assertEquals(chunkLength1, node.get("uploadedSize").getValueAsText());
ArrayNode chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(1, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Get file upload status by doing an empty POST
response = getResponse(RequestType.POST, "upload/" + batchId + "/0");
assertEquals(308, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
// TODO NXP-18247: Put actual uploaded size in response
// assertEquals(chunkLength1, node.get("uploadedSize").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(1, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Get file info, here just to test the GET method
response = getResponse(RequestType.GET, "upload/" + batchId + "/0");
assertEquals(308, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("Fichier accentué.txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(1, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Chunk 3
headers.put("X-Upload-Chunk-Index", "2");
response = getResponse(RequestType.POST, "upload/" + batchId + "/0", chunk3, headers);
assertEquals(308, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
assertEquals(chunkLength3, node.get("uploadedSize").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(2, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("2", chunkIds.get(1).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Get file upload status by doing an empty POST
response = getResponse(RequestType.POST, "upload/" + batchId + "/0");
assertEquals(308, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
// TODO NXP-18247: Put actual uploaded size in response
// assertEquals(chunkLength3, node.get("uploadedSize").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(2, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("2", chunkIds.get(1).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Get file info, here just to test the GET method
response = getResponse(RequestType.GET, "upload/" + batchId + "/0");
assertEquals(308, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("Fichier accentué.txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(2, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("2", chunkIds.get(1).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Chunk 2
headers.put("X-Upload-Chunk-Index", "1");
response = getResponse(RequestType.POST, "upload/" + batchId + "/0", chunk2, headers);
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
assertEquals(chunkLength2, node.get("uploadedSize").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(3, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("1", chunkIds.get(1).getValueAsText());
assertEquals("2", chunkIds.get(2).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Get file upload status by doing an empty POST
response = getResponse(RequestType.POST, "upload/" + batchId + "/0");
assertEquals(201, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("true", node.get("uploaded").getValueAsText());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
// TODO NXP-18247: Put actual uploaded size in response
// assertEquals(chunkLength2, node.get("uploadedSize").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(3, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("1", chunkIds.get(1).getValueAsText());
assertEquals("2", chunkIds.get(2).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Get file info, here just to test the GET method
response = getResponse(RequestType.GET, "upload/" + batchId + "/0");
assertEquals(Status.OK.getStatusCode(), response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals("Fichier accentué.txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(3, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("1", chunkIds.get(1).getValueAsText());
assertEquals("2", chunkIds.get(2).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
// Get batch info
response = getResponse(RequestType.GET, "upload/" + batchId);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
ArrayNode nodes = (ArrayNode) mapper.readTree(response.getEntityInputStream());
assertEquals(1, nodes.size());
node = nodes.get(0);
assertEquals("Fichier accentué.txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(3, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("1", chunkIds.get(1).getValueAsText());
assertEquals("2", chunkIds.get(2).getValueAsText());
assertEquals("3", node.get("chunkCount").getValueAsText());
BatchManager bm = Framework.getService(BatchManager.class);
Blob blob = bm.getBlob(batchId, "0");
assertNotNull(blob);
assertEquals("Fichier accentué.txt", blob.getFilename());
assertEquals("text/plain", blob.getMimeType());
assertEquals(Long.parseLong(fileSize), blob.getLength());
assertEquals("Contenu accentué composé de 3 chunks", blob.getString());
bm.clean(batchId);
}
/**
* Tests the use of /upload using file chunks + /upload/{batchId}/{fileIdx}/execute.
*
* @since 7.4
*/
@Test
public void testBatchExecuteWithChunkedUpload() throws IOException {
testBatchExecuteWithChunkedUpload(false);
}
/**
* Tests the use of /upload using file chunks + /upload/{batchId}/{fileIdx}/execute with the "X-Batch-No-Drop"
* header set to true.
*
* @since 8.4
*/
@Test
public void testBatchExecuteWithChunkedUploadNoDrop() throws IOException {
testBatchExecuteWithChunkedUpload(true);
}
/**
* Tests the use of /upload using file chunks + /upload/{batchId}/{fileIdx}/execute.
*
* @since 7.4
*/
public void testBatchExecuteWithChunkedUpload(boolean noDrop) throws IOException {
// Get batch id, used as a session id
ClientResponse response = getResponse(RequestType.POST, "upload");
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
// Upload chunks in desorder
String fileName = URLEncoder.encode("Fichier accentué.txt", "UTF-8");
String mimeType = "text/plain";
String fileContent = "Contenu accentué composé de 2 chunks";
String fileSize = String.valueOf(fileContent.getBytes().length);
String chunk1 = "Contenu accentué compo";
String chunkLength1 = String.valueOf(chunk1.getBytes().length);
String chunk2 = "sé de 2 chunks";
String chunkLength2 = String.valueOf(chunk2.getBytes().length);
// Chunk 2
Map<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/octet-stream");
headers.put("X-Upload-Type", "chunked");
headers.put("X-Upload-Chunk-Index", "1");
headers.put("X-Upload-Chunk-Count", "2");
headers.put("X-File-Name", fileName);
headers.put("X-File-Size", fileSize);
headers.put("X-File-Type", mimeType);
response = getResponse(RequestType.POST, "upload/" + batchId + "/0", chunk2, headers);
assertEquals(308, response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
assertEquals(chunkLength2, node.get("uploadedSize").getValueAsText());
ArrayNode chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(1, chunkIds.size());
assertEquals("1", chunkIds.get(0).getValueAsText());
assertEquals("2", node.get("chunkCount").getValueAsText());
// Chunk 1
headers.put("X-Upload-Chunk-Index", "0");
response = getResponse(RequestType.POST, "upload/" + batchId + "/0", chunk1, headers);
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
node = mapper.readTree(response.getEntityInputStream());
assertEquals(batchId, node.get("batchId").getValueAsText());
assertEquals("0", node.get("fileIdx").getValueAsText());
assertEquals("chunked", node.get("uploadType").getValueAsText());
assertEquals(chunkLength1, node.get("uploadedSize").getValueAsText());
chunkIds = (ArrayNode) node.get("uploadedChunkIds");
assertEquals(2, chunkIds.size());
assertEquals("0", chunkIds.get(0).getValueAsText());
assertEquals("1", chunkIds.get(1).getValueAsText());
assertEquals("2", node.get("chunkCount").getValueAsText());
// Create a doc and attach the uploaded blob to it using the /batch/{batchId}/{fileIdx}/execute endpoint
DocumentModel file = session.createDocumentModel("/", "testBatchExecuteDoc", "File");
file = session.createDocument(file);
TransactionHelper.commitOrRollbackTransaction();
TransactionHelper.startTransaction();
String json = "{\"params\":{";
json += "\"document\":\"" + file.getPathAsString() + "\"";
json += "}}";
if (noDrop) {
headers = new HashMap<>();
headers.put("X-Batch-No-Drop", "true");
response = getResponse(RequestType.POSTREQUEST, "upload/" + batchId + "/0/execute/Blob.Attach", json,
headers);
} else {
response = getResponse(RequestType.POSTREQUEST, "upload/" + batchId + "/0/execute/Blob.Attach", json);
}
assertEquals(Status.OK.getStatusCode(), response.getStatus());
DocumentModel doc = session.getDocument(new PathRef("/testBatchExecuteDoc"));
Blob blob = (Blob) doc.getPropertyValue("file:content");
assertNotNull(blob);
assertEquals("Fichier accentué.txt", blob.getFilename());
assertEquals("text/plain", blob.getMimeType());
assertEquals("Contenu accentué composé de 2 chunks", blob.getString());
if (noDrop) {
assertBatchExists(batchId);
}
}
/**
* @since 7.4
*/
@Test
public void testCancelBatch() throws IOException {
// Init batch
ClientResponse response = getResponse(RequestType.POST, "upload");
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
// Cancel batch
response = getResponse(RequestType.DELETE, "upload/" + batchId);
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
/**
* @since 7.4
*/
@Test
public void testEmptyResponseCases() throws IOException {
// Upload
assertEquals(Status.NOT_FOUND.getStatusCode(),
getResponse(RequestType.POST, "upload/fakeBatchId/0").getStatus());
// Get batch info
assertEquals(Status.NOT_FOUND.getStatusCode(), getResponse(RequestType.GET, "upload/fakeBatchId").getStatus());
ClientResponse response = getResponse(RequestType.POST, "upload");
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
assertEquals(Status.NO_CONTENT.getStatusCode(), getResponse(RequestType.GET, "upload/" + batchId).getStatus());
// Get file info
assertEquals(Status.NOT_FOUND.getStatusCode(),
getResponse(RequestType.GET, "upload/fakeBatchId/0").getStatus());
assertEquals(Status.NOT_FOUND.getStatusCode(),
getResponse(RequestType.GET, "upload/" + batchId + "/0").getStatus());
// Cancel batch
assertEquals(Status.NOT_FOUND.getStatusCode(),
getResponse(RequestType.DELETE, "upload/fakeBatchId").getStatus());
}
/**
* @since 7.10
*/
@Test
public void testBadRequests() throws IOException {
ClientResponse response = getResponse(RequestType.POST, "upload");
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
// Bad file index
assertEquals(Status.BAD_REQUEST.getStatusCode(),
getResponse(RequestType.POST, "upload/" + batchId + "/a").getStatus());
// Bad chunk index
Map<String, String> headers = new HashMap<String, String>();
headers.put("X-Upload-Type", "chunked");
headers.put("X-Upload-Chunk-Count", "2");
headers.put("X-Upload-Chunk-Index", "a");
headers.put("X-File-Size", "100");
assertEquals(Status.BAD_REQUEST.getStatusCode(),
getResponse(RequestType.POST, "upload/" + batchId + "/0", "chunkContent", headers).getStatus());
// Bad chunk count
headers = new HashMap<String, String>();
headers.put("X-Upload-Type", "chunked");
headers.put("X-Upload-Chunk-Count", "a");
headers.put("X-Upload-Chunk-Index", "0");
headers.put("X-File-Size", "100");
assertEquals(Status.BAD_REQUEST.getStatusCode(),
getResponse(RequestType.POST, "upload/" + batchId + "/0", "chunkContent", headers).getStatus());
// Bad file size
headers = new HashMap<String, String>();
headers.put("X-Upload-Type", "chunked");
headers.put("X-Upload-Chunk-Count", "2");
headers.put("X-Upload-Chunk-Index", "0");
headers.put("X-File-Size", "a");
assertEquals(Status.BAD_REQUEST.getStatusCode(),
getResponse(RequestType.POST, "upload/" + batchId + "/0", "chunkContent", headers).getStatus());
}
/**
* @since 8.4
*/
@Test
public void testRemoveFile() throws IOException {
// Get batch id, used as a session id
ClientResponse response = getResponse(RequestType.POST, "upload");
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
JsonNode node = mapper.readTree(response.getEntityInputStream());
String batchId = node.get("batchId").getValueAsText();
assertNotNull(batchId);
int numfiles = 5;
// Upload test files
String fileName, data, fileSize = null, mimeType = "text/plain";
Map<String, String> headers = new HashMap<String, String>();
for (int i = 0; i < numfiles; i++) {
fileName = URLEncoder.encode("Test File " + Integer.toString(i + 1) + ".txt", "UTF-8");
data = "Test Content " + Integer.toString(i + 1);
if (fileSize == null) {
fileSize = String.valueOf(data.getBytes().length);
headers.put("Content-Type", "application/octet-stream");
headers.put("X-Upload-Type", "normal");
headers.put("X-File-Size", fileSize);
headers.put("X-File-Type", mimeType);
}
headers.put("X-File-Name", fileName);
response = getResponse(RequestType.POST, "upload/" + batchId + "/" + Integer.toString(i), data, headers);
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
}
// Get batch info
response = getResponse(RequestType.GET, "upload/" + batchId);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
ArrayNode nodes = (ArrayNode) mapper.readTree(response.getEntityInputStream());
assertEquals(numfiles, nodes.size());
for (int i = 0; i < numfiles; i++) {
node = nodes.get(i);
assertEquals("Test File " + Integer.toString(i + 1) + ".txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
}
// remove files #2 and #4
response = getResponse(RequestType.DELETE, "upload/" + batchId + "/1");
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
response = getResponse(RequestType.DELETE, "upload/" + batchId + "/3");
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
// check if the remaining files are the correct ones
response = getResponse(RequestType.GET, "upload/" + batchId);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
nodes = (ArrayNode) mapper.readTree(response.getEntityInputStream());
assertEquals(numfiles - 2, nodes.size());
node = nodes.get(0);
assertEquals("Test File 1.txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
node = nodes.get(1);
assertEquals("Test File 3.txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
node = nodes.get(2);
assertEquals("Test File 5.txt", node.get("name").getValueAsText());
assertEquals(fileSize, node.get("size").getValueAsText());
assertEquals("normal", node.get("uploadType").getValueAsText());
// test removal of invalid file index
response = getResponse(RequestType.DELETE, "upload/" + batchId + "/3");
assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
}
private void assertBatchExists(String batchId) {
ClientResponse response = getResponse(RequestType.GET, "upload/" + batchId);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
}
}