diff --git a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java index 651e4bf84c..927b01f7ef 100644 --- a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java +++ b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java @@ -33,7 +33,6 @@ 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; @@ -82,8 +81,8 @@ 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 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; @@ -140,9 +139,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware public CollectionWithPagingInfo getRenditions(String nodeId, Parameters parameters) { final NodeRef nodeRef = validateSourceNode(nodeId); - - ContentData contentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - String contentMimeType = contentData.getMimetype(); + String contentMimeType = getMimeType(nodeRef); Query query = parameters.getQuery(); boolean includeCreated = true; @@ -153,7 +150,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware MapBasedQueryWalker propertyWalker = new MapBasedQueryWalker(RENDITION_STATUS_COLLECTION_EQUALS_QUERY_PROPERTIES, null); QueryHelper.walk(query, propertyWalker); - String withStatus = propertyWalker.getProperty(PARAM_status, WhereClauseParser.EQUALS); + String withStatus = propertyWalker.getProperty(PARAM_STATUS, WhereClauseParser.EQUALS); if (withStatus != null) { try @@ -219,7 +216,27 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware ThumbnailDefinition thumbnailDefinition = thumbnailService.getThumbnailRegistry().getThumbnailDefinition(renditionId); if (thumbnailDefinition == null) { - throw new EntityNotFoundException(renditionId); + throw new NotFoundException(renditionId + " is not registered."); + } + else + { + String contentMimeType = getMimeType(nodeRef); + // List all available thumbnail definitions for the source node + List thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentMimeType, -1); + boolean found = false; + for (ThumbnailDefinition td : thumbnailDefinitions) + { + // Check the registered renditionId is applicable for the node's mimeType + if (renditionId.equals(td.getName())) + { + found = true; + break; + } + } + if (!found) + { + throw new NotFoundException(renditionId + " is not applicable for the node's mimeType " + contentMimeType); + } } return toApiRendition(thumbnailDefinition); } @@ -248,14 +265,10 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware ThumbnailDefinition thumbnailDefinition = registry.getThumbnailDefinition(rendition.getId()); if (thumbnailDefinition == null) { - throw new EntityNotFoundException(rendition.getId() + "' is not registered."); + throw new NotFoundException(rendition.getId() + " is not registered."); } - ContentData contentData = (ContentData) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_CONTENT); - if (!ContentData.hasContent(contentData)) - { - throw new InvalidArgumentException("Unable to create thumbnail '" + thumbnailDefinition.getName() + "' as there is no content."); - } + ContentData contentData = getContentData(sourceNodeRef, true); // Check if anything is currently available to generate thumbnails for the specified mimeType if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), contentData.getMimetype(), contentData.getSize(), sourceNodeRef, thumbnailDefinition)) @@ -291,12 +304,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware { 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(); - } + String sourceNodeMimeType = getMimeType(sourceNodeRef); // resource based on the content's mimeType and rendition id String phPath = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath(renditionId, sourceNodeMimeType); if (phPath == null) @@ -373,8 +381,15 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware String renditionName = (String) nodeService.getProperty(renditionNodeRef, ContentModel.PROP_NAME); apiRendition.setId(renditionName); - ContentData contentData = (ContentData) nodeService.getProperty(renditionNodeRef, ContentModel.PROP_CONTENT); - ContentInfo contentInfo = new ContentInfo(contentData.getMimetype(), getMimeTypeDisplayName(contentData.getMimetype()), contentData.getSize(), contentData.getEncoding()); + ContentData contentData = getContentData(renditionNodeRef, false); + ContentInfo contentInfo = null; + if (contentData != null) + { + contentInfo = new ContentInfo(contentData.getMimetype(), + getMimeTypeDisplayName(contentData.getMimetype()), + contentData.getSize(), + contentData.getEncoding()); + } apiRendition.setContent(contentInfo); apiRendition.setStatus(RenditionStatus.CREATED); @@ -407,4 +422,20 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware { return mimetypeService.getDisplaysByMimetype().get(mimeType); } + + private ContentData getContentData(NodeRef nodeRef, boolean validate) + { + ContentData contentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (validate && !ContentData.hasContent(contentData)) + { + throw new InvalidArgumentException("Node id '" + nodeRef.getId() + "' has no content."); + } + return contentData; + } + + private String getMimeType(NodeRef nodeRef) + { + ContentData contentData = getContentData(nodeRef, true); + return contentData.getMimetype(); + } } diff --git a/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java b/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java index d8969ae838..cd53519fe3 100644 --- a/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java +++ b/source/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java @@ -33,7 +33,6 @@ import org.alfresco.util.PropertyCheck; import org.springframework.beans.factory.InitializingBean; import org.springframework.extensions.webscripts.Status; -import java.util.Collections; import java.util.List; /** @@ -82,7 +81,7 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Read. + */ + package org.alfresco.rest.framework.core.exceptions; import java.util.Map; @@ -31,6 +51,7 @@ public class ApiException extends PlatformRuntimeException public ApiException(String msgId, Object[] msgParams) { super(msgId, msgParams); + this.msgId = msgId; } public ApiException(String msgId, Throwable cause) 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 4886a6b9a9..5ff522cc53 100644 --- a/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java @@ -33,6 +33,7 @@ import org.alfresco.rest.api.tests.RepoService.TestNetwork; 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.ExpectedErrorResponse; import org.alfresco.rest.api.tests.client.PublicApiClient.ExpectedPaging; import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; import org.alfresco.rest.api.tests.client.data.ContentInfo; @@ -68,7 +69,7 @@ import java.util.UUID; public class RenditionsTest extends AbstractBaseApiTest { private static final long PAUSE_TIME = 5000; //millisecond - private static final int MAX_RETRY = 8; + private static final int MAX_RETRY = 10; /** * User one from network one @@ -221,6 +222,19 @@ public class RenditionsTest extends AbstractBaseApiTest // nodeId in the path parameter does not exist getAll(getRenditionsUrl(UUID.randomUUID().toString()), userOneN1.getId(), paging, params, 404); + + // Create a node without any content + String emptyContentNodeId = addToDocumentLibrary(userOneN1Site, "emptyDoc.txt", ContentModel.TYPE_CONTENT, userOneN1.getId()); + // The source node has no content + getAll(getRenditionsUrl(emptyContentNodeId), userOneN1.getId(), paging, params, 400); + + // Invalid status value + params.put("where", "(status='WRONG')"); + getAll(getRenditionsUrl(contentNodeId), userOneN1.getId(), paging, params, 400); + + // Invalid filter (only 'status' is supported) + params.put("where", "(id='doclib')"); + getAll(getRenditionsUrl(contentNodeId), userOneN1.getId(), paging, params, 400); } /** @@ -277,6 +291,33 @@ public class RenditionsTest extends AbstractBaseApiTest // renditionId in the path parameter is not registered/available getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), ("renditionId" + System.currentTimeMillis()), 404); + + // Create a node without any content + String emptyContentNodeId = addToDocumentLibrary(userOneN1Site, "emptyDoc.txt", ContentModel.TYPE_CONTENT, userOneN1.getId()); + // The source node has no content + getSingle(getRenditionsUrl(emptyContentNodeId), userOneN1.getId(), "doclib", 400); + + // Create multipart request + String jpgFileName = "quick.jpg"; + File jpgFile = getResourceFile(fileName); + reqBody = MultiPartBuilder.create() + .setFileData(new FileData(jpgFileName, jpgFile, MimetypeMap.MIMETYPE_IMAGE_JPEG)) + .build(); + + // Upload quick.jpg file into 'folder' + response = post("nodes/" + folder_Id + "/children", userOneN1.getId(), reqBody.getBody(), null, reqBody.getContentType(), 201); + Document jpgImage = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + String jpgImageNodeId = jpgImage.getId(); + + // List all available renditions (includes those that have been created and those that are yet to be created) + response = getAll(getRenditionsUrl(jpgImageNodeId), userOneN1.getId(), getPaging(0, 50), 200); + List renditions = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Rendition.class); + // Check there is no pdf rendition is available for the jpg file + Rendition pdf = getRendition(renditions, "pdf"); + assertNull(pdf); + + // The renditionId (pdf) is registered but it is not applicable for the node's mimeType + getSingle(getRenditionsUrl(jpgImageNodeId), userOneN1.getId(), "pdf", 404); } /** @@ -321,7 +362,14 @@ public class RenditionsTest extends AbstractBaseApiTest // -ve Tests // The rendition requested already exists - post(getRenditionsUrl(folder_Id), userOneN1.getId(), toJsonAsString(new Rendition().setId("imgpreview")), 400); + response = post(getRenditionsUrl(contentNodeId), userOneN1.getId(), toJsonAsString(new Rendition().setId("imgpreview")), 400); + ExpectedErrorResponse errorResponse = RestApiUtil.parseErrorResponse(response.getJsonResponse()); + assertNotNull(errorResponse); + assertNotNull(errorResponse.getErrorKey()); + assertNotNull(errorResponse.getBriefSummary()); + assertNotNull(errorResponse.getStackTrace()); + assertNotNull(errorResponse.getDescriptionURL()); + assertEquals(400, errorResponse.getStatusCode()); // Create 'doclib' rendition request Rendition renditionRequest = new Rendition().setId("doclib"); @@ -329,12 +377,26 @@ public class RenditionsTest extends AbstractBaseApiTest post(getRenditionsUrl(folder_Id), userOneN1.getId(), toJsonAsString(renditionRequest), 400); // nodeId in the path parameter does not exist - post(getRenditionsUrl(UUID.randomUUID().toString()), userOneN1.getId(), toJsonAsString(renditionRequest), 404); + response = post(getRenditionsUrl(UUID.randomUUID().toString()), userOneN1.getId(), toJsonAsString(renditionRequest), 404); + // EntityNotFoundException + errorResponse = RestApiUtil.parseErrorResponse(response.getJsonResponse()); + assertNotNull(errorResponse); + assertNotNull(errorResponse.getErrorKey()); + assertNotNull(errorResponse.getBriefSummary()); + assertNotNull(errorResponse.getStackTrace()); + assertNotNull(errorResponse.getDescriptionURL()); + assertEquals(404, errorResponse.getStatusCode()); // renditionId is not registered final String randomRenditionId = "renditionId" + System.currentTimeMillis(); post(getRenditionsUrl(contentNodeId), userOneN1.getId(), toJsonAsString(new Rendition().setId(randomRenditionId)), 404); + // renditionId is null + post(getRenditionsUrl(contentNodeId), userOneN1.getId(), toJsonAsString(new Rendition().setId(null)), 400); + + // renditionId is empty + post(getRenditionsUrl(contentNodeId), userOneN1.getId(), toJsonAsString(new Rendition().setId("")), 400); + // Create a node without any content String emptyContentNodeId = addToDocumentLibrary(userOneN1Site, "emptyDoc.txt", ContentModel.TYPE_CONTENT, userOneN1.getId()); // The source node has no content @@ -364,7 +426,14 @@ public class RenditionsTest extends AbstractBaseApiTest response = post("nodes/" + folder_Id + "/children", userOneN1.getId(), reqBody.getBody(), null, reqBody.getContentType(), 201); Document txtDocument = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); // Thumbnail generation has been disabled - post(getRenditionsUrl(txtDocument.getId()), userOneN1.getId(), toJsonAsString(renditionRequest), 501); + response = post(getRenditionsUrl(txtDocument.getId()), userOneN1.getId(), toJsonAsString(renditionRequest), 501); + errorResponse = RestApiUtil.parseErrorResponse(response.getJsonResponse()); + assertNotNull(errorResponse); + assertNotNull(errorResponse.getErrorKey()); + assertNotNull(errorResponse.getBriefSummary()); + assertNotNull(errorResponse.getStackTrace()); + assertNotNull(errorResponse.getDescriptionURL()); + assertEquals(501, errorResponse.getStatusCode()); } finally { @@ -481,7 +550,7 @@ public class RenditionsTest extends AbstractBaseApiTest 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")) + .setFileData(new FileData(file.getName(), file, MimetypeMap.MIMETYPE_TEXT_PLAIN)) .build(); // Upload temp file into 'folder' response = post("nodes/" + folder_Id + "/children", userOneN1.getId(), reqBody.getBody(), null, reqBody.getContentType(), 201); @@ -504,6 +573,11 @@ public class RenditionsTest extends AbstractBaseApiTest params = Collections.singletonMap("placeholder", "true"); getSingle(getRenditionsUrl(contentNodeId), userOneN1.getId(), ("renditionId" + System.currentTimeMillis() + "/content"), params, 404); + // Create a node without any content + String emptyContentNodeId = addToDocumentLibrary(userOneN1Site, "emptyDoc.txt", ContentModel.TYPE_CONTENT, userOneN1.getId()); + // The source node has no content + getSingle(getRenditionsUrl(emptyContentNodeId), userOneN1.getId(), "doclib/content", params, 400); + //TODO add tests for 304 response } @@ -529,7 +603,8 @@ public class RenditionsTest extends AbstractBaseApiTest { try { - post(getRenditionsUrl(sourceNodeId), userOneN1.getId(), toJsonAsString(renditionRequest), 202); + HttpResponse res = post(getRenditionsUrl(sourceNodeId), userOneN1.getId(), toJsonAsString(renditionRequest), 202); + assertNull(res.getJsonResponse()); break; } catch (AssertionError ex) diff --git a/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java b/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java index 6d66099b2d..23ec22926f 100644 --- a/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java +++ b/source/test-java/org/alfresco/rest/api/tests/client/PublicApiClient.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.math.BigInteger; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -2002,4 +2003,114 @@ public class PublicApiClient + (maxItems != null ? "maxItems=" + maxItems : "") + "]"; } } + + /** + * Representation of an error response. + * + * @author Jamal Kaabi-Mofrad + */ + public static class ExpectedErrorResponse + { + private String errorKey; + private int statusCode; + private String briefSummary; + private String stackTrace; + private Map additionalState; + private String descriptionURL; + + public ExpectedErrorResponse() + { + } + + public ExpectedErrorResponse(String errorKey, int statusCode, String briefSummary, StackTraceElement[] stackTrace, + Map additionalState) + { + super(); + this.errorKey = errorKey; + this.statusCode = statusCode; + this.briefSummary = briefSummary; + this.stackTrace = Arrays.toString(stackTrace); + this.additionalState = additionalState; + } + + public String getErrorKey() + { + return errorKey; + } + + public ExpectedErrorResponse setErrorKey(String errorKey) + { + this.errorKey = errorKey; + return this; + } + + public int getStatusCode() + { + return statusCode; + } + + public ExpectedErrorResponse setStatusCode(int statusCode) + { + this.statusCode = statusCode; + return this; + } + + public String getBriefSummary() + { + return briefSummary; + } + + public ExpectedErrorResponse setBriefSummary(String briefSummary) + { + this.briefSummary = briefSummary; + return this; + } + + public String getStackTrace() + { + return stackTrace; + } + + public ExpectedErrorResponse setStackTrace(String stackTrace) + { + this.stackTrace = stackTrace; + return this; + } + + public Map getAdditionalState() + { + return additionalState; + } + + public ExpectedErrorResponse setAdditionalState(Map additionalState) + { + this.additionalState = additionalState; + return this; + } + + public String getDescriptionURL() + { + return descriptionURL; + } + + public ExpectedErrorResponse setDescriptionURL(String descriptionURL) + { + this.descriptionURL = descriptionURL; + return this; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(250); + sb.append("ExpectedErrorResponse [errorKey='").append(errorKey) + .append(", statusCode=").append(statusCode) + .append(", briefSummary='").append(briefSummary) + .append(", stackTrace='").append(stackTrace) + .append(", additionalState=").append(additionalState) + .append(", descriptionURL='").append(descriptionURL) + .append(']'); + return sb.toString(); + } + } } diff --git a/source/test-java/org/alfresco/rest/api/tests/util/RestApiUtil.java b/source/test-java/org/alfresco/rest/api/tests/util/RestApiUtil.java index 15ceb9df75..b7a08ee468 100644 --- a/source/test-java/org/alfresco/rest/api/tests/util/RestApiUtil.java +++ b/source/test-java/org/alfresco/rest/api/tests/util/RestApiUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2015 Alfresco Software Limited. + * Copyright (C) 2005-2016 Alfresco Software Limited. * * This file is part of Alfresco * @@ -127,6 +127,18 @@ public class RestApiUtil return pojoModel; } + /** + * Parses the alfresco REST API error response. + * + * @param jsonObject the {@code JSONObject} derived from the response + * @return ExpectedErrorResponse the error object + * @throws Exception + */ + public static PublicApiClient.ExpectedErrorResponse parseErrorResponse(JSONObject jsonObject) throws Exception + { + return parsePojo("error", jsonObject, PublicApiClient.ExpectedErrorResponse.class); + } + /** * Converts the POJO which represents the JSON payload into a JSON string */ diff --git a/source/test-resources/publicapi/upload/quick.jpg b/source/test-resources/publicapi/upload/quick.jpg new file mode 100644 index 0000000000..08473b8e8b Binary files /dev/null and b/source/test-resources/publicapi/upload/quick.jpg differ