Merged HEAD (5.2) to 5.2.N (5.2.1)

126380 jkaabimofrad: Merged FILE-FOLDER-API (5.2.0) to HEAD (5.2)
      120084 jkaabimofrad: RA-640, RA-681: made "update node content" API to return the default JSON representation of the file node. Also added a test.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@126726 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Ancuta Morarasu
2016-05-11 10:53:26 +00:00
parent 79848e45da
commit 5fbede5332
12 changed files with 306 additions and 79 deletions

View File

@@ -125,8 +125,7 @@ public interface Nodes
// TODO update REST fwk - to optionally support "attachment" (Content-Disposition) header
BinaryResource getContent(String fileNodeId, Parameters parameters);
// TODO update REST fwk - to optionally support return of json
void updateContent(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters);
Node updateContent(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters);
/**
* Uploads file content and meta-data into the repository.

View File

@@ -35,6 +35,7 @@ import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.rest.antlr.WhereClauseParser;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.ContentInfo;
import org.alfresco.rest.api.model.Document;
import org.alfresco.rest.api.model.Folder;
import org.alfresco.rest.api.model.Node;
@@ -45,7 +46,6 @@ import org.alfresco.rest.framework.core.exceptions.ApiException;
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.core.exceptions.RequestEntityTooLargeException;
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
@@ -93,12 +93,9 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.webscripts.servlet.FormData;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1001,10 +998,20 @@ public class NodesImpl implements Nodes
}
}
if (isContent) {
if (isContent)
{
// add empty file
ContentWriter writer = sr.getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
String mimeType = sr.getMimetypeService().guessMimetype(nodeName);
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
String mimeType;
ContentInfo contentInfo = nodeInfo.getContent();
if (contentInfo != null && contentInfo.getMimeType() != null)
{
mimeType = contentInfo.getMimeType();
}
else
{
mimeType = mimetypeService.guessMimetype(nodeName);
}
writer.setMimetype(mimeType);
writer.putContent("");
}
@@ -1159,7 +1166,7 @@ public class NodesImpl implements Nodes
}
@Override
public void updateContent(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters)
public Node updateContent(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters)
{
final NodeRef nodeRef = validateNode(fileNodeId);
@@ -1168,7 +1175,7 @@ public class NodesImpl implements Nodes
throw new InvalidArgumentException("NodeId of content is expected: " + nodeRef);
}
ContentWriter writer = sr.getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
String mimeType = contentInfo.getMimeType();
if (mimeType == null)
@@ -1180,13 +1187,13 @@ public class NodesImpl implements Nodes
{
writer.setMimetype(mimeType);
}
writer.guessEncoding();
writer.putContent(stream);
// TODO - hmm - we may wish to return json info !!
return;
return getFolderOrDocumentFullInfo(nodeRef,
getParentNodeRef(nodeRef),
nodeService.getType(nodeRef),
parameters);
}
@Override
@@ -1389,9 +1396,8 @@ public class NodesImpl implements Nodes
protected void write(NodeRef nodeRef, Content content, String fileName, boolean applyMimeType, boolean guessEncoding)
{
ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true);
InputStream is;
String mimeType = content.getMimetype();
// Per RA-637 requirement the mimeType provided by the client takes precedent, however,
// Per RA-637 requirement the mimeType provided by the client takes precedence, however,
// if the mimeType is null, then it will be retrieved or guessed.
if (mimeType == null || !applyMimeType)
{
@@ -1405,28 +1411,17 @@ public class NodesImpl implements Nodes
mimeType = mimetypeService.guessMimetype(fileName);
}
}
writer.setMimetype(mimeType);
if (guessEncoding)
{
is = new BufferedInputStream(content.getInputStream());
is.mark(1024);
writer.setEncoding(guessEncoding(is, mimeType));
try
{
is.reset();
}
catch (IOException e)
{
logger.error("Failed to reset input stream", e);
}
writer.guessEncoding();
}
else
{
writer.setEncoding(content.getEncoding());
is = content.getInputStream();
}
writer.setMimetype(mimeType);
writer.putContent(is);
writer.putContent(content.getInputStream());
}
/**
@@ -1448,22 +1443,6 @@ public class NodesImpl implements Nodes
versionService.ensureVersioningEnabled(nodeRef, props);
}
/**
* Guesses the character encoding of the given inputStream.
*/
protected String guessEncoding(InputStream in, String mimeType)
{
String encoding = "UTF-8";
if (in != null)
{
Charset charset = mimetypeService.getContentCharsetFinder().getCharset(in, mimeType);
encoding = charset.name();
}
return encoding;
}
/**
* Extracts the given node metadata asynchronously.
*/

View File

@@ -52,7 +52,7 @@ import java.io.InputStream;
@EntityResource(name="nodes", title = "Nodes")
public class NodesEntityResource implements
EntityResourceAction.ReadById<Node>, EntityResourceAction.Delete, EntityResourceAction.Update<Node>,
BinaryResourceAction.Read, BinaryResourceAction.Update, InitializingBean
BinaryResourceAction.Read, BinaryResourceAction.Update<Node>, InitializingBean
{
private Nodes nodes;
@@ -93,9 +93,9 @@ public class NodesEntityResource implements
@Override
@WebApiDescription(title = "Upload content", description = "Upload content")
@BinaryProperties({"content"})
public void updateProperty(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters)
public Node updateProperty(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters)
{
nodes.updateContent(fileNodeId, contentInfo, stream, parameters);
return nodes.updateContent(fileNodeId, contentInfo, stream, parameters);
}
/**

View File

@@ -79,7 +79,7 @@ public interface BinaryResourceAction
/**
* HTTP PUT - Updates a binary resource if it exists, error if not
*/
public static interface Update extends ResourceAction
public static interface Update<E> extends ResourceAction
{
/**
@@ -90,7 +90,7 @@ public interface BinaryResourceAction
* @param contentInfo Basic information about the content stream
* @param params {@link Parameters}
*/
public void updateProperty (String entityId, BasicContentInfo contentInfo, InputStream stream, Parameters params);
public E updateProperty (String entityId, BasicContentInfo contentInfo, InputStream stream, Parameters params);
}
}

View File

@@ -31,7 +31,6 @@ import java.util.Locale;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.web.scripts.BufferedRequest;
import org.alfresco.rest.framework.core.ResourceInspector;
import org.alfresco.rest.framework.core.ResourceLocator;
import org.alfresco.rest.framework.core.ResourceMetadata;
@@ -196,10 +195,8 @@ public class ResourceWebScriptPut extends AbstractResourceWebScript implements P
{
throw new DeletedResourceException("(UPDATE) "+resource.getMetaData().getUniqueId());
}
BinaryResourceAction.Update binUpdater = (BinaryResourceAction.Update) resource.getResource();
binUpdater.updateProperty(params.getEntityId(),params.getContentInfo(), params.getStream(), params);
//Don't pass anything to the callback - its just successful
return null;
BinaryResourceAction.Update<Object> binUpdater = (BinaryResourceAction.Update<Object>) resource.getResource();
return binUpdater.updateProperty(params.getEntityId(), params.getContentInfo(), params.getStream(), params);
default:
throw new UnsupportedResourceOperationException("PUT not supported for Actions");
}

View File

@@ -35,6 +35,7 @@ import org.alfresco.rest.api.tests.RepoService.TestPerson;
import org.alfresco.rest.api.tests.RepoService.TestSite;
import org.alfresco.rest.api.tests.client.HttpResponse;
import org.alfresco.rest.api.tests.client.PublicApiClient;
import org.alfresco.rest.api.tests.client.PublicApiHttpClient.BinaryPayload;
import org.alfresco.rest.api.tests.client.RequestContext;
import org.alfresco.service.cmr.site.SiteVisibility;
@@ -147,6 +148,27 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi
return response;
}
protected HttpResponse putBinary(String url, int version, String runAsUser, BinaryPayload payload, String queryString, Map<String, String> params,
int expectedStatus) throws Exception
{
publicApiClient.setRequestContext(new RequestContext(runAsUser));
if (queryString != null)
{
url += queryString;
}
HttpResponse response = publicApiClient.putBinary(getScope(), version, url, null, null, null, payload, params);
checkStatus(expectedStatus, response.getStatusCode());
return response;
}
protected HttpResponse putBinary(String url, String runAsUser, BinaryPayload payload, String queryString, Map<String, String> params,
int expectedStatus) throws Exception
{
return putBinary(url, 1, runAsUser, payload, queryString, params, expectedStatus);
}
protected HttpResponse delete(String url, String runAsUser, String entityId, int expectedStatus) throws Exception
{
publicApiClient.setRequestContext(new RequestContext(runAsUser));

View File

@@ -34,6 +34,7 @@ import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.tests.client.PublicApiHttpClient.BinaryPayload;
import org.alfresco.rest.api.tests.client.data.ContentInfo;
import org.alfresco.rest.api.tests.client.data.Document;
import org.alfresco.rest.api.tests.client.data.Folder;
@@ -62,6 +63,7 @@ import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.util.TempFileProvider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.junit.After;
@@ -69,6 +71,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.util.ResourceUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -1153,6 +1156,96 @@ public class NodeApiTest extends AbstractBaseApiTest
put("nodes", user1, fId, toJsonAsStringNonNull(fUpdate), null, 400);
}
/**
* Tests update file content
* <p>PUT:</p>
* {@literal <host>:<port>/alfresco/api/-default-/public/alfresco/versions/1/nodes/<nodeId>/content}
*/
@Test
public void testUpdateFileWithBinaryUpload() throws Exception
{
AuthenticationUtil.setFullyAuthenticatedUser(user1);
NodeRef myFilesNodeRef = repositoryHelper.getUserHome(personService.getPerson(user1));
Folder f1 = new Folder();
f1.setName("F1");
f1.setNodeType("cm:folder");
HttpResponse response = post(getChildrenUrl(myFilesNodeRef), user1, toJsonAsStringNonNull(f1), 201);
Folder folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class);
assertEquals(f1.getName(), folderResp.getName());
final String f1_nodeId = folderResp.getId();
assertNotNull(f1_nodeId);
Document doc = new Document();
final String docName = "testdoc";
doc.setName(docName);
doc.setNodeType("cm:content");
ContentInfo contentInfo = new ContentInfo();
contentInfo.setMimeType(MimetypeMap.MIMETYPE_TEXT_PLAIN);
doc.setContent(contentInfo);
// create an empty file within F1 folder
response = post(getChildrenUrl(f1_nodeId), user1, toJsonAsStringNonNull(doc), 201);
Document docResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
assertEquals(docName, docResp.getName());
assertNotNull(docResp.getContent());
assertEquals(0, docResp.getContent().getSizeInBytes().intValue());
assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, docResp.getContent().getMimeType());
// Update content & Download URL
final String url = "nodes/" + docResp.getId() + "/content";
// Update the empty node's content
String content = "The quick brown fox jumps over the lazy dog.";
ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes());
File file = TempFileProvider.createTempFile(inputStream, getClass().getSimpleName(), ".txt");
BinaryPayload payload = new BinaryPayload(file, MimetypeMap.MIMETYPE_TEXT_PLAIN);
// Try to update a folder!
putBinary("nodes/" + f1_nodeId + "/content", user1, payload, null, null, 400);
// Try to update a non-existent file
putBinary("nodes/" + UUID.randomUUID().toString() + "/content", user1, payload, null, null, 404);
// Update the empty file
response = putBinary(url, user1, payload, null, null, 200);
docResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
assertEquals(docName, docResp.getName());
assertNull(docResp.getPath());
assertNotNull(docResp.getContent());
assertTrue(docResp.getContent().getSizeInBytes().intValue() > 0);
assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, docResp.getContent().getMimeType());
// Download the file
response = getSingle(url, user1, null, 200);
assertEquals(content, response.getResponse());
// Update the node's content again. Also, change the mimeType and make the response to return path!
file = getResourceFile("quick.pdf");
payload = new BinaryPayload(file, MimetypeMap.MIMETYPE_PDF);
response = putBinary(url + "?select=path", user1, payload, null, null, 200);
docResp = jacksonUtil.parseEntry(response.getJsonResponse(), Document.class);
assertEquals(docName, docResp.getName());
assertNotNull(docResp.getContent());
assertTrue(docResp.getContent().getSizeInBytes().intValue() > 0);
assertEquals(MimetypeMap.MIMETYPE_PDF, docResp.getContent().getMimeType());
PathInfo pathInfo = docResp.getPath();
assertNotNull(pathInfo);
assertTrue(pathInfo.getIsComplete());
List<ElementInfo> pathElements = pathInfo.getElements();
assertNotNull(pathElements);
assertTrue(pathElements.size() > 0);
// check the last element is F1
assertEquals(f1.getName(), pathElements.get(pathElements.size() - 1).getName());
// Download the file
response = getSingle(url, user1, null, 200);
assertNotNull(content, response.getResponse());
}
// TODO add test to create multiple folders & empty files (within same parent folder)
// TODO add test for file/folder links - creating, getting, listing, deleting

View File

@@ -33,6 +33,7 @@ import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
@@ -84,10 +85,15 @@ public class HttpResponse
else if (method instanceof PutMethod)
{
requestType = "PUT";
StringRequestEntity requestEntity = (StringRequestEntity)((PutMethod)method).getRequestEntity();
if(requestEntity != null)
RequestEntity requestEntity = ((PutMethod) method).getRequestEntity();
if (requestEntity instanceof StringRequestEntity)
{
requestBody = requestEntity.getContent();
StringRequestEntity stringRequestEntity = (StringRequestEntity) requestEntity;
requestBody = stringRequestEntity.getContent();
}
else if (requestEntity != null)
{
requestBody = requestEntity.toString();
}
}
else if(method instanceof PostMethod)

View File

@@ -39,6 +39,7 @@ import javax.servlet.http.HttpServletResponse;
import org.alfresco.cmis.client.impl.AlfrescoObjectFactoryImpl;
import org.alfresco.opencmis.CMISDispatcherRegistry.Binding;
import org.alfresco.rest.api.tests.client.PublicApiHttpClient.BinaryPayload;
import org.alfresco.rest.api.tests.client.data.Activities;
import org.alfresco.rest.api.tests.client.data.Activity;
import org.alfresco.rest.api.tests.client.data.CMISNode;
@@ -549,6 +550,17 @@ public class PublicApiClient
return response;
}
public HttpResponse putBinary(String scope, int version, String entityCollectionName, Object entityId, String relationCollectionName,
Object relationshipEntityId, BinaryPayload payload, Map<String, String> params) throws IOException
{
HttpResponse response = client.putBinary(getRequestContext(), scope, version, entityCollectionName, entityId, relationCollectionName, relationshipEntityId,
payload, params);
logger.debug(response.toString());
return response;
}
public HttpResponse delete(String scope, String entityCollectionName, Object entityId, String relationCollectionName, Object relationshipEntityId) throws IOException
{
HttpResponse response = client.delete(getRequestContext(), scope, entityCollectionName, entityId, relationCollectionName, relationshipEntityId);

View File

@@ -26,7 +26,12 @@
package org.alfresco.rest.api.tests.client;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
@@ -55,6 +60,7 @@ import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.OptionsMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.TraceMethod;
import org.apache.commons.logging.Log;
@@ -570,6 +576,23 @@ public class PublicApiHttpClient
return submitRequest(req, rq);
}
public HttpResponse putBinary(final RequestContext rq, final String scope, final int version, final String entityCollectionName,
final Object entityId, final String relationCollectionName, final Object relationshipEntityId, final BinaryPayload payload,
final Map<String, String> params) throws IOException
{
RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName, entityId, relationCollectionName,
relationshipEntityId, params);
String url = endpoint.getUrl();
PutMethod req = new PutMethod(url);
if (payload != null)
{
BinaryRequestEntity requestEntity = new BinaryRequestEntity(payload.getFile(), payload.getMimeType(), payload.getCharset());
req.setRequestEntity(requestEntity);
}
return submitRequest(req, rq);
}
/*
* Encapsulates information relating to a rest api end point, generating and
* encoding urls based on the rest api implementation class.
@@ -824,4 +847,100 @@ public class PublicApiHttpClient
return url;
}
}
/**
* @author Jamal Kaabi-Mofrad
*/
public static class BinaryRequestEntity implements RequestEntity
{
private final File file;
private final String mimeType;
private final String charset;
public BinaryRequestEntity(File file, String mimeType, String charset)
{
this.file = file;
this.mimeType = (mimeType == null) ? "application/octet-stream" : mimeType;
this.charset = (charset == null) ? "UTF-8" : charset;
}
@Override
public boolean isRepeatable()
{
return true;
}
@Override
public void writeRequest(OutputStream out) throws IOException
{
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
try
{
int len;
byte[] buffer = new byte[8190];
while ((len = inputStream.read(buffer)) != -1)
{
out.write(buffer, 0, len);
}
}
finally
{
inputStream.close();
}
}
@Override
public long getContentLength()
{
return file.length();
}
@Override
public String getContentType()
{
return mimeType + "; " + charset;
}
}
/**
* @author Jamal Kaabi-Mofrad
*/
public static class BinaryPayload
{
private File file;
private String mimeType;
private String charset;
public BinaryPayload(File file, String mimeType, String charset)
{
this.file = file;
this.mimeType = mimeType;
this.charset = charset;
}
public BinaryPayload(File file, String mimeType)
{
this(file, mimeType, null);
}
public BinaryPayload(File file)
{
this(file, null, null);
}
public File getFile()
{
return file;
}
public String getMimeType()
{
return mimeType;
}
public String getCharset()
{
return charset;
}
}
}

View File

@@ -40,16 +40,16 @@ import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.util.TempFileProvider;
@EntityResource(name="flock",title="A resource used for testing binary properties")
public class FlockEntityResource implements BinaryResourceAction.Read, BinaryResourceAction.Delete, BinaryResourceAction.Update
public class FlockEntityResource implements BinaryResourceAction.Read, BinaryResourceAction.Delete, BinaryResourceAction.Update<Flock>
{
//versions/1/flock/xyz/photo PUT
@Override
@WebApiDescription(title = "Updates a photo")
@BinaryProperties("photo")
public void updateProperty(String entityId, BasicContentInfo contentInfo, InputStream stream, Parameters params)
public Flock updateProperty(String entityId, BasicContentInfo contentInfo, InputStream stream, Parameters params)
{
return;
return null;
}
//versions/1/flock/xyz/photo DELETE

View File

@@ -37,15 +37,15 @@ import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.Parameters;
@EntityResource(name="flocket",title="A resource used for testing binary properties with lost of properties")
public class FlocketEntityResource implements BinaryResourceAction.Read, BinaryResourceAction.Delete, BinaryResourceAction.Update
public class FlocketEntityResource implements BinaryResourceAction.Read, BinaryResourceAction.Delete, BinaryResourceAction.Update<Flocket>
{
@Override
@WebApiDescription(title = "Updates a flocket")
@BinaryProperties({"photo","album"})
public void updateProperty(String entityId, BasicContentInfo contentInfo, InputStream stream, Parameters params)
public Flocket updateProperty(String entityId, BasicContentInfo contentInfo, InputStream stream, Parameters params)
{
return;
return null;
}
@Override