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
@@ -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 @@