diff --git a/.travis.yml b/.travis.yml index d00f0c84ea..e0bd953f54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,7 @@ branches: only: - master - /support\/.*/ + - /feature\/.*/ stages: - test diff --git a/src/main/java/org/alfresco/rest/api/Renditions.java b/src/main/java/org/alfresco/rest/api/Renditions.java index c281bc9162..0fc1a7d91c 100644 --- a/src/main/java/org/alfresco/rest/api/Renditions.java +++ b/src/main/java/org/alfresco/rest/api/Renditions.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2020 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -39,36 +39,61 @@ import java.util.List; /** * Renditions API * - * @author Jamal Kaabi-Mofrad + * @author Jamal Kaabi-Mofrad, janv */ public interface Renditions { String PARAM_STATUS = "status"; + String PARAM_ATTACHMENT = "attachment"; + String PARAM_PLACEHOLDER = "placeholder"; + /** * Lists all available renditions includes those that have been created and those that are yet to be created. * - * @param nodeRef + * @param nodeRef the source/live nodeRef * @param parameters the {@link Parameters} object to get the parameters passed into the request * @return the rendition results */ CollectionWithPagingInfo getRenditions(NodeRef nodeRef, Parameters parameters); + /** + * Lists all available renditions includes those that have been created and those that are yet to be created. + * + * @param nodeRef the source/live nodeRef + * @param versionId the version id (aka version label) + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return the rendition results + */ + CollectionWithPagingInfo getRenditions(NodeRef nodeRef, String versionId, Parameters parameters); + /** * Gets information about a rendition of a node in the repository. * If there is no rendition, then returns the available/registered rendition. * - * @param nodeRef + * @param nodeRef the source nodeRef, ie. live node * @param renditionId the rendition id * @param parameters the {@link Parameters} object to get the parameters passed into the request * @return the {@link Rendition} object */ Rendition getRendition(NodeRef nodeRef, String renditionId, Parameters parameters); + /** + * Gets information about a rendition of a node in the repository. + * If there is no rendition, then returns the available/registered rendition. + * + * @param nodeRef the source nodeRef, ie. live node + * @param versionId the version id (aka version label) + * @param renditionId the rendition id + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return the {@link Rendition} object + */ + Rendition getRendition(NodeRef nodeRef, String versionId, String renditionId, Parameters parameters); + /** * Creates a rendition for the given node asynchronously. * - * @param nodeRef + * @param nodeRef the source nodeRef, ie. live node * @param rendition the {@link Rendition} request * @param parameters the {@link Parameters} object to get the parameters passed into the request */ @@ -77,18 +102,29 @@ public interface Renditions /** * Creates a rendition for the given node - either async r sync * - * @param nodeRef - * @param rendition + * @param nodeRef the source nodeRef, ie. live node + * @param rendition the {@link Rendition} request * @param executeAsync * @param parameters */ void createRendition(NodeRef nodeRef, Rendition rendition, boolean executeAsync, Parameters parameters); + /** + * Creates a rendition for the given node - either async r sync + * + * @param nodeRef the source nodeRef, ie. live node + * @param versionId the version id (aka version label) + * @param rendition the {@link Rendition} request + * @param executeAsync + * @param parameters + */ + void createRendition(NodeRef nodeRef, String versionId, Rendition rendition, boolean executeAsync, Parameters parameters); + /** * Creates renditions that don't already exist for the given node asynchronously. * - * @param nodeRef - * @param renditions the {@link Rendition} request + * @param nodeRef the source nodeRef, ie. live node + * @param renditions the list of {@link Rendition} requests * @param parameters the {@link Parameters} object to get the parameters passed into the request * @throws NotFoundException if any of the rendition id do not exist. * @throws ConstraintViolatedException if all of the renditions already exist. @@ -97,23 +133,58 @@ public interface Renditions throws NotFoundException, ConstraintViolatedException; /** - * Downloads rendition. + * Creates renditions that don't already exist for the given node asynchronously. * - * @param sourceNodeRef the source nodeRef - * @param renditionId the rendition id - * @param parameters the {@link Parameters} object to get the parameters passed into the request - * @return the rendition stream + * @param nodeRef the source nodeRef, ie. live node + * @param versionId the version id (aka version label) + * @param renditions the list of {@link Rendition} requests + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @throws NotFoundException if any of the rendition id do not exist. + * @throws ConstraintViolatedException if all of the renditions already exist. */ - BinaryResource getContent(NodeRef sourceNodeRef, String renditionId, Parameters parameters); + void createRenditions(NodeRef nodeRef, String versionId, List renditions, Parameters parameters) + throws NotFoundException, ConstraintViolatedException; /** * Downloads rendition. * - * @param sourceNodeRef the source nodeRef - * @param renditionId the rendition id - * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @param nodeRef the source nodeRef, ie. live node + * @param renditionId the rendition id + * @param parameters the {@link Parameters} object to get the parameters passed into the request * @return the rendition stream */ - BinaryResource getContentNoValidation(NodeRef sourceNodeRef, String renditionId, Parameters parameters); + BinaryResource getContent(NodeRef nodeRef, String renditionId, Parameters parameters); + + /** + * Downloads rendition. + * + * @param nodeRef the source nodeRef, ie. live node + * @param versionId the version id (aka version label) + * @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(NodeRef nodeRef, String versionId, String renditionId, Parameters parameters); + + /** + * Downloads rendition. + * + * @param nodeRef the source nodeRef, ie. live node + * @param renditionId the rendition id + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return the rendition stream + */ + BinaryResource getContentNoValidation(NodeRef nodeRef, String renditionId, Parameters parameters); + + /** + * Downloads rendition. + * + * @param nodeRef the source nodeRef, ie. live node + * @param versionId the version id (aka version label) + * @param renditionId the rendition id + * @param parameters the {@link Parameters} object to get the parameters passed into the request + * @return the rendition stream + */ + BinaryResource getContentNoValidation(NodeRef nodeRef, String versionId, String renditionId, Parameters parameters); } diff --git a/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java b/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java index 646d600c10..0dfaf070fb 100644 --- a/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java +++ b/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2020 Alfresco Software LimitedP * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -34,6 +34,7 @@ import org.alfresco.repo.rendition2.RenditionDefinitionRegistry2; import org.alfresco.repo.rendition2.RenditionService2; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.thumbnail.script.ScriptThumbnailService; +import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.rest.antlr.WhereClauseParser; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.Renditions; @@ -64,6 +65,10 @@ import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionDoesNotExistException; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.QName; import org.alfresco.util.PropertyCheck; import org.alfresco.util.TempFileProvider; @@ -87,7 +92,7 @@ import java.util.StringJoiner; import java.util.TreeMap; /** - * @author Jamal Kaabi-Mofrad + * @author Jamal Kaabi-Mofrad, janv */ public class RenditionsImpl implements Renditions, ResourceLoaderAware { @@ -104,6 +109,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware private TenantService tenantService; private RenditionService2 renditionService2; private RenditionsDataCollector renditionsDataCollector; + private VersionService versionService; public void setNodes(Nodes nodes) { @@ -151,14 +157,21 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware PropertyCheck.mandatory(this, "renditionsDataCollector", renditionsDataCollector); this.nodeService = serviceRegistry.getNodeService(); + this.versionService = serviceRegistry.getVersionService(); this.mimetypeService = serviceRegistry.getMimetypeService(); } @Override public CollectionWithPagingInfo getRenditions(NodeRef nodeRef, Parameters parameters) { - final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId()); - ContentData contentData = getContentData(nodeRef, true); + return getRenditions(nodeRef, null, parameters); + } + + @Override + public CollectionWithPagingInfo getRenditions(NodeRef nodeRef, String versionLabelId, Parameters parameters) + { + final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionLabelId, parameters); + ContentData contentData = getContentData(validatedNodeRef, true); String sourceMimetype = contentData.getMimetype(); boolean includeCreated = true; @@ -214,7 +227,13 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware @Override public Rendition getRendition(NodeRef nodeRef, String renditionId, Parameters parameters) { - final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId()); + return getRendition(nodeRef, null, renditionId, parameters); + } + + @Override + public Rendition getRendition(NodeRef nodeRef, String versionLabelId, String renditionId, Parameters parameters) + { + final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionLabelId, parameters); NodeRef renditionNodeRef = getRenditionByName(validatedNodeRef, renditionId, parameters); boolean includeNotCreated = true; String status = getStatus(parameters); @@ -272,6 +291,12 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware @Override public void createRendition(NodeRef nodeRef, Rendition rendition, boolean executeAsync, Parameters parameters) + { + createRendition(nodeRef, null, rendition, executeAsync, parameters); + } + + @Override + public void createRendition(NodeRef nodeRef, String versionLabelId, Rendition rendition, boolean executeAsync, Parameters parameters) { // If thumbnail generation has been configured off, then don't bother. if (!renditionService2.isEnabled()) @@ -279,7 +304,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware throw new DisabledServiceException("Rendition generation has been disabled."); } - final NodeRef sourceNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId()); + final NodeRef sourceNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionLabelId, parameters); final NodeRef renditionNodeRef = getRenditionByName(sourceNodeRef, rendition.getId(), parameters); if (renditionNodeRef != null) { @@ -307,6 +332,13 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware @Override public void createRenditions(NodeRef nodeRef, List renditions, Parameters parameters) throws NotFoundException, ConstraintViolatedException + { + createRenditions(nodeRef, null, renditions, parameters); + } + + @Override + public void createRenditions(NodeRef nodeRef, String versionLabelId, List renditions, Parameters parameters) + throws NotFoundException, ConstraintViolatedException { if (renditions.isEmpty()) { @@ -318,7 +350,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware throw new DisabledServiceException("Rendition generation has been disabled."); } - final NodeRef sourceNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId()); + final NodeRef sourceNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionLabelId, parameters); RenditionDefinitionRegistry2 renditionDefinitionRegistry2 = renditionService2.getRenditionDefinitionRegistry2(); // So that POST /nodes/{nodeId}/renditions can specify rendition names as a comma separated list just like @@ -349,7 +381,6 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware List renditionNamesToCreate = new ArrayList<>(); for (String renditionName : renditionNames) { - System.err.println("renditionName="+renditionName); if (renditionName == null) { throw new IllegalArgumentException(("Null rendition name supplied")); // 400 @@ -417,18 +448,36 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware @Override public BinaryResource getContent(NodeRef nodeRef, String renditionId, Parameters parameters) { - final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId()); - return getContentNoValidation(validatedNodeRef, renditionId, parameters); + return getContent(nodeRef, null, renditionId, parameters); } @Override - public BinaryResource getContentNoValidation(NodeRef sourceNodeRef, String renditionId, Parameters parameters) + public BinaryResource getContent(NodeRef nodeRef, String versionLabelId, String renditionId, Parameters parameters) { - NodeRef renditionNodeRef = getRenditionByName(sourceNodeRef, renditionId, parameters); + final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionLabelId, parameters); + return getContentImpl(validatedNodeRef, renditionId, parameters); + } + + @Override + public BinaryResource getContentNoValidation(NodeRef nodeRef, String renditionId, Parameters parameters) + { + return getContentNoValidation(nodeRef, null, renditionId, parameters); + } + + @Override + public BinaryResource getContentNoValidation(NodeRef nodeRef, String versionLabelId, String renditionId, Parameters parameters) + { + nodeRef = findVersionIfApplicable(nodeRef, versionLabelId); + return getContentImpl(nodeRef, renditionId, parameters); + } + + private BinaryResource getContentImpl(NodeRef nodeRef, String renditionId, Parameters parameters) + { + NodeRef renditionNodeRef = getRenditionByName(nodeRef, renditionId, parameters); // By default set attachment header (with rendition Id) unless attachment=false boolean attach = true; - String attachment = parameters.getParameter("attachment"); + String attachment = parameters.getParameter(PARAM_ATTACHMENT); if (attachment != null) { attach = Boolean.valueOf(attachment); @@ -437,7 +486,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware if (renditionNodeRef == null) { - boolean isPlaceholder = Boolean.valueOf(parameters.getParameter("placeholder")); + boolean isPlaceholder = Boolean.valueOf(parameters.getParameter(PARAM_PLACEHOLDER)); if (!isPlaceholder) { throw new NotFoundException("Thumbnail was not found for [" + renditionId + ']'); @@ -445,7 +494,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware String sourceNodeMimeType = null; try { - sourceNodeMimeType = (sourceNodeRef != null ? getMimeType(sourceNodeRef) : null); + sourceNodeMimeType = (nodeRef != null ? getMimeType(nodeRef) : null); } catch (InvalidArgumentException e) { @@ -514,23 +563,25 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware protected NodeRef getRenditionByName(NodeRef nodeRef, String renditionId, Parameters parameters) { - if (nodeRef == null) + if (nodeRef != null) { - return null; + if (StringUtils.isEmpty(renditionId)) + { + throw new InvalidArgumentException("renditionId can't be null or empty."); + } + + ChildAssociationRef nodeRefRendition = renditionService2.getRenditionByName(nodeRef, renditionId); + if (nodeRefRendition != null) + { + ContentData contentData = getContentData(nodeRefRendition.getChildRef(), false); + if (contentData != null) + { + return tenantService.getName(nodeRef, nodeRefRendition.getChildRef()); + } + } } - if (StringUtils.isEmpty(renditionId)) - { - throw new InvalidArgumentException("renditionId can't be null or empty."); - } - - ChildAssociationRef nodeRefRendition = renditionService2.getRenditionByName(nodeRef, renditionId); - if (nodeRefRendition == null) - { - return null; - } - - return tenantService.getName(nodeRef, nodeRefRendition.getChildRef()); + return null; } protected Rendition toApiRendition(NodeRef renditionNodeRef) @@ -570,16 +621,42 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware return apiRendition; } - public NodeRef validateNode(StoreRef storeRef, String nodeId) + private NodeRef validateNode(StoreRef storeRef, final String nodeId, String versionLabelId, Parameters parameters) { if (nodeId == null) { throw new InvalidArgumentException("Missing nodeId"); } - final NodeRef nodeRef = nodes.validateNode(storeRef, nodeId); + NodeRef nodeRef = nodes.validateNode(storeRef, nodeId); // check if the node represents a file isContentFile(nodeRef); + + nodeRef = findVersionIfApplicable(nodeRef, versionLabelId); + + return nodeRef; + } + + private NodeRef findVersionIfApplicable(NodeRef nodeRef, String versionLabelId) + { + if (versionLabelId != null) + { + nodeRef = nodes.validateOrLookupNode(nodeRef.getId(), null); + VersionHistory vh = versionService.getVersionHistory(nodeRef); + if (vh != null) + { + try + { + Version v = vh.getVersion(versionLabelId); + nodeRef = VersionUtil.convertNodeRef(v.getFrozenStateNodeRef()); + } + catch (VersionDoesNotExistException vdne) + { + throw new NotFoundException("Couldn't find version: [" + nodeRef.getId() + ", " + versionLabelId + "]"); + } + } + } + return nodeRef; } diff --git a/src/main/java/org/alfresco/rest/api/nodes/NodeVersionRenditionsRelation.java b/src/main/java/org/alfresco/rest/api/nodes/NodeVersionRenditionsRelation.java new file mode 100644 index 0000000000..37171b676c --- /dev/null +++ b/src/main/java/org/alfresco/rest/api/nodes/NodeVersionRenditionsRelation.java @@ -0,0 +1,117 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2020 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 + * (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 . + * #L% + */ + +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.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.PropertyCheck; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.extensions.webscripts.Status; + +import java.util.List; + +/** + * + * Node version renditions + * + * - GET /nodes/{nodeId}/versions/{versionId}/renditions + * - POST /nodes/{nodeId}/versions/{versionId}/renditions + * - GET /nodes/{nodeId}/versions/{versionId}/renditions/{renditionId} + * - GET /nodes/{nodeId}/versions/{versionId}/renditions/{renditionId}/content + * + * @author janv + */ +@RelationshipResource(name = "renditions", entityResource = NodeVersionsRelation.class, title = "Node version renditions") +public class NodeVersionRenditionsRelation implements RelationshipResourceAction.Read, + RelationshipResourceAction.ReadById, + RelationshipResourceAction.Create, + RelationshipResourceBinaryAction.Read, + InitializingBean +{ + private Renditions renditions; + + public void setRenditions(Renditions renditions) + { + this.renditions = renditions; + } + + @Override + public void afterPropertiesSet() throws Exception + { + PropertyCheck.mandatory(this, "renditions", renditions); + } + + @Override + public CollectionWithPagingInfo readAll(String nodeId, Parameters parameters) + { + String versionId = parameters.getRelationshipId(); + + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId); + return renditions.getRenditions(nodeRef, versionId, parameters); + } + + @Override + public Rendition readById(String nodeId, String versionId, Parameters parameters) + { + String renditionId = parameters.getRelationship2Id(); + + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId); + return renditions.getRendition(nodeRef, versionId, renditionId, parameters); + } + + @WebApiDescription(title = "Create rendition", successStatus = Status.STATUS_ACCEPTED) + @Override + public List create(String nodeId, List entity, Parameters parameters) + { + String versionId = parameters.getRelationshipId(); + + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId); + renditions.createRenditions(nodeRef, versionId, entity, parameters); + return null; + } + + @WebApiDescription(title = "Download rendition", description = "Download rendition") + @BinaryProperties({ "content" }) + @Override + public BinaryResource readProperty(String nodeId, String versionId, Parameters parameters) + { + String renditionId = parameters.getRelationship2Id(); + + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId); + return renditions.getContent(nodeRef, versionId, renditionId, parameters); + } +} diff --git a/src/main/resources/alfresco/public-rest-context.xml b/src/main/resources/alfresco/public-rest-context.xml index bd4489b19d..2e22ddb029 100644 --- a/src/main/resources/alfresco/public-rest-context.xml +++ b/src/main/resources/alfresco/public-rest-context.xml @@ -1389,6 +1389,10 @@ + + + + diff --git a/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java b/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java index e5ccfe9dce..4938ecf42e 100644 --- a/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java +++ b/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2020 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,12 +25,14 @@ */ package org.alfresco.rest.api.tests; +import org.alfresco.rest.api.tests.client.PublicApiHttpClient; import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsString; import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsStringNonNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.tenant.TenantService; @@ -76,6 +78,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.HashMap; /** * Generic methods for calling the Api (originally taken and adapted from BaseCustomModelApiTest) @@ -94,7 +97,9 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi protected static final String URL_NODES = "nodes"; - private static final String URL_RENDITIONS = "renditions"; + protected static final String URL_RENDITIONS = "renditions"; + protected static final String URL_VERSIONS = "versions"; + private static final String URL_CHILDREN = "children"; private static final String URL_CONTENT = "content"; @@ -841,22 +846,112 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi return RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); } + /** + * This test helper method uses "update binary content" to create one or more new versions. The file must already exist. + * + * @param userId + * @param contentNodeId + * @param cnt + * @param textContentPrefix + * @param verCnt + * @param majorVersion + * @param currentVersionLabel + * @return + * @throws Exception + */ + protected String updateFileVersions(String userId, String contentNodeId, int cnt, + String textContentPrefix, int verCnt, + Boolean majorVersion, String currentVersionLabel) throws Exception + { + String[] parts = currentVersionLabel.split("\\."); + + int majorVer = new Integer(parts[0]).intValue(); + int minorVer = new Integer(parts[1]).intValue(); + + Map params = new HashMap<>(); + params.put(Nodes.PARAM_OVERWRITE, "true"); + + if (majorVersion != null) + { + params.put(Nodes.PARAM_VERSION_MAJOR, majorVersion.toString()); + } + else + { + majorVersion = false; + } + + + if (majorVersion) + { + minorVer = 0; + } + + for (int i = 1; i <= cnt; i++) + { + if (majorVersion) + { + majorVer++; + } + else + { + minorVer++; + } + + verCnt++; + + params.put("comment", "my version " + verCnt); + + String textContent = textContentPrefix + verCnt; + + currentVersionLabel = majorVer + "." + minorVer; + + // Update + ByteArrayInputStream inputStream = new ByteArrayInputStream(textContent.getBytes()); + File txtFile = TempFileProvider.createTempFile(inputStream, getClass().getSimpleName(), ".txt"); + PublicApiHttpClient.BinaryPayload payload = new PublicApiHttpClient.BinaryPayload(txtFile); + + HttpResponse response = putBinary(getNodeContentUrl(contentNodeId), payload, null, params, 200); + Node nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); + + assertTrue(nodeResp.getAspectNames().contains("cm:versionable")); + assertNotNull(nodeResp.getProperties()); + assertEquals(currentVersionLabel, nodeResp.getProperties().get("cm:versionLabel")); + assertEquals((majorVersion ? "MAJOR" : "MINOR"), nodeResp.getProperties().get("cm:versionType")); + + // double-check - get version node info + response = getSingle(getNodeVersionsUrl(contentNodeId), currentVersionLabel, null, 200); + nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); + assertEquals(currentVersionLabel, nodeResp.getProperties().get("cm:versionLabel")); + assertEquals((majorVersion ? "MAJOR" : "MINOR"), nodeResp.getProperties().get("cm:versionType")); + } + + return currentVersionLabel; + } + protected static final long PAUSE_TIME = 5000; //millisecond protected static final int MAX_RETRY = 20; - protected Rendition waitAndGetRendition(String sourceNodeId, String renditionId) throws Exception + protected Rendition waitAndGetRendition(String sourceNodeId, String versionId, String renditionId) throws Exception { - return waitAndGetRendition(sourceNodeId, renditionId, MAX_RETRY, PAUSE_TIME); + return waitAndGetRendition(sourceNodeId, versionId, renditionId, MAX_RETRY, PAUSE_TIME); } - protected Rendition waitAndGetRendition(String sourceNodeId, String renditionId, int maxRetry, long pauseTime) throws Exception + protected Rendition waitAndGetRendition(String sourceNodeId, String versionId, String renditionId, int maxRetry, long pauseTime) throws Exception { int retryCount = 0; while (retryCount < maxRetry) { try { - HttpResponse response = getSingle(getNodeRenditionsUrl(sourceNodeId), renditionId, 200); + HttpResponse response; + if ((versionId != null) && (! versionId.isEmpty())) + { + response = getSingle(getNodeVersionRenditionsUrl(sourceNodeId, versionId), renditionId, 200); + } + else + { + response = getSingle(getNodeRenditionsUrl(sourceNodeId), renditionId, 200); + } Rendition rendition = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Rendition.class); assertNotNull(rendition); assertEquals(Rendition.RenditionStatus.CREATED, rendition.getStatus()); @@ -877,6 +972,11 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi } protected Rendition createAndGetRendition(String sourceNodeId, String renditionId) throws Exception + { + return createAndGetRendition(sourceNodeId, null, renditionId); + } + + protected Rendition createAndGetRendition(String sourceNodeId, String versionId, String renditionId) throws Exception { Rendition renditionRequest = new Rendition(); renditionRequest.setId(renditionId); @@ -886,8 +986,16 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi { try { - HttpResponse res = post(getNodeRenditionsUrl(sourceNodeId), toJsonAsString(renditionRequest), 202); - assertNull(res.getJsonResponse()); + HttpResponse response; + if ((versionId != null) && (! versionId.isEmpty())) + { + response = post(getNodeVersionRenditionsUrl(sourceNodeId, versionId), toJsonAsString(renditionRequest), 202); + } + else + { + response = post(getNodeRenditionsUrl(sourceNodeId), toJsonAsString(renditionRequest), 202); + } + assertNull(response.getJsonResponse()); break; } catch (AssertionError ex) @@ -901,7 +1009,7 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi } } - return waitAndGetRendition(sourceNodeId, renditionId); + return waitAndGetRendition(sourceNodeId, versionId, renditionId); } protected String getNodeRenditionsUrl(String nodeId) @@ -909,6 +1017,16 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi return URL_NODES + "/" + nodeId + "/" + URL_RENDITIONS; } + protected String getNodeVersionsUrl(String nodeId) + { + return URL_NODES + "/" + nodeId + "/" + URL_VERSIONS; + } + + protected String getNodeVersionRenditionsUrl(String nodeId, String versionId) + { + return URL_NODES + "/" + nodeId + "/" + URL_VERSIONS + "/" + versionId + "/" + URL_RENDITIONS; + } + protected String getNodeChildrenUrl(String nodeId) { return URL_NODES + "/" + nodeId + "/" + URL_CHILDREN; diff --git a/src/test/java/org/alfresco/rest/api/tests/NodeVersionRenditionsApiTest.java b/src/test/java/org/alfresco/rest/api/tests/NodeVersionRenditionsApiTest.java new file mode 100644 index 0000000000..13fb237af6 --- /dev/null +++ b/src/test/java/org/alfresco/rest/api/tests/NodeVersionRenditionsApiTest.java @@ -0,0 +1,267 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2020 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 + * (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 . + * #L% + */ +package org.alfresco.rest.api.tests; + +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.rest.AbstractSingleNetworkSiteTest; +import org.alfresco.rest.api.Nodes; +import org.alfresco.rest.api.tests.client.HttpResponse; +import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; +import org.alfresco.rest.api.tests.client.PublicApiHttpClient; +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.Node; +import org.alfresco.rest.api.tests.client.data.Rendition; +import org.alfresco.rest.api.tests.util.RestApiUtil; +import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsString; +import org.alfresco.util.TempFileProvider; +import static org.junit.Assert.*; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.util.*; + +/** + * V1 REST API tests for Node Version Renditions + * + * {@literal :/alfresco/api//public/alfresco/versions/1/nodes/{nodeId}/versions/{versionId}/renditions + * + * @author janv + */ +public class NodeVersionRenditionsApiTest extends AbstractSingleNetworkSiteTest +{ + private final static long DELAY_IN_MS = 500; + + /** + * Upload some versions and then create and retrieve version renditions + * + *

POST:

+ * @literal :/alfresco/api/-default-/public/alfresco/versions/1/nodes//versions//renditions} + * + *

GET:

+ * {@literal :/alfresco/api//public/alfresco/versions/1/nodes//versions//renditions} + * {@literal :/alfresco/api//public/alfresco/versions/1/nodes//versions//renditions/} + * {@literal :/alfresco/api//public/alfresco/versions/1/nodes//versions//renditions//content} + * + * @throws Exception + */ + @Test + public void testUpFileVersionRenditions() throws Exception + { + setRequestContext(user1); + + String myFolderNodeId = getMyNodeId(); + + // create folder + String f1Id = createFolder(myFolderNodeId, "f1").getId(); + + try + { + int verCnt = 1; + int cnt = 1; + String versionLabel = "1.0"; + + String textContentSuffix = "Amazingly few discotheques provide jukeboxes "; + String contentName = "content-2-" + System.currentTimeMillis(); + String content = textContentSuffix + cnt; + + Map params = new HashMap<>(); + params.put("majorVersion", "true"); + + // create a new file + Document documentResp = createTextFile(f1Id, contentName, content, "UTF-8", params); + String docId = documentResp.getId(); + assertTrue(documentResp.getAspectNames().contains("cm:versionable")); + assertNotNull(documentResp.getProperties()); + assertEquals(versionLabel, documentResp.getProperties().get("cm:versionLabel")); + + cnt = 2; + versionLabel = updateFileVersions(user1, docId, cnt, textContentSuffix, verCnt, true, versionLabel); + verCnt = verCnt+cnt; + + assertEquals("3.0", versionLabel); + assertEquals(3, verCnt); + + // check version history count + HttpResponse response = getAll(getNodeVersionsUrl(docId), null, null, 200); + List nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class); + assertEquals(verCnt, nodes.size()); + + // pause briefly + Thread.sleep(DELAY_IN_MS); + + checkCreateAndGetVersionRendition(docId, "1.0", "doclib"); + checkCreateAndGetVersionRendition(docId, "3.0", "doclib"); + checkCreateAndGetVersionRendition(docId, "2.0", "doclib"); + + // also live node + checkCreateAndGetVersionRendition(docId, null, "doclib"); + } + finally + { + // some cleanup + setRequestContext(user1); + deleteNode(f1Id, true, 204); + } + } + + @Test + public void testNegative() throws Exception + { + AuthenticationUtil.clearCurrentSecurityContext(); + setRequestContext(null); + + // -ve: not authenticated + getAll(getNodeVersionRenditionsUrl("dummy", "1.0"), null, 401); + getSingle(getNodeVersionRenditionsUrl("dummy", "1.0"), ("doclib"), null, 401); + getSingle(getNodeVersionRenditionsUrl("dummy", "1.0"), ("doclib/content"), null, 401); + + Rendition renditionRequest = new Rendition(); + renditionRequest.setId("doclib"); + post(getNodeVersionRenditionsUrl("dummy", "1.0"), toJsonAsString(renditionRequest), 401); + + setRequestContext(user1); + + String myFolderNodeId = getMyNodeId(); + + // create folder + String f1Id = createFolder(myFolderNodeId, "f1").getId(); + + try + { + int verCnt = 1; + int cnt = 1; + String versionLabel = "1.0"; + + String textContentSuffix = "Amazingly few discotheques provide jukeboxes "; + String contentName = "content-2-" + System.currentTimeMillis(); + String content = textContentSuffix + cnt; + + // create a new file + Document documentResp = createTextFile(f1Id, contentName, content, "UTF-8", null); + String docId = documentResp.getId(); + + getAll(getNodeVersionRenditionsUrl(docId, "1.0"), null, 200); + checkCreateAndGetVersionRendition(docId, "1.0", "doclib"); + + + // -ve: rendition already exits (409) + renditionRequest.setId("doclib"); + post(getNodeVersionRenditionsUrl(docId, "1.0"), toJsonAsString(renditionRequest), 409); + + // -ve: no such rendition (404) + getSingle(getNodeVersionRenditionsUrl("dummy", "1.0"), ("dummy"), null, 404); + getSingle(getNodeVersionRenditionsUrl("dummy", "1.0"), ("dummy/content"), null, 404); + + renditionRequest.setId("dummy"); + post(getNodeVersionRenditionsUrl("dummy", "1.0"), toJsonAsString(renditionRequest), 404); + + + // -ve: no such version (404) + getAll(getNodeVersionRenditionsUrl(docId, "4.0"), null, 404); + getSingle(getNodeVersionRenditionsUrl(docId, "4.0"), ("doclib"), null, 404); + getSingle(getNodeVersionRenditionsUrl(docId, "4.0"), ("doclib/content"), null, 404); + + renditionRequest.setId("doclib"); + post(getNodeVersionRenditionsUrl(docId, "4.0"), toJsonAsString(renditionRequest), 404); + + + // -ve: no such file (404) + getAll(getNodeVersionRenditionsUrl("dummy", "1.0"), null, 404); + getSingle(getNodeVersionRenditionsUrl("dummy", "1.0"), ("doclib"), null, 404); + getSingle(getNodeVersionRenditionsUrl("dummy", "1.0"), ("doclib/content"), null, 404); + + renditionRequest.setId("doclib"); + post(getNodeVersionRenditionsUrl("dummy", "1.0"), toJsonAsString(renditionRequest), 404); + } + finally + { + // some cleanup + setRequestContext(user1); + deleteNode(f1Id, true, 204); + } + } + + private void checkCreateAndGetVersionRendition(String docId, String versionId, String renditionId) throws Exception + { + String getRenditionsUrl; + if ((versionId != null) && (! versionId.isEmpty())) + { + getRenditionsUrl = getNodeVersionRenditionsUrl(docId, versionId); + } + else + { + getRenditionsUrl = getNodeRenditionsUrl(docId); + } + + // List renditions for version + Paging paging = getPaging(0, 50); + HttpResponse response = getAll(getRenditionsUrl, paging, 200); + List renditions = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Rendition.class); + assertTrue(renditions.size() >= 5); + + for (Rendition rendition : renditions) + { + assertEquals(Rendition.RenditionStatus.NOT_CREATED, rendition.getStatus()); + } + + // Get rendition (not created yet) information for node + response = getSingle(getRenditionsUrl, renditionId, 200); + Rendition rendition = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Rendition.class); + assertNotNull(rendition); + assertEquals(Rendition.RenditionStatus.NOT_CREATED, rendition.getStatus()); + + // -ve test: try to download non-existent rendition (and no placeholder) + Map params = new HashMap<>(); + params.put("placeholder", "false"); + getSingle(getRenditionsUrl, (renditionId+"/content"), params, 404); + + // +ve test: download placeholder instead + params = new HashMap<>(); + params.put("placeholder", "true"); + response = getSingle(getRenditionsUrl, (renditionId+"/content"), params, 200); + assertNotNull(response.getResponseAsBytes()); + + // Create and get version rendition + rendition = createAndGetRendition(docId, versionId, renditionId); + assertNotNull(rendition); + assertEquals(Rendition.RenditionStatus.CREATED, rendition.getStatus()); + ContentInfo contentInfo = rendition.getContent(); + assertNotNull(contentInfo); + assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG, contentInfo.getMimeType()); + assertEquals("PNG Image", contentInfo.getMimeTypeName()); + assertNotNull(contentInfo.getEncoding()); + assertTrue(contentInfo.getSizeInBytes() > 0); + } + + @Override + public String getScope() + { + return "public"; + } +} diff --git a/src/test/java/org/alfresco/rest/api/tests/NodeVersionsApiTest.java b/src/test/java/org/alfresco/rest/api/tests/NodeVersionsApiTest.java index cb48dfcf29..3091136d2b 100644 --- a/src/test/java/org/alfresco/rest/api/tests/NodeVersionsApiTest.java +++ b/src/test/java/org/alfresco/rest/api/tests/NodeVersionsApiTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2020 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -61,18 +61,12 @@ import static org.junit.Assert.*; public class NodeVersionsApiTest extends AbstractSingleNetworkSiteTest { private static final String URL_DELETED_NODES = "deleted-nodes"; - private static final String URL_VERSIONS = "versions"; protected String getNodeVersionRevertUrl(String nodeId, String versionId) { return getNodeVersionsUrl(nodeId) + "/" + versionId + "/revert"; } - protected String getNodeVersionsUrl(String nodeId) - { - return URL_NODES + "/" + nodeId + "/" + URL_VERSIONS; - } - /** * Test version creation when uploading files (via multi-part/form-data with overwrite=true) * @@ -561,89 +555,6 @@ public class NodeVersionsApiTest extends AbstractSingleNetworkSiteTest return new Pair(currentVersionLabel, docId); } - /** - * This test helper method uses "update binary content" to create one or more new versions. The file must already exist. - * - * @param userId - * @param contentNodeId - * @param cnt - * @param textContentPrefix - * @param verCnt - * @param majorVersion - * @param currentVersionLabel - * @return - * @throws Exception - */ - private String updateFileVersions(String userId, String contentNodeId, int cnt, - String textContentPrefix, int verCnt, - Boolean majorVersion, String currentVersionLabel) throws Exception - { - String[] parts = currentVersionLabel.split("\\."); - - int majorVer = new Integer(parts[0]).intValue(); - int minorVer = new Integer(parts[1]).intValue(); - - Map params = new HashMap<>(); - params.put(Nodes.PARAM_OVERWRITE, "true"); - - if (majorVersion != null) - { - params.put(Nodes.PARAM_VERSION_MAJOR, majorVersion.toString()); - } - else - { - majorVersion = false; - } - - - if (majorVersion) - { - minorVer = 0; - } - - for (int i = 1; i <= cnt; i++) - { - if (majorVersion) - { - majorVer++; - } - else - { - minorVer++; - } - - verCnt++; - - params.put("comment", "my version " + verCnt); - - String textContent = textContentPrefix + verCnt; - - currentVersionLabel = majorVer + "." + minorVer; - - // Update - ByteArrayInputStream inputStream = new ByteArrayInputStream(textContent.getBytes()); - File txtFile = TempFileProvider.createTempFile(inputStream, getClass().getSimpleName(), ".txt"); - PublicApiHttpClient.BinaryPayload payload = new PublicApiHttpClient.BinaryPayload(txtFile); - - HttpResponse response = putBinary(getNodeContentUrl(contentNodeId), payload, null, params, 200); - Node nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); - - assertTrue(nodeResp.getAspectNames().contains("cm:versionable")); - assertNotNull(nodeResp.getProperties()); - assertEquals(currentVersionLabel, nodeResp.getProperties().get("cm:versionLabel")); - assertEquals((majorVersion ? "MAJOR" : "MINOR"), nodeResp.getProperties().get("cm:versionType")); - - // double-check - get version node info - response = getSingle(getNodeVersionsUrl(contentNodeId), currentVersionLabel, null, 200); - nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); - assertEquals(currentVersionLabel, nodeResp.getProperties().get("cm:versionLabel")); - assertEquals((majorVersion ? "MAJOR" : "MINOR"), nodeResp.getProperties().get("cm:versionType")); - } - - return currentVersionLabel; - } - - /** * Tests api when uploading a file and then updating with a new version * diff --git a/src/test/java/org/alfresco/rest/api/tests/RenditionsTest.java b/src/test/java/org/alfresco/rest/api/tests/RenditionsTest.java index 44d2e9e4dd..b596a37bc2 100644 --- a/src/test/java/org/alfresco/rest/api/tests/RenditionsTest.java +++ b/src/test/java/org/alfresco/rest/api/tests/RenditionsTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Remote API * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2020 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -548,7 +548,7 @@ public class RenditionsTest extends AbstractBaseApiTest String contentNodeId = document.getId(); // wait and check that rendition is created ... - Rendition rendition = waitAndGetRendition(contentNodeId, renditionName); + Rendition rendition = waitAndGetRendition(contentNodeId, null, renditionName); assertNotNull(rendition); assertEquals(RenditionStatus.CREATED, rendition.getStatus()); @@ -637,7 +637,7 @@ public class RenditionsTest extends AbstractBaseApiTest { for (String renditionName : renditionNames) { - Rendition rendition = waitAndGetRendition(contentNodeId, renditionName); + Rendition rendition = waitAndGetRendition(contentNodeId, null, renditionName); assertNotNull(rendition); assertEquals(RenditionStatus.CREATED, rendition.getStatus()); }