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 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 e0c103c0d1..6bfe478b71 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 @@ -33,6 +33,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; @@ -2009,4 +2010,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 dd610f10ba..514d1a9287 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,14 +1,14 @@ /* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * * 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 @@ -134,6 +134,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