diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml index 7f5187378f..c18a6f948b 100644 --- a/config/alfresco/public-rest-context.xml +++ b/config/alfresco/public-rest-context.xml @@ -1061,6 +1061,7 @@ + diff --git a/source/java/org/alfresco/rest/api/Renditions.java b/source/java/org/alfresco/rest/api/Renditions.java index 4bbeb8d612..c331dbf4af 100644 --- a/source/java/org/alfresco/rest/api/Renditions.java +++ b/source/java/org/alfresco/rest/api/Renditions.java @@ -20,6 +20,7 @@ package org.alfresco.rest.api; import org.alfresco.rest.api.model.Rendition; +import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; @@ -58,4 +59,14 @@ public interface Renditions * @param parameters the {@link Parameters} object to get the parameters passed into the request */ void createRendition(String nodeId, Rendition rendition, Parameters parameters); + + /** + * Downloads rendition. + * + * @param nodeId the source node id + * @param renditionId the rendition id + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return the rendition stream + */ + BinaryResource getContent(String nodeId, String renditionId, Parameters parameters); } diff --git a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java index 8510f80b6f..651e4bf84c 100644 --- a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java +++ b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java @@ -24,15 +24,22 @@ import org.alfresco.query.PagingResults; import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.thumbnail.ThumbnailHelper; import org.alfresco.repo.thumbnail.ThumbnailRegistry; +import org.alfresco.repo.thumbnail.script.ScriptThumbnailService; import org.alfresco.rest.antlr.WhereClauseParser; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.Renditions; import org.alfresco.rest.api.model.ContentInfo; import org.alfresco.rest.api.model.Rendition; import org.alfresco.rest.api.model.Rendition.RenditionStatus; +import org.alfresco.rest.framework.core.exceptions.ApiException; import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; 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.resource.content.BinaryResource; +import org.alfresco.rest.framework.resource.content.ContentInfoImpl; +import org.alfresco.rest.framework.resource.content.FileBinaryResource; +import org.alfresco.rest.framework.resource.content.NodeBinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Parameters; @@ -52,8 +59,16 @@ import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.PropertyCheck; +import org.alfresco.util.TempFileProvider; import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.ResourceLoader; +import java.io.File; +import java.io.InputStream; +import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.Map; @@ -63,19 +78,23 @@ import java.util.TreeMap; /** * @author Jamal Kaabi-Mofrad */ -public class RenditionsImpl implements Renditions +public class RenditionsImpl implements Renditions, ResourceLoaderAware { + private static final Log LOGGER = LogFactory.getLog(RenditionsImpl.class); + private static final String PARAM_status = "status"; private static final Set RENDITION_STATUS_COLLECTION_EQUALS_QUERY_PROPERTIES = Collections.singleton(PARAM_status); private Nodes nodes; private NodeService nodeService; private ThumbnailService thumbnailService; + private ScriptThumbnailService scriptThumbnailService; private RenditionService renditionService; private MimetypeService mimetypeService; private ActionService actionService; private NamespaceService namespaceService; private ServiceRegistry serviceRegistry; + private ResourceLoader resourceLoader; public void setNodes(Nodes nodes) { @@ -87,15 +106,27 @@ public class RenditionsImpl implements Renditions this.thumbnailService = thumbnailService; } + public void setScriptThumbnailService(ScriptThumbnailService scriptThumbnailService) + { + this.scriptThumbnailService = scriptThumbnailService; + } + public void setServiceRegistry(ServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) + { + this.resourceLoader = resourceLoader; + } + public void init() { PropertyCheck.mandatory(this, "nodes", nodes); PropertyCheck.mandatory(this, "thumbnailService", thumbnailService); + PropertyCheck.mandatory(this, "scriptThumbnailService", scriptThumbnailService); PropertyCheck.mandatory(this, "serviceRegistry", serviceRegistry); this.nodeService = serviceRegistry.getNodeService(); @@ -238,6 +269,86 @@ public class RenditionsImpl implements Renditions actionService.executeAction(action, sourceNodeRef, true, true); } + @Override + public BinaryResource getContent(String nodeId, String renditionId, Parameters parameters) + { + final NodeRef sourceNodeRef = validateSourceNode(nodeId); + final NodeRef renditionNodeRef = getRenditionByName(sourceNodeRef, renditionId, parameters); + + // By default set attachment header (with rendition Id) unless attachment=false + boolean attach = true; + String attachment = parameters.getParameter("attachment"); + if (attachment != null) + { + attach = Boolean.valueOf(attachment); + } + final String attachFileName = (attach ? renditionId : null); + + if (renditionNodeRef == null) + { + boolean isPlaceholder = Boolean.valueOf(parameters.getParameter("placeholder")); + if (!isPlaceholder) + { + throw new NotFoundException("Thumbnail was not found for [" + renditionId + ']'); + } + ContentData contentData = (ContentData) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_CONTENT); + String sourceNodeMimeType = null; + if (contentData != null) + { + sourceNodeMimeType = contentData.getMimetype(); + } + // resource based on the content's mimeType and rendition id + String phPath = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath(renditionId, sourceNodeMimeType); + if (phPath == null) + { + // 404 since no thumbnail was found + throw new NotFoundException("Thumbnail was not found and no placeholder resource available for [" + renditionId + ']'); + } + else + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Retrieving content from resource path [" + phPath + ']'); + } + // get extension of resource + String ext = ""; + int extIndex = phPath.lastIndexOf('.'); + if (extIndex != -1) + { + ext = phPath.substring(extIndex); + } + + try + { + final String resourcePath = "classpath:" + phPath; + InputStream inputStream = resourceLoader.getResource(resourcePath).getInputStream(); + // create temporary file + File file = TempFileProvider.createTempFile(inputStream, "RenditionsApi-", ext); + return new FileBinaryResource(file, attachFileName); + } + catch (Exception ex) + { + if (LOGGER.isErrorEnabled()) + { + LOGGER.error("Couldn't load the placeholder." + ex.getMessage()); + } + new ApiException("Couldn't load the placeholder."); + } + } + } + + Map nodeProps = nodeService.getProperties(renditionNodeRef); + ContentData contentData = (ContentData) nodeProps.get(ContentModel.PROP_CONTENT); + + org.alfresco.rest.framework.resource.content.ContentInfo contentInfo = null; + if (contentData != null) + { + contentInfo = new ContentInfoImpl(contentData.getMimetype(), contentData.getEncoding(), contentData.getSize(), contentData.getLocale()); + } + + return new NodeBinaryResource(renditionNodeRef, ContentModel.PROP_CONTENT, contentInfo, attachFileName); + } + protected NodeRef getRenditionByName(NodeRef nodeRef, String renditionId, Parameters parameters) { if (StringUtils.isEmpty(renditionId)) diff --git a/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java b/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java index f74c6cdc87..d8969ae838 100644 --- a/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java +++ b/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java @@ -21,9 +21,12 @@ package org.alfresco.rest.api.nodes; import org.alfresco.rest.api.Renditions; import org.alfresco.rest.api.model.Rendition; +import org.alfresco.rest.framework.BinaryProperties; import org.alfresco.rest.framework.WebApiDescription; import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; +import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.util.PropertyCheck; @@ -42,6 +45,7 @@ import java.util.List; public class NodeRenditionsRelation implements RelationshipResourceAction.Read, RelationshipResourceAction.ReadById, RelationshipResourceAction.Create, + RelationshipResourceBinaryAction.Read, InitializingBean { @@ -80,4 +84,12 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Read. + */ + +package org.alfresco.rest.framework.resource.content; + +/** + * An abstract binary resource. + * + * @author Jamal Kaabi-Mofrad + */ +public class AbstractBinaryResource implements BinaryResource +{ + final String attachFileName; + + public AbstractBinaryResource(String attachFileName) + { + this.attachFileName = attachFileName; + } + + public String getAttachFileName() + { + return attachFileName; + } +} diff --git a/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java b/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java index ed41842a88..903fd7ec5c 100755 --- a/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java +++ b/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java @@ -30,16 +30,21 @@ import java.io.File; /** * A binary resource based on a File. - * + * * @author Gethin James */ -public class FileBinaryResource implements BinaryResource +public class FileBinaryResource extends AbstractBinaryResource { final File file; public FileBinaryResource(File file) { - super(); + this(file, null); + } + + public FileBinaryResource(File file, String attachFileName) + { + super(attachFileName); this.file = file; } diff --git a/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java b/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java index 6410d84e4b..21e176d73b 100755 --- a/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java +++ b/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java @@ -30,25 +30,22 @@ import org.alfresco.service.namespace.QName; /** * A binary resource based on a Node reference. - * + * * @author Gethin James */ -public class NodeBinaryResource implements BinaryResource +public class NodeBinaryResource extends AbstractBinaryResource { final NodeRef nodeRef; final QName propertyQName; final ContentInfo contentInfo; - final String attachFileName; - + public NodeBinaryResource(NodeRef nodeRef, QName propertyQName, ContentInfo contentInfo, String attachFileName) { - super(); - + super(attachFileName); this.nodeRef = nodeRef; this.propertyQName = propertyQName; this.contentInfo = contentInfo; - this.attachFileName = attachFileName; } public NodeRef getNodeRef() @@ -65,9 +62,4 @@ public class NodeBinaryResource implements BinaryResource { return this.contentInfo; } - - public String getAttachFileName() - { - return this.attachFileName; - } -} +} \ No newline at end of file diff --git a/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java b/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java index 5b61920df6..802bcc4014 100644 --- a/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java +++ b/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java @@ -47,6 +47,7 @@ import org.alfresco.rest.framework.resource.content.ContentInfo; import org.alfresco.rest.framework.resource.content.FileBinaryResource; import org.alfresco.rest.framework.resource.content.NodeBinaryResource; import org.alfresco.rest.framework.resource.parameters.Params; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.jackson.JsonGenerationException; @@ -90,11 +91,11 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements @Override public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException { - try - { + try + { final Map respons = new HashMap(); final Map templateVars = req.getServiceMatch().getTemplateVars(); - final ResourceWithMetadata resource = locator.locateResource(api,templateVars, httpMethod); + final ResourceWithMetadata resource = locator.locateResource(api,templateVars, httpMethod); final Params params = paramsExtractor.extractParams(resource.getMetaData(),req); final boolean isReadOnly = HttpMethod.GET==httpMethod; @@ -132,19 +133,19 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements } } - } - catch (ApiException apiException) - { - renderErrorResponse(resolveException(apiException), res); - } - catch (WebScriptException webException) - { - renderErrorResponse(resolveException(webException), res); - } - catch (RuntimeException runtimeException) - { - renderErrorResponse(resolveException(runtimeException), res); - } + } + catch (ApiException apiException) + { + renderErrorResponse(resolveException(apiException), res); + } + catch (WebScriptException webException) + { + renderErrorResponse(resolveException(webException), res); + } + catch (RuntimeException runtimeException) + { + renderErrorResponse(resolveException(runtimeException), res); + } } public Object execute(final ResourceWithMetadata resource, final Params params, final WebScriptResponse res, boolean isReadOnly) @@ -176,6 +177,8 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements if (resource instanceof FileBinaryResource) { FileBinaryResource fileResource = (FileBinaryResource) resource; + // if requested, set attachment + setAttachment(res, fileResource.getAttachFileName()); streamer.streamContent(req, res, fileResource.getFile(), null, false, null, null); } else if (resource instanceof NodeBinaryResource) @@ -183,16 +186,21 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements NodeBinaryResource nodeResource = (NodeBinaryResource) resource; ContentInfo contentInfo = nodeResource.getContentInfo(); setContentInfoOnResponse(res,contentInfo); - String attachFileName = nodeResource.getAttachFileName(); - if ((attachFileName != null) && (attachFileName.length() > 0)) - { - String headerValue = "attachment; filename=\"" + attachFileName + "\"; filename*=UTF-8''" + URLEncoder.encode(attachFileName); - res.setHeader(HDR_NAME_CONTENT_DISPOSITION, headerValue); - } + // if requested, set attachment + setAttachment(res, nodeResource.getAttachFileName()); streamer.streamContent(req, res, nodeResource.getNodeRef(), nodeResource.getPropertyQName(), false, null, null); } } + private void setAttachment(final WebScriptResponse res, final String attachFileName) + { + if (StringUtils.isNotEmpty(attachFileName)) + { + String headerValue = "attachment; filename=\"" + attachFileName + "\"; filename*=UTF-8''" + URLEncoder.encode(attachFileName); + res.setHeader(HDR_NAME_CONTENT_DISPOSITION, headerValue); + } + } + /** * The response status must be set before the response is written by Jackson (which will by default close and commit the response). * In a r/w txn, web script buffered responses ensure that it doesn't really matter but for r/o txns this is important. diff --git a/source/test-java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java b/source/test-java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java index 76d952ccf9..a8e0a911f0 100644 --- a/source/test-java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java @@ -147,10 +147,15 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi } protected HttpResponse getSingle(String url, String runAsUser, String entityId, int expectedStatus) throws Exception + { + return getSingle(url, runAsUser, entityId, null, expectedStatus); + } + + protected HttpResponse getSingle(String url, String runAsUser, String entityId, Map params, int expectedStatus) throws Exception { publicApiClient.setRequestContext(new RequestContext(runAsUser)); - HttpResponse response = publicApiClient.get(getScope(), url, entityId, null, null, null); + HttpResponse response = publicApiClient.get(getScope(), url, entityId, null, null, params); checkStatus(expectedStatus, response.getStatusCode()); return response; diff --git a/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java b/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java index 2d1185fe5d..4886a6b9a9 100644 --- a/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java @@ -53,6 +53,8 @@ import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.InputStream; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -370,6 +372,141 @@ public class RenditionsTest extends AbstractBaseApiTest } } + /** + * Tests download rendition. + *

GET:

+ * {@literal :/alfresco/api//public/alfresco/versions/1/nodes//renditions//content} + */ + @Test + public void testDownloadRendition() throws Exception + { + // Create a folder within the site document's library + String folderName = "folder" + System.currentTimeMillis(); + String folder_Id = addToDocumentLibrary(userOneN1Site, folderName, ContentModel.TYPE_FOLDER, userOneN1.getId()); + + // Create multipart request + String fileName = "quick.pdf"; + File file = getResourceFile(fileName); + MultiPartBuilder multiPartBuilder = MultiPartBuilder.create() + .setFileData(new FileData(fileName, file, MimetypeMap.MIMETYPE_PDF)); + MultiPartRequest reqBody = multiPartBuilder.build(); + + // Upload quick.pdf file into 'folder' + HttpResponse response = post("nodes/" + folder_Id + "/children", userOneN1.getId(), reqBody.getBody(), null, reqBody.getContentType(), 201); + Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + String contentNodeId = document.getId(); + + // Get rendition (not created yet) information for node + response = getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), "doclib", 200); + Rendition rendition = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Rendition.class); + assertNotNull(rendition); + assertEquals(RenditionStatus.NOT_CREATED, rendition.getStatus()); + + // Download placeholder - by default with Content-Disposition header + Map params = new HashMap<>(); + params.put("placeholder", "true"); + response = getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), ("doclib/content"), params, 200); + assertNotNull(response.getResponseAsBytes()); + Map responseHeaders = response.getHeaders(); + assertNotNull(responseHeaders); + String contentDisposition = responseHeaders.get("Content-Disposition"); + assertNotNull(contentDisposition); + assertTrue(contentDisposition.contains("filename=\"doclib\"")); + String contentType = responseHeaders.get("Content-Type"); + assertNotNull(contentType); + assertTrue(contentType.startsWith(MimetypeMap.MIMETYPE_IMAGE_PNG)); + + // Download placeholder - without Content-Disposition header (attachment=false) + params.put("attachment", "false"); + response = getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), ("doclib/content"), params, 200); + assertNotNull(response.getResponseAsBytes()); + responseHeaders = response.getHeaders(); + assertNotNull(responseHeaders); + assertNull(responseHeaders.get("Content-Disposition")); + contentType = responseHeaders.get("Content-Type"); + assertNotNull(contentType); + assertTrue(contentType.startsWith(MimetypeMap.MIMETYPE_IMAGE_PNG)); + + // Create and get 'doclib' rendition + rendition = createAndGetRendition(contentNodeId, "doclib"); + assertNotNull(rendition); + assertEquals(RenditionStatus.CREATED, rendition.getStatus()); + + // Download rendition - by default with Content-Disposition header + response = getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), "doclib/content", 200); + assertNotNull(response.getResponseAsBytes()); + responseHeaders = response.getHeaders(); + assertNotNull(responseHeaders); + contentDisposition = responseHeaders.get("Content-Disposition"); + assertNotNull(contentDisposition); + assertTrue(contentDisposition.contains("filename=\"doclib\"")); + contentType = responseHeaders.get("Content-Type"); + assertNotNull(contentType); + assertTrue(contentType.startsWith(MimetypeMap.MIMETYPE_IMAGE_PNG)); + + // Download rendition - without Content-Disposition header (attachment=false) + params = Collections.singletonMap("attachment", "false"); + response = getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), "doclib/content", params, 200); + assertNotNull(response.getResponseAsBytes()); + responseHeaders = response.getHeaders(); + assertNotNull(responseHeaders); + assertNull(responseHeaders.get("Content-Disposition")); + contentType = responseHeaders.get("Content-Type"); + assertNotNull(contentType); + assertTrue(contentType.startsWith(MimetypeMap.MIMETYPE_IMAGE_PNG)); + + // Download rendition - with Content-Disposition header (attachment=true) same as default + params = Collections.singletonMap("attachment", "true"); + response = getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), "doclib/content", params, 200); + assertNotNull(response.getResponseAsBytes()); + responseHeaders = response.getHeaders(); + assertNotNull(responseHeaders); + contentDisposition = responseHeaders.get("Content-Disposition"); + assertNotNull(contentDisposition); + assertTrue(contentDisposition.contains("filename=\"doclib\"")); + contentType = responseHeaders.get("Content-Type"); + assertNotNull(contentType); + assertTrue(contentType.startsWith(MimetypeMap.MIMETYPE_IMAGE_PNG)); + + //-ve tests + // nodeId in the path parameter does not represent a file + getSingle(getRenditionsUrl(folder_Id), userOneN1.getId(), "doclib/content", 400); + + // nodeId in the path parameter does not exist + getSingle(getRenditionsUrl(UUID.randomUUID().toString()), userOneN1.getId(), "doclib/content", 404); + + // renditionId in the path parameter is not registered/available + getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), ("renditionId" + System.currentTimeMillis() + "/content"), 404); + + InputStream inputStream = new ByteArrayInputStream("The quick brown fox jumps over the lazy dog".getBytes()); + file = TempFileProvider.createTempFile(inputStream, "RenditionsTest-", ".abcdef"); + reqBody = MultiPartBuilder.create() + .setFileData(new FileData(file.getName(), file, "application/unknown")) + .build(); + // Upload temp file into 'folder' + response = post("nodes/" + folder_Id + "/children", userOneN1.getId(), reqBody.getBody(), null, reqBody.getContentType(), 201); + document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + contentNodeId = document.getId(); + // Check there is no rendition created yet + response = getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), "doclib", 200); + rendition = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Rendition.class); + assertNotNull(rendition); + assertEquals(RenditionStatus.NOT_CREATED, rendition.getStatus()); + + // The content of the rendition does not exist and the placeholder parameter is not present + getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), "doclib/content", 404); + + // The content of the rendition does not exist and the placeholder parameter has a value of "false" + params = Collections.singletonMap("placeholder", "false"); + getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), "doclib/content", params, 404); + + // The rendition does not exist, a placeholder is not available and the placeholder parameter has a value of "true" + params = Collections.singletonMap("placeholder", "true"); + getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), ("renditionId" + System.currentTimeMillis() + "/content"), params, 404); + + //TODO add tests for 304 response + } + private String addToDocumentLibrary(final TestSite testSite, final String name, final QName type, String user) { return TenantUtil.runAsUserTenant(new TenantUtil.TenantRunAsWork()