ACE-4469: Merged BRANCHES/DEV/HEAD-SFS (cherry picked) to HEAD

113520: SFS-179: Added multipart upload support into Public API framework.
   114561: SFS-179: Added tests for upload API, as well as minor fixes.
   114732: SFS-179: Changed the assert import from 3.X to 4.X.
   114734: SFS-179: Added unit tests for Public API framework multiPart support.
   114735: SFS-179: Fixed unit test failure.
- Also removed mergeinfo added in r112639

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@114736 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jamal Kaabi-Mofrad
2015-10-19 23:25:59 +00:00
parent a4c853d923
commit 8855ef66f8
19 changed files with 760 additions and 92 deletions

View File

@@ -61,6 +61,19 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi
return response;
}
protected HttpResponse post(String url, String runAsUser, String body, String queryString, String contentType, int expectedStatus) throws Exception
{
publicApiClient.setRequestContext(new RequestContext(runAsUser));
if (queryString != null)
{
url += queryString;
}
HttpResponse response = publicApiClient.post(getScope(), url, null, null, null, body, contentType);
checkStatus(expectedStatus, response.getStatusCode());
return response;
}
protected HttpResponse getAll(String url, String runAsUser, PublicApiClient.Paging paging, int expectedStatus) throws Exception
{
publicApiClient.setRequestContext(new RequestContext(runAsUser));

View File

@@ -440,6 +440,17 @@ public class PublicApiClient
return response;
}
public HttpResponse post(String scope, String entityCollectionName, Object entityId, String relationCollectionName, Object relationshipEntityId,
String body, String contentType) throws IOException
{
HttpResponse response = client.post(getRequestContext(), scope, entityCollectionName, entityId, relationCollectionName,
relationshipEntityId != null ? relationshipEntityId.toString() : null, body, contentType);
logger.debug(response.toString());
return response;
}
public HttpResponse post(String urlSuffix, String body) throws IOException
{
HttpResponse response = client.post(getRequestContext(), urlSuffix, body);

View File

@@ -450,20 +450,33 @@ public class PublicApiHttpClient
PatchMethod req = new PatchMethod(url.toString());
return submitRequest(req, rq);
}
public HttpResponse post(final RequestContext rq, final String scope, final String entityCollectionName, final Object entityId, final String relationCollectionName, final Object relationshipEntityId, final String body) throws IOException
{
RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, null);
String url = endpoint.getUrl();
PostMethod req = new PostMethod(url.toString());
if(body != null)
{
StringRequestEntity requestEntity = new StringRequestEntity(body, "application/json", "UTF-8");
req.setRequestEntity(requestEntity);
}
return submitRequest(req, rq);
}
public HttpResponse post(final RequestContext rq, final String scope, final String entityCollectionName, final Object entityId,
final String relationCollectionName, final Object relationshipEntityId, final String body) throws IOException
{
return post(rq, scope, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, body, "application/json");
}
public HttpResponse post(final RequestContext rq, final String scope, final String entityCollectionName, final Object entityId,
final String relationCollectionName, final Object relationshipEntityId, final String body, String contentType) throws IOException
{
RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, entityCollectionName, entityId, relationCollectionName,
relationshipEntityId, null);
String url = endpoint.getUrl();
PostMethod req = new PostMethod(url.toString());
if (body != null)
{
if (contentType == null || contentType.isEmpty())
{
contentType = "application/json";
}
StringRequestEntity requestEntity = new StringRequestEntity(body, contentType, "UTF-8");
req.setRequestEntity(requestEntity);
}
return submitRequest(req, rq);
}
public HttpResponse delete(final Class<?> c, final RequestContext rq, final Object entityId, final Object relationshipEntityId) throws IOException
{

View File

@@ -0,0 +1,259 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.rest.api.tests.util;
import static org.junit.Assert.assertNotNull;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.params.HttpMethodParams;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* <i><b>multipart/form-data</b></i> builder.
*
* @author Jamal Kaabi-Mofrad
*/
public class MultiPartBuilder
{
private FileData fileData;
private String siteId;
private String containerId;
private String destination;
private String uploadDirectory;
private String updateNodeRef;
private String description;
private String contentTypeQNameStr;
private List<String> aspects;
private boolean majorVersion;
private boolean overwrite = true; // If a fileName clashes for a versionable file
private MultiPartBuilder()
{
}
private MultiPartBuilder(MultiPartBuilder that)
{
this.fileData = that.fileData;
this.siteId = that.siteId;
this.containerId = that.containerId;
this.destination = that.destination;
this.uploadDirectory = that.uploadDirectory;
this.updateNodeRef = that.updateNodeRef;
this.description = that.description;
this.contentTypeQNameStr = that.contentTypeQNameStr;
this.aspects = that.aspects;
this.majorVersion = that.majorVersion;
this.overwrite = that.overwrite;
}
public static MultiPartBuilder create()
{
return new MultiPartBuilder();
}
public static MultiPartBuilder copy(MultiPartBuilder copy)
{
return new MultiPartBuilder(copy);
}
public MultiPartBuilder setFileData(FileData fileData)
{
this.fileData = fileData;
return this;
}
public MultiPartBuilder setSiteId(String siteId)
{
this.siteId = siteId;
return this;
}
public MultiPartBuilder setContainerId(String containerId)
{
this.containerId = containerId;
return this;
}
public MultiPartBuilder setDestination(String destination)
{
this.destination = destination;
return this;
}
public MultiPartBuilder setUploadDirectory(String uploadDirectory)
{
this.uploadDirectory = uploadDirectory;
return this;
}
public MultiPartBuilder setUpdateNoderef(String updateNodeRef)
{
this.updateNodeRef = updateNodeRef;
return this;
}
public MultiPartBuilder setDescription(String description)
{
this.description = description;
return this;
}
public MultiPartBuilder setContentTypeQNameStr(String contentTypeQNameStr)
{
this.contentTypeQNameStr = contentTypeQNameStr;
return this;
}
public MultiPartBuilder setAspects(List<String> aspects)
{
this.aspects = aspects;
return this;
}
public MultiPartBuilder setMajorVersion(boolean majorVersion)
{
this.majorVersion = majorVersion;
return this;
}
public MultiPartBuilder setOverwrite(boolean overwrite)
{
this.overwrite = overwrite;
return this;
}
private String getAspects(List<String> aspects)
{
if (aspects != null)
{
StringBuilder sb = new StringBuilder(aspects.size() * 2);
for (String str : aspects)
{
sb.append(str).append(',');
}
sb.deleteCharAt(sb.length() - 1); // remove leading separator
return sb.toString();
}
return null;
}
public static class FileData
{
private final String fileName;
private final File file;
private final String mimetype;
public FileData(String fileName, File file, String mimetype)
{
this.fileName = fileName;
this.file = file;
this.mimetype = mimetype;
}
public String getFileName()
{
return fileName;
}
public File getFile()
{
return file;
}
public String getMimetype()
{
return mimetype;
}
}
public static class MultiPartRequest
{
private final byte[] body;
private final String contentType;
private final long contentLength;
public MultiPartRequest(byte[] body, String contentType, long contentLength)
{
this.body = body;
this.contentType = contentType;
this.contentLength = contentLength;
}
public byte[] getBody()
{
return body;
}
public String getContentType()
{
return contentType;
}
public long getContentLength()
{
return contentLength;
}
}
public MultiPartRequest build() throws IOException
{
assertNotNull(fileData);
List<Part> parts = new ArrayList<>();
parts.add(new FilePart("filedata", fileData.getFileName(), fileData.getFile(), fileData.getMimetype(), null));
addPartIfNotNull(parts, "filename", fileData.getFileName());
addPartIfNotNull(parts, "siteid", siteId);
addPartIfNotNull(parts, "containerid", containerId);
addPartIfNotNull(parts, "destination", destination);
addPartIfNotNull(parts, "uploaddirectory", uploadDirectory);
addPartIfNotNull(parts, "updatenoderef", updateNodeRef);
addPartIfNotNull(parts, "description", description);
addPartIfNotNull(parts, "contenttype", contentTypeQNameStr);
addPartIfNotNull(parts, "aspects", getAspects(aspects));
addPartIfNotNull(parts, "majorversion", Boolean.toString(majorVersion));
addPartIfNotNull(parts, "overwrite", Boolean.toString(overwrite));
MultipartRequestEntity req = new MultipartRequestEntity(parts.toArray(new Part[parts.size()]), new HttpMethodParams());
ByteArrayOutputStream os = new ByteArrayOutputStream();
req.writeRequest(os);
return new MultiPartRequest(os.toByteArray(), req.getContentType(), req.getContentLength());
}
private void addPartIfNotNull(List<Part> list, String partName, String partValue)
{
if (partValue != null)
{
list.add(new StringPart(partName, partValue));
}
}
}

View File

@@ -0,0 +1,22 @@
package org.alfresco.rest.framework.tests.api.mocks;
import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.springframework.extensions.webscripts.servlet.FormData;
/**
* @author Jamal Kaabi-Mofrad
*/
@EntityResource(name = "multiparttest", title = "multi-part upload test")
public class MultiPartTestEntityResource
implements MultiPartResourceAction.Create<MultiPartTestResponse>
{
@Override
public MultiPartTestResponse create(FormData formData, Parameters parameters)
{
return new MultiPartTestResponse(formData.getParameters().get("filename")[0]);
}
}

View File

@@ -0,0 +1,24 @@
package org.alfresco.rest.framework.tests.api.mocks;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartRelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.springframework.extensions.webscripts.servlet.FormData;
/**
* @author Jamal Kaabi-Mofrad
*/
@RelationshipResource(name = "sheepUpload", entityResource = SheepEntityResource.class, title = "Sheep mulitpart upload")
public class MultiPartTestRelationshipResource
implements MultiPartRelationshipResourceAction.Create<MultiPartTestResponse>
{
@Override
public MultiPartTestResponse create(String entityResourceId, FormData formData,
Parameters parameters)
{
return new MultiPartTestResponse(formData.getParameters().get("filename")[0]);
}
}

View File

@@ -0,0 +1,23 @@
package org.alfresco.rest.framework.tests.api.mocks;
/**
* Simple mock pojo for MultiPart response.
*
* @author Jamal Kaabi-Mofrad
*/
public class MultiPartTestResponse
{
private String fileName;
public MultiPartTestResponse(String fileName)
{
this.fileName = fileName;
}
public String getFileName()
{
return this.fileName;
}
}

View File

@@ -26,6 +26,9 @@ import org.alfresco.rest.framework.tests.api.mocks.Farmer;
import org.alfresco.rest.framework.tests.api.mocks.GoatEntityResource;
import org.alfresco.rest.framework.tests.api.mocks.Grass;
import org.alfresco.rest.framework.tests.api.mocks.GrassEntityResource;
import org.alfresco.rest.framework.tests.api.mocks.MultiPartTestEntityResource;
import org.alfresco.rest.framework.tests.api.mocks.MultiPartTestRelationshipResource;
import org.alfresco.rest.framework.tests.api.mocks.MultiPartTestResponse;
import org.alfresco.rest.framework.tests.api.mocks.Sheep;
import org.alfresco.rest.framework.tests.api.mocks.SheepBlackSheepResource;
import org.alfresco.rest.framework.tests.api.mocks.SheepEntityResource;
@@ -83,7 +86,17 @@ public class InspectorTests
assertTrue("FlockEntityResource supports PUT", metaData.supports(HttpMethod.PUT));
assertTrue("FlockEntityResource supports DELETE", metaData.supports(HttpMethod.DELETE));
assertTrue("FlockEntityResource does not support POST", !metaData.supports(HttpMethod.POST));
metainfo = ResourceInspector.inspect(MultiPartTestEntityResource.class);
assertTrue("Must be one ResourceMetadata",metainfo.size()==1);
metaData = metainfo.get(0);
assertNotNull(metaData);
assertTrue("MultiPartTestEntityResource support POST", metaData.supports(HttpMethod.POST));
assertFalse("MultiPartTestEntityResource does not supports GET", metaData.supports(HttpMethod.GET));
assertFalse("MultiPartTestEntityResource does not supports PUT", metaData.supports(HttpMethod.PUT));
assertFalse("MultiPartTestEntityResource does not supports DELETE", metaData.supports(HttpMethod.DELETE));
assertTrue("MultiPartTestEntityResource must support MultiPartTestResponse", MultiPartTestResponse.class.equals(metaData.getObjectType(HttpMethod.POST)));
}
@Test
@@ -114,9 +127,18 @@ public class InspectorTests
assertTrue("SheepBlackSheepResource supports DELETE", metaData.supports(HttpMethod.DELETE));
params = metaData.getParameters(HttpMethod.DELETE);
assertTrue("DELETE method on a relations should have 2 url params.", params.size() == 2);
metainfo = ResourceInspector.inspect(MultiPartTestRelationshipResource.class);
assertTrue("Must be one ResourceMetadata",metainfo.size()==1);
metaData = metainfo.get(0);
assertNotNull(metaData);
assertTrue("MultiPartTestRelationshipResource support POST", metaData.supports(HttpMethod.POST));
assertFalse("MultiPartTestRelationshipResource does not supports GET", metaData.supports(HttpMethod.GET));
assertFalse("MultiPartTestRelationshipResource does not supports PUT", metaData.supports(HttpMethod.PUT));
assertFalse("MultiPartTestRelationshipResource does not supports DELETE", metaData.supports(HttpMethod.DELETE));
assertTrue("MultiPartTestRelationshipResource must support MultiPartTestResponse", MultiPartTestResponse.class.equals(metaData.getObjectType(HttpMethod.POST)));
}
@Test
public void testInspectApi()
{

View File

@@ -9,13 +9,18 @@ import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.rest.api.tests.util.MultiPartBuilder;
import org.alfresco.rest.api.tests.util.MultiPartBuilder.FileData;
import org.alfresco.rest.api.tests.util.MultiPartBuilder.MultiPartRequest;
import org.alfresco.rest.framework.core.ResourceLocator;
import org.alfresco.rest.framework.core.ResourceMetadata;
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
@@ -30,12 +35,16 @@ import org.alfresco.rest.framework.webscripts.ResourceWebScriptDelete;
import org.alfresco.rest.framework.webscripts.ResourceWebScriptGet;
import org.alfresco.rest.framework.webscripts.ResourceWebScriptPost;
import org.alfresco.rest.framework.webscripts.ResourceWebScriptPut;
import org.alfresco.util.TempFileProvider;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.webscripts.Match;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.servlet.FormData;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
/**
* Tests extracting of params from req
@@ -119,7 +128,7 @@ public class ParamsExtractorTests
assertNotNull(params.getFilter());
assertTrue("Default filter is BeanPropertiesFilter.AllProperties", BeanPropertiesFilter.AllProperties.class.equals(params.getFilter().getClass()));
Object passed = params.getPassedIn();
assertNotNull(passed);
@@ -178,6 +187,65 @@ public class ParamsExtractorTests
}
}
@Test
public void testMultiPartPostExtractor() throws Exception
{
ResourceWebScriptPost extractor = new ResourceWebScriptPost();
extractor.setJsonHelper(jsonHelper);
Map<String, String> templateVars = new HashMap<String, String>();
WebScriptRequest request = mock(WebScriptRequest.class);
when(request.getServiceMatch()).thenReturn(new Match(null, templateVars, null));
File file = TempFileProvider.createTempFile("ParamsExtractorTests-", ".txt");
PrintWriter writer = new PrintWriter(file);
writer.println("Multipart Mock test.");
writer.close();
MultiPartRequest reqBody = MultiPartBuilder.create()
.setFileData(new FileData(file.getName(), file, MimetypeMap.MIMETYPE_TEXT_PLAIN))
.build();
MockHttpServletRequest mockRequest = new MockHttpServletRequest("POST", "");
mockRequest.setContent(reqBody.getBody());
mockRequest.setContentType(reqBody.getContentType());
when(request.getContentType()).thenReturn("multipart/form-data");
when(request.parseContent()).thenReturn(new FormData(mockRequest));
Params params = extractor.extractParams(mockEntity(), request);
assertNotNull(params);
Object passed = params.getPassedIn();
assertNotNull(passed);
assertTrue(FormData.class.isAssignableFrom(passed.getClass()));
FormData formData = (FormData) passed;
assertTrue(formData.getIsMultiPart());
assertNotNull(params.getStatus());
assertFalse(params.getStatus().getRedirect());
// No entity id for POST
templateVars.put(ResourceLocator.ENTITY_ID, "1234");
try
{
params = extractor.extractParams(mockEntity(), request);
fail("Should not get here. No entity id for POST");
}
catch (UnsupportedResourceOperationException uoe)
{
assertNotNull(uoe);
}
params = extractor.extractParams(mockRelationship(), request);
assertNotNull(params);
assertEquals("1234", params.getEntityId());
passed = params.getPassedIn();
assertNotNull(passed);
assertTrue(FormData.class.isAssignableFrom(passed.getClass()));
formData = (FormData) passed;
assertTrue(formData.getIsMultiPart());
}
@Test
public void testPutExtractor() throws IOException
{

View File

@@ -11,7 +11,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -19,8 +21,12 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.rest.api.tests.util.MultiPartBuilder;
import org.alfresco.rest.api.tests.util.MultiPartBuilder.FileData;
import org.alfresco.rest.api.tests.util.MultiPartBuilder.MultiPartRequest;
import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.core.ResourceDictionaryBuilder;
import org.alfresco.rest.framework.core.ResourceLookupDictionary;
@@ -34,6 +40,7 @@ import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.ActionExecutor.ExecutionCallback;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction.Read;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction.ReadById;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
@@ -52,6 +59,7 @@ import org.alfresco.rest.framework.tests.api.mocks3.SlimGoat;
import org.alfresco.rest.framework.webscripts.AbstractResourceWebScript;
import org.alfresco.rest.framework.webscripts.ResourceWebScriptHelper;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
@@ -69,7 +77,9 @@ import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.extensions.webscripts.Format;
import org.springframework.extensions.webscripts.servlet.FormData;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -133,6 +143,31 @@ public class SerializeTests
out = writeResponse(helper.postProcessResponse(api,null, Params.valueOf("notUsed", null), resources));
assertTrue("There must be json output as List", StringUtils.startsWith(out, "{\"list\":"));
}
@Test
public void testInvokeMultiPartEntity() throws IOException
{
ResourceWithMetadata entityResource = locator.locateEntityResource(api,"multiparttest", HttpMethod.POST);
assertNotNull(entityResource);
MultiPartResourceAction.Create<?> resource = (MultiPartResourceAction.Create<?>) entityResource.getResource();
File file = TempFileProvider.createTempFile("ParamsExtractorTests-", ".txt");
PrintWriter writer = new PrintWriter(file);
writer.println("Multipart Mock test2.");
writer.close();
MultiPartRequest reqBody = MultiPartBuilder.create()
.setFileData(new FileData(file.getName(), file, MimetypeMap.MIMETYPE_TEXT_PLAIN))
.build();
MockHttpServletRequest mockRequest = new MockHttpServletRequest("POST", "");
mockRequest.setContent(reqBody.getBody());
mockRequest.setContentType(reqBody.getContentType());
String out = writeResponse(helper.postProcessResponse(api,null, NOT_USED, resource.create(new FormData(mockRequest), NOT_USED)));
assertTrue("There must be json output", StringUtils.startsWith(out, "{\"entry\":"));
}
@Test
public void testSerializeResponse() throws IOException
{

View File

@@ -59,7 +59,7 @@ public class WriterTests
ResourceDictionary resourceDic = locator.getDictionary();
Map<String, ResourceWithMetadata> apiResources = resourceDic.getAllResources().get(api);
String writtenOut = testWriter(defaultMetaWriter, apiResources.get("/sheep"), apiResources);
assertTrue(writtenOut.startsWith("{\"list\":{\"pagination\":{\"count\":4"));
assertTrue(writtenOut.startsWith("{\"list\":{\"pagination\":{\"count\":5"));
// ResourceMetaDataWriter wadlWriter = new WebScriptOptionsMetaData();
// writtenOut = testWriter(wadlWriter, apiResources.get("/sheep"), apiResources);