From 0b0ae94a9009b3bb6aa6f68b92fcd5013c3148bf Mon Sep 17 00:00:00 2001 From: Sara Aspery Date: Wed, 4 Aug 2021 08:48:50 +0100 Subject: [PATCH] Content Service changes both ACS-1781 and 1782 --- .../alfresco/repo/content/ContentStore.java | 62 +++++++- .../cmr/repository/DirectAccessUrl.java | 39 ++++- .../repo/content/ContentServiceImpl.java | 136 ++++++++++++++---- .../content/caching/CachingContentStore.java | 38 +++-- .../DirectAccessUrlDisabledException.java | 44 ++++++ .../replication/AggregatingContentStore.java | 55 +++++-- .../cmr/repository/ContentService.java | 48 ++++++- .../alfresco/content-services-context.xml | 3 + .../caching/CachingContentStoreTest.java | 14 +- .../AggregatingContentStoreTest.java | 82 +++++------ .../repo/version/ContentServiceImplTest.java | 26 ++-- 11 files changed, 421 insertions(+), 126 deletions(-) create mode 100644 repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java diff --git a/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java b/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java index ccded57adb..4344a15c87 100644 --- a/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java +++ b/data-model/src/main/java/org/alfresco/repo/content/ContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Data model classes * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 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,6 +25,8 @@ */ package org.alfresco.repo.content; +import java.util.Date; + import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentIOException; @@ -32,8 +34,8 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentStreamListener; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; -import java.util.Date; /** * Provides low-level retrieval of content @@ -239,6 +241,58 @@ public interface ContentStore */ public boolean delete(String contentUrl); + /** + * Checks if the store supports the retrieving of direct access URLs. + * + * @return {@code true} if direct access URLs retrieving is supported, {@code false} otherwise + */ + default boolean isContentDirectUrlEnabled() + { + return false; + } + + /** + * Checks if the store supports the retrieving of a direct access URL for the given node. + * + * @return {@code true} if direct access URLs retrieving is supported for the node, {@code false} otherwise + */ + default boolean isContentDirectUrlEnabled(NodeRef nodeRef) + { + return false; + } + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param contentUrl A content store {@code URL} + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param fileName File name of the content + * @return A direct access {@code URL} object for the content + * @throws UnsupportedOperationException if the store is unable to provide the information + */ + default DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName) + { + return requestContentDirectUrl(contentUrl, attachment, fileName, null); + } + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param contentUrl A content store {@code URL} + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param fileName File name of the content + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information + */ + default DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) + { + throw new UnsupportedOperationException( + "Retrieving direct access URLs is not supported by this content store."); + } + /** * Gets a presigned URL to directly access a binary content. It is up to the actual store * implementation if it can fulfil this request with an expiry time or not. @@ -248,10 +302,11 @@ public interface ContentStore * @return A direct access URL object for a binary content * @throws UnsupportedOperationException if the store is unable to provide the information */ + @Deprecated default DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) { throw new UnsupportedOperationException( - "Retrieving direct access URLs is not supported by this content store."); + "Retrieving direct access URLs is not supported by this content store."); } /** @@ -259,6 +314,7 @@ public interface ContentStore * * @return true if direct access URLs retrieving is supported, false otherwise */ + @Deprecated default boolean isDirectAccessSupported() { return false; diff --git a/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java b/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java index 0df9532f7a..365f441bb2 100644 --- a/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java +++ b/data-model/src/main/java/org/alfresco/service/cmr/repository/DirectAccessUrl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Data model classes * %% - * Copyright (C) 2005 - 2020 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -27,6 +27,7 @@ package org.alfresco.service.cmr.repository; import java.io.Serializable; import java.util.Date; +import java.util.Objects; import org.alfresco.api.AlfrescoPublicApi; @@ -36,7 +37,8 @@ public class DirectAccessUrl implements Serializable private static final long serialVersionUID = -881676208224414139L; private String contentUrl; - private Date expiresAt; + private Date expiryTime; + private boolean attachment; public String getContentUrl() { @@ -48,13 +50,38 @@ public class DirectAccessUrl implements Serializable this.contentUrl = contentUrl; } - public Date getExpiresAt() + public Date getExpiryTime() { - return expiresAt; + return expiryTime; } - public void setExpiresAt(Date expiresAt) + public void setExpiryTime(Date expiryTime) { - this.expiresAt = expiresAt; + this.expiryTime = expiryTime; + } + + public boolean isAttachment() + { + return attachment; + } + + public void setAttachment(boolean attachment) + { + this.attachment = attachment; + } + + @Override public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + + DirectAccessUrl that = (DirectAccessUrl) obj; + return attachment == that.attachment && Objects.equals(contentUrl, + that.contentUrl) && Objects.equals(expiryTime, that.expiryTime); + } + + @Override public int hashCode() + { + return Objects.hash(contentUrl, expiryTime, attachment); } } diff --git a/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java b/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java index 86e58e6527..48668417b6 100644 --- a/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/repository/src/main/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * Copyright (C) 2005 - 2021 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,22 @@ */ package org.alfresco.repo.content; +import java.io.Serializable; +import java.time.Instant; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentServicePolicies.OnContentPropertyUpdatePolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner; +import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException; +import org.alfresco.repo.content.directurl.SystemWideDirectUrlConfig; import org.alfresco.repo.content.filestore.FileContentStore; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.ClassPolicyDelegate; @@ -47,6 +57,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.MimetypeServiceAware; import org.alfresco.service.cmr.repository.NodeRef; @@ -62,13 +73,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.extensions.surf.util.I18NUtil; -import java.io.Serializable; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - /** * Service implementation acting as a level of indirection between the client * and the underlying content store. @@ -82,7 +86,7 @@ import java.util.Set; */ public class ContentServiceImpl implements ContentService, ApplicationContextAware { - private static Log logger = LogFactory.getLog(ContentServiceImpl.class); + private static final Log logger = LogFactory.getLog(ContentServiceImpl.class); private DictionaryService dictionaryService; private NodeService nodeService; @@ -99,6 +103,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa /** Should we consider zero byte content to be the same as no content? */ private boolean ignoreEmptyContent; + private SystemWideDirectUrlConfig systemWideDirectUrlConfig; + /** * The policy component */ @@ -140,7 +146,12 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa { this.store = store; } - + + public void setSystemWideDirectUrlConfig(SystemWideDirectUrlConfig systemWideDirectUrlConfig) + { + this.systemWideDirectUrlConfig = systemWideDirectUrlConfig; + } + public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; @@ -510,23 +521,10 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa return tempStore.getWriter(ContentContext.NULL_CONTEXT); } - @Override + @Deprecated public DirectAccessUrl getDirectAccessUrl(NodeRef nodeRef, Date expiresAt) { - ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); - - // check that the URL is available - if (contentData == null || contentData.getContentUrl() == null) - { - throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); - } - - if (store.isDirectAccessSupported()) - { - return store.getDirectAccessUrl(contentData.getContentUrl(), expiresAt); - } - - return null; + return requestContentDirectUrl(nodeRef, true, null); } /** @@ -586,4 +584,92 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } } } + + /** + * {@inheritDoc} + */ + @Override + public boolean isContentDirectUrlEnabled() + { + return systemWideDirectUrlConfig.isEnabled() && store.isContentDirectUrlEnabled(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isContentDirectUrlEnabled(NodeRef nodeRef) + { + boolean contentDirectUrlEnabled = false; + + // TODO: update this + if (systemWideDirectUrlConfig.isEnabled()) + { + ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); + + // check that the URL is available + if (contentData == null || contentData.getContentUrl() == null) + { + throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); + } + + contentDirectUrlEnabled = (store.isContentDirectUrlEnabled(nodeRef)); + } + + return contentDirectUrlEnabled; + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor) + { + if (!isContentDirectUrlEnabled()) + { + throw new DirectAccessUrlDisabledException("Direct access url isn't available."); + } + + String contentUrl = getContentUrl(nodeRef); + String fileName = getFileName(nodeRef); + validFor = validateValidFor(validFor); + + return store.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); + } + + private String getContentUrl(NodeRef nodeRef) + { + ContentData contentData = getContentData(nodeRef, ContentModel.PROP_CONTENT); + + // check that the URL is available + if (contentData == null || contentData.getContentUrl() == null) + { + throw new IllegalArgumentException("The supplied nodeRef " + nodeRef + " has no content."); + } + + return contentData.getContentUrl(); + } + + private String getFileName(NodeRef nodeRef) + { + String fileName = null; + + try + { + fileName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + } + catch (InvalidNodeRefException ex) + { + } + + return fileName; + } + + private Long validateValidFor(Long validFor) + { + if (validFor == null || validFor > systemWideDirectUrlConfig.getDefaultExpiryTimeInSec()) + { + validFor = systemWideDirectUrlConfig.getDefaultExpiryTimeInSec(); + } + return validFor; + } } \ No newline at end of file diff --git a/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java b/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java index 5cad001984..77e7851547 100644 --- a/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java +++ b/repository/src/main/java/org/alfresco/repo/content/caching/CachingContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 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,7 +25,6 @@ */ package org.alfresco.repo.content.caching; -import java.util.Date; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; @@ -41,6 +40,7 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentStreamListener; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanNameAware; @@ -103,7 +103,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis { eventPublisher.publishEvent(new CachingContentStoreCreatedEvent(this)); } - + @Override public boolean isContentUrlSupported(String contentUrl) { @@ -137,7 +137,7 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis /** * {@inheritDoc} *

- * For {@link #SPOOF_PROTOCOL spoofed} URLs, the URL always exists. + * For {@link FileContentStore#SPOOF_PROTOCOL spoofed} URLs, the URL always exists. */ @Override public boolean exists(String contentUrl) @@ -478,13 +478,35 @@ public class CachingContentStore implements ContentStore, ApplicationEventPublis return this.beanName; } - public boolean isDirectAccessSupported() + /** + * {@inheritDoc} + */ + public boolean isContentDirectUrlEnabled() { - return backingStore.isDirectAccessSupported(); + return backingStore.isContentDirectUrlEnabled(); } - public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) + /** + * {@inheritDoc} + */ + public boolean isContentDirectUrlEnabled(NodeRef nodeRef) { - return backingStore.getDirectAccessUrl(contentUrl, expiresAt); + return backingStore.isContentDirectUrlEnabled(nodeRef); + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName) + { + return backingStore.requestContentDirectUrl(contentUrl, attachment, fileName); + } + + /** + * {@inheritDoc} + */ + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) + { + return backingStore.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } } diff --git a/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java new file mode 100644 index 0000000000..d0702b398d --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/content/directurl/DirectAccessUrlDisabledException.java @@ -0,0 +1,44 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 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.repo.content.directurl; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Runtime exception thrown when direct access URLs are disabled. + * + * @author Sara Aspery + */ +public class DirectAccessUrlDisabledException extends AlfrescoRuntimeException +{ + + private static final long serialVersionUID = -6506082117146782993L; + + public DirectAccessUrlDisabledException(String msg) + { + super(msg); + } +} diff --git a/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java b/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java index 15cc9c5246..6c700e0572 100644 --- a/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java +++ b/repository/src/main/java/org/alfresco/repo/content/replication/AggregatingContentStore.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 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,7 +25,6 @@ */ package org.alfresco.repo.content.replication; -import java.util.Date; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; @@ -41,6 +40,7 @@ import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DirectAccessUrl; +import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -65,7 +65,7 @@ import org.apache.commons.logging.LogFactory; */ public class AggregatingContentStore extends AbstractContentStore { - private static Log logger = LogFactory.getLog(AggregatingContentStore.class); + private static final Log logger = LogFactory.getLog(AggregatingContentStore.class); private ContentStore primaryStore; private List secondaryStores; @@ -266,33 +266,58 @@ public class AggregatingContentStore extends AbstractContentStore } /** - * @return Returns true if at least one store supports direct access + * @return Returns {@code true} if at least one store supports direct access URLs */ - public boolean isDirectAccessSupported() + public boolean isContentDirectUrlEnabled() { // Check the primary store - boolean isDirectAccessSupported = primaryStore.isDirectAccessSupported(); + boolean isContentDirectUrlEnabled = primaryStore.isContentDirectUrlEnabled(); - if (!isDirectAccessSupported) + if (!isContentDirectUrlEnabled) { // Direct access is not supported by the primary store so we have to check the // other stores for (ContentStore store : secondaryStores) { + isContentDirectUrlEnabled = store.isContentDirectUrlEnabled(); - isDirectAccessSupported = store.isDirectAccessSupported(); - - if (isDirectAccessSupported) + if (isContentDirectUrlEnabled) { break; } } } - return isDirectAccessSupported; + return isContentDirectUrlEnabled; } - public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt) + /** + * @return Returns {@code true} if at least one store supports direct access URL for node + */ + public boolean isContentDirectUrlEnabled(NodeRef nodeRef) + { + // Check the primary store + boolean isContentDirectUrlEnabled = primaryStore.isContentDirectUrlEnabled(nodeRef); + + if (!isContentDirectUrlEnabled) + { + // Direct access is not supported by the primary store so we have to check the + // other stores + for (ContentStore store : secondaryStores) + { + isContentDirectUrlEnabled = store.isContentDirectUrlEnabled(nodeRef); + + if (isContentDirectUrlEnabled) + { + break; + } + } + } + + return isContentDirectUrlEnabled; + } + + public DirectAccessUrl requestContentDirectUrl(String contentUrl, boolean attachment, String fileName, Long validFor) { if (primaryStore == null) { @@ -312,13 +337,13 @@ public class AggregatingContentStore extends AbstractContentStore // Check the primary store try { - directAccessUrl = primaryStore.getDirectAccessUrl(contentUrl, expiresAt); + directAccessUrl = primaryStore.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } catch (UnsupportedOperationException e) { // The store does not support direct access URL directAccessUrlSupported = false; - } + } catch (UnsupportedContentUrlException e) { // The store can't handle the content URL @@ -335,7 +360,7 @@ public class AggregatingContentStore extends AbstractContentStore { try { - directAccessUrl = store.getDirectAccessUrl(contentUrl, expiresAt); + directAccessUrl = store.requestContentDirectUrl(contentUrl, attachment, fileName, validFor); } catch (UnsupportedOperationException e) { diff --git a/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java b/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java index 81622ff41a..ce73035374 100644 --- a/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/repository/src/main/java/org/alfresco/service/cmr/repository/ContentService.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2019 Alfresco Software Limited + * Copyright (C) 2005 - 2021 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,13 +25,13 @@ */ package org.alfresco.service.cmr.repository; +import java.util.Date; + import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.service.Auditable; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.namespace.QName; -import java.util.Date; - /** * Provides methods for accessing and transforming content. *

@@ -168,6 +168,48 @@ public interface ContentService * @return A direct access URL object for a binary content or returns null if not supported * @throws IllegalArgumentException if there is no binary content for the node */ + @Deprecated @Auditable(parameters = {"nodeRef", "expiresAt"}) public DirectAccessUrl getDirectAccessUrl(NodeRef nodeRef, Date expiresAt); + + /** + * Checks if the system and at least one store supports the retrieving of direct access URLs. + * + * @return {@code true} if direct access URLs retrieving is supported, {@code false} otherwise + */ + boolean isContentDirectUrlEnabled(); + + /** + * Checks if the system and store supports the retrieving of a direct access {@code URL} for the given node. + * + * @return {@code true} if direct access URLs retrieving is supported for the node, {@code false} otherwise + */ + boolean isContentDirectUrlEnabled(NodeRef nodeRef); + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param nodeRef Node ref for which to obtain the direct access {@code URL}. + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information. + */ + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment) + { + return requestContentDirectUrl(nodeRef, attachment, null); + } + + /** + * Gets a presigned URL to directly access the content. It is up to the actual store + * implementation if it can fulfil this request with an expiry time or not. + * + * @param nodeRef Node ref for which to obtain the direct access {@code URL}. + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information. + */ + @Auditable(parameters = {"nodeRef", "validFor"}) + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor); } diff --git a/repository/src/main/resources/alfresco/content-services-context.xml b/repository/src/main/resources/alfresco/content-services-context.xml index ab02159381..a086db2e41 100644 --- a/repository/src/main/resources/alfresco/content-services-context.xml +++ b/repository/src/main/resources/alfresco/content-services-context.xml @@ -161,6 +161,9 @@ ${policy.content.update.ignoreEmpty} + + + diff --git a/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java b/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java index ffac9eea8b..3243e9150b 100644 --- a/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java @@ -493,10 +493,10 @@ public class CachingContentStoreTest @Test public void isDirectAccessSupported() { - assertFalse(cachingStore.isDirectAccessSupported()); + assertFalse(cachingStore.isContentDirectUrlEnabled()); - when(backingStore.isDirectAccessSupported()).thenReturn(true); - assertTrue(cachingStore.isDirectAccessSupported()); + when(backingStore.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(cachingStore.isContentDirectUrlEnabled()); } @Test @@ -504,8 +504,8 @@ public class CachingContentStoreTest { try { - when(backingStore.getDirectAccessUrl(anyString(), any())).thenThrow(new UnsupportedOperationException()); - cachingStore.getDirectAccessUrl("url", null); + when(backingStore.requestContentDirectUrl(anyString(), any(), anyString(), any())).thenThrow(new UnsupportedOperationException()); + cachingStore.requestContentDirectUrl("url", true,null, 30L); fail(); } catch (UnsupportedOperationException e) @@ -517,7 +517,7 @@ public class CachingContentStoreTest @Test public void getDirectAccessUrl() { - when(backingStore.getDirectAccessUrl(anyString(), any())).thenReturn(new DirectAccessUrl()); - cachingStore.getDirectAccessUrl("url", null); + when(backingStore.requestContentDirectUrl(anyString(), any(), anyString(), any())).thenReturn(new DirectAccessUrl()); + cachingStore.requestContentDirectUrl("url", true,null, 30L); } } diff --git a/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java b/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java index ecfaf17823..9576677d43 100644 --- a/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/replication/AggregatingContentStoreTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -191,7 +191,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes } @Test - public void testIsDirectAccessSupported() + public void testIsContentDirectUrlEnabled() { // Create the aggregating store AggregatingContentStore aggStore = new AggregatingContentStore(); @@ -199,21 +199,21 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes aggStore.setSecondaryStores(List.of(secondaryStoreMock)); // By default it is unsupported - assertFalse(aggStore.isDirectAccessSupported()); + assertFalse(aggStore.isContentDirectUrlEnabled()); // Supported if at least one store supports direct access { - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(false); - when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(true); - assertTrue(aggStore.isDirectAccessSupported()); + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(false); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(aggStore.isContentDirectUrlEnabled()); - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true); - when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(true); - assertTrue(aggStore.isDirectAccessSupported()); + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + assertTrue(aggStore.isContentDirectUrlEnabled()); - when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true); - when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(false); - assertTrue(aggStore.isDirectAccessSupported()); + when(primaryStoreMock.isContentDirectUrlEnabled()).thenReturn(true); + when(secondaryStoreMock.isContentDirectUrlEnabled()).thenReturn(false); + assertTrue(aggStore.isContentDirectUrlEnabled()); } } @@ -229,15 +229,15 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes UnsupportedContentUrlException unsupportedContentUrlExc = new UnsupportedContentUrlException(aggStore, ""); // By default it is unsupported - DirectAccessUrl directAccessUrl = aggStore.getDirectAccessUrl("url", null); + DirectAccessUrl directAccessUrl = aggStore.requestContentDirectUrl("url", true, "anyfilename", 30L); assertNull(directAccessUrl); // Direct access not supported try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - aggStore.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + aggStore.requestContentDirectUrl(eq("urlDANotSupported"), true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -247,9 +247,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - aggStore.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + aggStore.requestContentDirectUrl("urlDANotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -259,9 +259,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc); - aggStore.getDirectAccessUrl("urlDANotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlDANotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + aggStore.requestContentDirectUrl("urlDANotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedOperationException e) @@ -272,9 +272,9 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes // Content url not supported try { - when(primaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc); - aggStore.getDirectAccessUrl("urlNotSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlNotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlNotSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + aggStore.requestContentDirectUrl("urlNotSupported", true, "anyfilename", 30L); fail(); } catch (UnsupportedContentUrlException e) @@ -282,31 +282,31 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes // Expected } - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenThrow(unsupportedExc); - directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenThrow(unsupportedExc); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenThrow(unsupportedContentUrlExc); - directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenThrow(unsupportedExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedContentUrlExc); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenThrow(unsupportedContentUrlExc); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl()); - when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl()); - directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null); + when(primaryStoreMock.requestContentDirectUrl(eq("urlPriSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + when(secondaryStoreMock.requestContentDirectUrl(eq("urlSecSupported"), any(), any(), any())).thenReturn(new DirectAccessUrl()); + directAccessUrl = aggStore.requestContentDirectUrl("urlPriSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); - directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null); + directAccessUrl = aggStore.requestContentDirectUrl("urlSecSupported", true, "anyfilename", 30L); assertNotNull(directAccessUrl); } } diff --git a/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java b/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java index 5cebb8acaf..fbb4490d9f 100644 --- a/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java +++ b/repository/src/test/java/org/alfresco/repo/version/ContentServiceImplTest.java @@ -2,7 +2,7 @@ * #%L * Alfresco Repository * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited + * Copyright (C) 2005 - 2021 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,18 +25,12 @@ */ package org.alfresco.repo.version; -import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.ContentStore; -import org.alfresco.repo.content.EmptyContentReader; -import org.alfresco.repo.content.MimetypeMap; -import org.alfresco.repo.content.MimetypeMapTest; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.namespace.QName; import org.alfresco.test_category.OwnJVMTestsCategory; @@ -47,7 +41,6 @@ import org.junit.experimental.categories.Category; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; -import java.util.Date; /** * Tests for getting content readers and writers. @@ -139,15 +132,12 @@ public class ContentServiceImplTest extends BaseVersionStoreTest } @Test - public void testWhenGetDirectAccessUrlIsNotSupported() + public void testWhenRequestContentDirectUrlIsNotSupported() { - assertFalse(contentStore.isDirectAccessSupported()); + assertFalse(contentStore.isContentDirectUrlEnabled()); // Set the presigned URL to expire after one minute. - Date expiresAt = new Date(); - long expTimeMillis = expiresAt.getTime(); - expTimeMillis += 1000 * 60; - expiresAt.setTime(expTimeMillis); + Long validFor = 60L; try { @@ -155,7 +145,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest NodeRef nodeRef = this.dbNodeService .createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName.createQName("{test}MyNoContentNode"), TEST_TYPE_QNAME, this.nodeProperties).getChildRef(); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, expiresAt)); + assertEquals(null, contentService.requestContentDirectUrl(nodeRef, true, validFor)); fail("nodeRef has no content"); } catch (IllegalArgumentException e) @@ -165,7 +155,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest try { - assertEquals(null, contentService.getDirectAccessUrl(null, null)); + assertEquals(null, contentService.requestContentDirectUrl(null, true, null)); fail("nodeRef is null"); } catch (IllegalArgumentException e) @@ -176,7 +166,7 @@ public class ContentServiceImplTest extends BaseVersionStoreTest // Create a node with content NodeRef nodeRef = createNewVersionableNode(); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, null)); - assertEquals(null, contentService.getDirectAccessUrl(nodeRef, expiresAt)); + assertEquals(null, contentService.requestContentDirectUrl(nodeRef, true, null)); + assertEquals(null, contentService.requestContentDirectUrl(nodeRef, true, validFor)); } }