From ae11fef3de434801e1874b47e7cba86fab1dcdf7 Mon Sep 17 00:00:00 2001 From: Ancuta Morarasu Date: Wed, 11 May 2016 12:06:18 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20HEAD=20(5.2)=20to=205.2.N=20(5.2.1)=20?= =?UTF-8?q?=20=20=20126543=20jkaabimofrad:=20Merged=20FILE-FOLDER-API=20(5?= =?UTF-8?q?.2.0)=20to=20HEAD=20(5.2)=20=20=20=20=20=20=20123868=20jkaabimo?= =?UTF-8?q?frad:=20RA-856:=20Added=20support=20for=20REST=20APIs=20?= =?UTF-8?q?=E2=80=9C/content=E2=80=9D=20endpoints=20to=20optionally=20set?= =?UTF-8?q?=20their=20own=20cache.=20(Currently,=20only=20the=20Renditions?= =?UTF-8?q?=20API=20is=20setting=20its=20own=20cache)=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20-=20Removed=20the=20http=20attachment=20header=20se?= =?UTF-8?q?tting=20from=20the=20AbstractResourceWebScript,=20as=20the=20Co?= =?UTF-8?q?ntentStreamer=20already=20has=20this=20feature=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20-=20Reformatted=20ContentStreamer=20according?= =?UTF-8?q?=20to=20Alfresco=20code=20standards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@126888 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../web/scripts/content/ContentStreamer.java | 360 ++++++++++-------- .../rest/api/impl/RenditionsImpl.java | 13 +- .../content/AbstractBinaryResource.java | 9 +- .../resource/content/CacheDirective.java | 133 +++++++ .../resource/content/FileBinaryResource.java | 2 +- .../resource/content/NodeBinaryResource.java | 7 +- .../webscripts/AbstractResourceWebScript.java | 29 +- .../alfresco/rest/api/tests/NodeApiTest.java | 5 +- .../rest/api/tests/RenditionsTest.java | 9 + 9 files changed, 378 insertions(+), 189 deletions(-) create mode 100644 source/java/org/alfresco/rest/framework/resource/content/CacheDirective.java diff --git a/source/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java b/source/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java index 9e662d4d8e..a3beaf4dc8 100644 --- a/source/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java +++ b/source/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (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% - */ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (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.web.scripts.content; import java.io.File; @@ -38,18 +38,12 @@ import java.util.Map; import javax.servlet.http.HttpServletResponse; -import org.alfresco.events.types.ContentEvent; -import org.alfresco.events.types.ContentEventImpl; -import org.alfresco.events.types.Event; import org.alfresco.model.ContentModel; -import org.alfresco.repo.Client; -import org.alfresco.repo.Client.ClientType; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.filestore.FileContentReader; -import org.alfresco.repo.events.EventPreparator; import org.alfresco.repo.events.EventPublisher; import org.alfresco.repo.web.util.HttpRangeProcessor; -import org.alfresco.repo.webdav.WebDAVHelper; +import org.alfresco.rest.framework.resource.content.CacheDirective; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; @@ -63,6 +57,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; +import org.springframework.extensions.surf.util.URLEncoder; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptRequest; @@ -80,74 +75,80 @@ import org.springframework.util.FileCopyUtils; public class ContentStreamer implements ResourceLoaderAware { - // Logger - private static final Log logger = LogFactory.getLog(ContentStreamer.class); + // Logger + private static final Log logger = LogFactory.getLog(ContentStreamer.class); - /** - * format definied by RFC 822, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 - */ - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z", Locale.US); - - private static final String HEADER_CONTENT_RANGE = "Content-Range"; - private static final String HEADER_CONTENT_LENGTH = "Content-Length"; - private static final String HEADER_ACCEPT_RANGES = "Accept-Ranges"; - private static final String HEADER_RANGE = "Range"; - private static final String HEADER_USER_AGENT = "User-Agent"; - - /** Services */ - // protected PermissionService permissionService; - protected NodeService nodeService; - protected ContentService contentService; - protected MimetypeService mimetypeService; - protected ResourceLoader resourceLoader; - protected EventPublisher eventPublisher; - protected SiteService siteService; - /** - * @param mimetypeService MimetypeService - */ - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } + public static final String KEY_ALLOW_BROWSER_TO_CACHE = "allowBrowserToCache"; + public static final String KEY_CACHE_DIRECTIVE = "cacheDirective"; - /** - * @param nodeService NodeService - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * @param eventPublisher EventPublisher - */ - public void setEventPublisher(EventPublisher eventPublisher) - { - this.eventPublisher = eventPublisher; - } - - /** - * @param siteService SiteService - */ - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - - @Override - public void setResourceLoader(ResourceLoader resourceLoader) - { - this.resourceLoader = resourceLoader; - } - - /** - * @param contentService ContentService - */ - public void setContentService(ContentService contentService) - - { - this.contentService = contentService; - } + /** + * format definied by RFC 822, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 + */ + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z", Locale.US); + + private static final String HEADER_CONTENT_RANGE = "Content-Range"; + private static final String HEADER_CONTENT_LENGTH = "Content-Length"; + private static final String HEADER_ACCEPT_RANGES = "Accept-Ranges"; + private static final String HEADER_RANGE = "Range"; + private static final String HEADER_USER_AGENT = "User-Agent"; + + /** + * Services + */ + // protected PermissionService permissionService; + protected NodeService nodeService; + protected ContentService contentService; + protected MimetypeService mimetypeService; + protected ResourceLoader resourceLoader; + protected EventPublisher eventPublisher; + protected SiteService siteService; + + /** + * @param mimetypeService MimetypeService + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * @param nodeService NodeService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param eventPublisher EventPublisher + */ + public void setEventPublisher(EventPublisher eventPublisher) + { + this.eventPublisher = eventPublisher; + } + + /** + * @param siteService SiteService + */ + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) + { + this.resourceLoader = resourceLoader; + } + + /** + * @param contentService ContentService + */ + public void setContentService(ContentService contentService) + + { + this.contentService = contentService; + } /** @@ -194,79 +195,79 @@ public class ContentStreamer implements ResourceLoaderAware } /** - * Streams the content on a given node's content property to the response of the web script. - * - * @param req Request - * @param res Response - * @param nodeRef The node reference - * @param propertyQName The content property name - * @param attach Indicates whether the content should be streamed as an attachment or not - * @param attachFileName Optional file name to use when attach is true - * @throws IOException - */ - public void streamContent(WebScriptRequest req, - WebScriptResponse res, - NodeRef nodeRef, - QName propertyQName, - boolean attach, - String attachFileName, - Map model) throws IOException + * Streams the content on a given node's content property to the response of the web script. + * + * @param req Request + * @param res Response + * @param nodeRef The node reference + * @param propertyQName The content property name + * @param attach Indicates whether the content should be streamed as an attachment or not + * @param attachFileName Optional file name to use when attach is true + * @throws IOException + */ + public void streamContent(WebScriptRequest req, + WebScriptResponse res, + NodeRef nodeRef, + QName propertyQName, + boolean attach, + String attachFileName, + Map model) throws IOException + { + if (logger.isDebugEnabled()) + logger.debug("Retrieving content from node ref " + nodeRef.toString() + " (property: " + propertyQName.toString() + ") (attach: " + attach + ")"); + + // TODO + // This was commented out to accomadate records management permissions. We need to review how we cope with this + // hard coded permission checked. + + // check that the user has at least READ_CONTENT access - else redirect to the login page + // if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED) + // { + // throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Permission denied"); + // } + + // check If-Modified-Since header and set Last-Modified header as appropriate + Date modified = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + if (modified != null) { - if (logger.isDebugEnabled()) - logger.debug("Retrieving content from node ref " + nodeRef.toString() + " (property: " + propertyQName.toString() + ") (attach: " + attach + ")"); - - // TODO - // This was commented out to accomadate records management permissions. We need to review how we cope with this - // hard coded permission checked. - - // check that the user has at least READ_CONTENT access - else redirect to the login page - // if (permissionService.hasPermission(nodeRef, PermissionService.READ_CONTENT) == AccessStatus.DENIED) - // { - // throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Permission denied"); - // } - - // check If-Modified-Since header and set Last-Modified header as appropriate - Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); - if (modified != null) + long modifiedSince = -1; + String modifiedSinceStr = req.getHeader("If-Modified-Since"); + if (modifiedSinceStr != null) { - long modifiedSince = -1; - String modifiedSinceStr = req.getHeader("If-Modified-Since"); - if (modifiedSinceStr != null) + try { - try + modifiedSince = dateFormat.parse(modifiedSinceStr).getTime(); + } + catch (Throwable e) + { + if (logger.isInfoEnabled()) + logger.info("Browser sent badly-formatted If-Modified-Since header: " + modifiedSinceStr); + } + + if (modifiedSince > 0L) + { + // round the date to the ignore millisecond value which is not supplied by header + long modDate = (modified.getTime() / 1000L) * 1000L; + if (modDate <= modifiedSince) { - modifiedSince = dateFormat.parse(modifiedSinceStr).getTime(); - } - catch (Throwable e) - { - if (logger.isInfoEnabled()) - logger.info("Browser sent badly-formatted If-Modified-Since header: " + modifiedSinceStr); - } - - if (modifiedSince > 0L) - { - // round the date to the ignore millisecond value which is not supplied by header - long modDate = (modified.getTime() / 1000L) * 1000L; - if (modDate <= modifiedSince) - { - res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - return; - } + res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; } } } - - // get the content reader - ContentReader reader = contentService.getReader(nodeRef, propertyQName); - if (reader == null || !reader.exists()) - { - throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to locate content for node ref " + nodeRef + " (property: " + propertyQName.toString() + ")"); - } - - // Stream the content - streamContentImpl(req, res, reader, nodeRef, propertyQName, attach, modified, modified == null ? null : String.valueOf(modified.getTime()), attachFileName, model); } + // get the content reader + ContentReader reader = contentService.getReader(nodeRef, propertyQName); + if (reader == null || !reader.exists()) + { + throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND, "Unable to locate content for node ref " + nodeRef + " (property: " + propertyQName.toString() + ")"); + } + + // Stream the content + streamContentImpl(req, res, reader, nodeRef, propertyQName, attach, modified, modified == null ? null : Long.toString(modified.getTime()), attachFileName, model); + } + /** * Streams content back to client from a given resource path. * @@ -355,7 +356,7 @@ public class ContentStreamer implements ResourceLoaderAware final String attachFileName, Map model) throws IOException { - setAttachment(null, res, attach, attachFileName); + setAttachment(req, res, attach, attachFileName); // establish mimetype String mimetype = reader.getMimetype(); @@ -475,7 +476,7 @@ public class ContentStreamer implements ResourceLoaderAware if (req == null) { - headerValue += "; filename*=UTF-8''" + WebDAVHelper.encodeURL(attachFileName) + headerValue += "; filename*=UTF-8''" + URLEncoder.encode(attachFileName) + "; filename=\"" + attachFileName + "\""; } else @@ -484,12 +485,12 @@ public class ContentStreamer implements ResourceLoaderAware boolean isLegacy = (null != userAgent) && (userAgent.contains("MSIE 8") || userAgent.contains("MSIE 7")); if (isLegacy) { - headerValue += "; filename=\"" + WebDAVHelper.encodeURL(attachFileName); + headerValue += "; filename=\"" + URLEncoder.encode(attachFileName); } else { headerValue += "; filename=\"" + attachFileName + "\"; filename*=UTF-8''" - + WebDAVHelper.encodeURL(attachFileName); + + URLEncoder.encode(attachFileName); } } } @@ -511,8 +512,21 @@ public class ContentStreamer implements ResourceLoaderAware protected void setResponseCache(WebScriptResponse res, Date modified, String eTag, Map model) { Cache cache = new Cache(); - if (model == null || model.get("allowBrowserToCache") == null || ((String)model.get("allowBrowserToCache")).equals("false")) + + Object obj; + if (model != null && (obj = model.get(KEY_CACHE_DIRECTIVE)) instanceof CacheDirective) { + CacheDirective cacheDirective = (CacheDirective) obj; + cache.setNeverCache(cacheDirective.isNeverCache()); + cache.setMustRevalidate(cacheDirective.isMustRevalidate()); + cache.setMaxAge(cacheDirective.getMaxAge()); + cache.setLastModified(cacheDirective.getLastModified()); + cache.setETag(cacheDirective.getETag()); + cache.setIsPublic(cacheDirective.isPublic()); + } + else if (model == null || !getBooleanValue(model.get(KEY_ALLOW_BROWSER_TO_CACHE))) + { + // if 'allowBrowserToCache' is null or false cache.setNeverCache(false); cache.setMustRevalidate(true); cache.setMaxAge(0L); @@ -523,12 +537,20 @@ public class ContentStreamer implements ResourceLoaderAware { cache.setNeverCache(false); cache.setMustRevalidate(false); - cache.setMaxAge(new Long(31536000)); + cache.setMaxAge(Long.valueOf(31536000));// one year cache.setLastModified(modified); cache.setETag(eTag); - res.setCache(cache); } + res.setCache(cache); } + private boolean getBooleanValue(Object obj) + { + if (obj instanceof String) + { + return Boolean.valueOf((String) obj); + } + return Boolean.TRUE.equals(obj); + } } diff --git a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java index 8cb5aa09d4..5d659cbadf 100644 --- a/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java +++ b/source/java/org/alfresco/rest/api/impl/RenditionsImpl.java @@ -37,6 +37,7 @@ import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; import org.alfresco.rest.framework.core.exceptions.NotFoundException; import org.alfresco.rest.framework.resource.content.BinaryResource; +import org.alfresco.rest.framework.resource.content.CacheDirective; import org.alfresco.rest.framework.resource.content.ContentInfoImpl; import org.alfresco.rest.framework.resource.content.FileBinaryResource; import org.alfresco.rest.framework.resource.content.NodeBinaryResource; @@ -70,6 +71,7 @@ import java.io.File; import java.io.InputStream; import java.io.Serializable; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; @@ -348,14 +350,23 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware Map nodeProps = nodeService.getProperties(renditionNodeRef); ContentData contentData = (ContentData) nodeProps.get(ContentModel.PROP_CONTENT); + Date modified = (Date) nodeProps.get(ContentModel.PROP_MODIFIED); org.alfresco.rest.framework.resource.content.ContentInfo contentInfo = null; if (contentData != null) { contentInfo = new ContentInfoImpl(contentData.getMimetype(), contentData.getEncoding(), contentData.getSize(), contentData.getLocale()); } + // add cache settings + CacheDirective cacheDirective = new CacheDirective.Builder() + .setNeverCache(false) + .setMustRevalidate(false) + .setLastModified(modified) + .setETag(modified != null ? Long.toString(modified.getTime()) : null) + .setMaxAge(Long.valueOf(31536000))// one year (in seconds) + .build(); - return new NodeBinaryResource(renditionNodeRef, ContentModel.PROP_CONTENT, contentInfo, attachFileName); + return new NodeBinaryResource(renditionNodeRef, ContentModel.PROP_CONTENT, contentInfo, attachFileName, cacheDirective); } protected NodeRef getRenditionByName(NodeRef nodeRef, String renditionId, Parameters parameters) diff --git a/source/java/org/alfresco/rest/framework/resource/content/AbstractBinaryResource.java b/source/java/org/alfresco/rest/framework/resource/content/AbstractBinaryResource.java index 0e0b40e54f..1e84316c96 100644 --- a/source/java/org/alfresco/rest/framework/resource/content/AbstractBinaryResource.java +++ b/source/java/org/alfresco/rest/framework/resource/content/AbstractBinaryResource.java @@ -27,14 +27,21 @@ package org.alfresco.rest.framework.resource.content; public class AbstractBinaryResource implements BinaryResource { final String attachFileName; + final CacheDirective cacheDirective; - public AbstractBinaryResource(String attachFileName) + public AbstractBinaryResource(String attachFileName, CacheDirective cacheDirective) { this.attachFileName = attachFileName; + this.cacheDirective = cacheDirective; } public String getAttachFileName() { return attachFileName; } + + public CacheDirective getCacheDirective() + { + return cacheDirective; + } } diff --git a/source/java/org/alfresco/rest/framework/resource/content/CacheDirective.java b/source/java/org/alfresco/rest/framework/resource/content/CacheDirective.java new file mode 100644 index 0000000000..492eeee958 --- /dev/null +++ b/source/java/org/alfresco/rest/framework/resource/content/CacheDirective.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2005-2016 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ + +package org.alfresco.rest.framework.resource.content; + +import java.util.Date; + +/** + * An immutable builder for setting the HTTP cache. + * + * @author Jamal Kaabi-Mofrad + */ +public class CacheDirective +{ + private final boolean neverCache; + private final boolean isPublic; + private final boolean mustRevalidate; + private final Date lastModified; + private final String eTag; + private final Long maxAge; + + private CacheDirective(Builder builder) + { + this.neverCache = builder.neverCache; + this.isPublic = builder.isPublic; + this.mustRevalidate = builder.mustRevalidate; + this.lastModified = builder.lastModified == null ? null : new Date(builder.lastModified.getTime()); + this.eTag = builder.eTag; + this.maxAge = builder.maxAge; + } + + public boolean isNeverCache() + { + return neverCache; + } + + public boolean isPublic() + { + return isPublic; + } + + public boolean isMustRevalidate() + { + return mustRevalidate; + } + + public Date getLastModified() + { + if (lastModified != null) + { + return new Date(lastModified.getTime()); + } + return null; + } + + public String getETag() + { + return eTag; + } + + public Long getMaxAge() + { + return maxAge; + } + + public static class Builder + { + // The default values are the same as the org.springframework.extensions.webscripts.Cache + private boolean neverCache = true; + private boolean isPublic = false; + private boolean mustRevalidate = true; + private Date lastModified = null; + private String eTag = null; + private Long maxAge = null; + + public Builder setNeverCache(boolean neverCache) + { + this.neverCache = neverCache; + return this; + } + + public Builder setPublic(boolean aPublic) + { + isPublic = aPublic; + return this; + } + + public Builder setMustRevalidate(boolean mustRevalidate) + { + this.mustRevalidate = mustRevalidate; + return this; + } + + public Builder setLastModified(Date lastModified) + { + this.lastModified = lastModified; + return this; + } + + public Builder setETag(String eTag) + { + this.eTag = eTag; + return this; + } + + public Builder setMaxAge(Long maxAge) + { + this.maxAge = maxAge; + return this; + } + + public CacheDirective build() + { + return new CacheDirective(this); + } + } +} diff --git a/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java b/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java index 903fd7ec5c..fced1bffd7 100755 --- a/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java +++ b/source/java/org/alfresco/rest/framework/resource/content/FileBinaryResource.java @@ -44,7 +44,7 @@ public class FileBinaryResource extends AbstractBinaryResource public FileBinaryResource(File file, String attachFileName) { - super(attachFileName); + super(attachFileName, null); this.file = file; } diff --git a/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java b/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java index 21e176d73b..7d275255e0 100755 --- a/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java +++ b/source/java/org/alfresco/rest/framework/resource/content/NodeBinaryResource.java @@ -42,7 +42,12 @@ public class NodeBinaryResource extends AbstractBinaryResource public NodeBinaryResource(NodeRef nodeRef, QName propertyQName, ContentInfo contentInfo, String attachFileName) { - super(attachFileName); + this(nodeRef, propertyQName, contentInfo, attachFileName, null); + } + + public NodeBinaryResource(NodeRef nodeRef, QName propertyQName, ContentInfo contentInfo, String attachFileName, CacheDirective cacheDirective) + { + super(attachFileName, cacheDirective); this.nodeRef = nodeRef; this.propertyQName = propertyQName; this.contentInfo = contentInfo; diff --git a/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java b/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java index 16a592555b..a310324d5c 100644 --- a/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java +++ b/source/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java @@ -26,14 +26,12 @@ package org.alfresco.rest.framework.webscripts; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.node.integrity.IntegrityException; import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.web.scripts.content.ContentStreamer; @@ -49,6 +47,7 @@ import org.alfresco.rest.framework.resource.actions.ActionExecutor; import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.content.BinaryResource; +import org.alfresco.rest.framework.resource.content.CacheDirective; import org.alfresco.rest.framework.resource.content.ContentInfo; import org.alfresco.rest.framework.resource.content.FileBinaryResource; import org.alfresco.rest.framework.resource.content.NodeBinaryResource; @@ -60,7 +59,6 @@ import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; -import org.springframework.extensions.surf.util.URLEncoder; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptRequest; @@ -91,8 +89,6 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements private ContentStreamer streamer; protected ResourceWebScriptHelper helper; - public final static String HDR_NAME_CONTENT_DISPOSITION = "Content-Disposition"; - @SuppressWarnings("rawtypes") @Override public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException @@ -194,27 +190,30 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements { FileBinaryResource fileResource = (FileBinaryResource) resource; // if requested, set attachment - setAttachment(res, fileResource.getAttachFileName()); - streamer.streamContent(req, res, fileResource.getFile(), null, false, null, null); + boolean attach = StringUtils.isNotEmpty(fileResource.getAttachFileName()); + Map model = getModelForCacheDirective(fileResource.getCacheDirective()); + streamer.streamContent(req, res, fileResource.getFile(), null, attach, fileResource.getAttachFileName(), model); } else if (resource instanceof NodeBinaryResource) { NodeBinaryResource nodeResource = (NodeBinaryResource) resource; ContentInfo contentInfo = nodeResource.getContentInfo(); - setContentInfoOnResponse(res,contentInfo); + setContentInfoOnResponse(res, contentInfo); // if requested, set attachment - setAttachment(res, nodeResource.getAttachFileName()); - streamer.streamContent(req, res, nodeResource.getNodeRef(), nodeResource.getPropertyQName(), false, null, null); + boolean attach = StringUtils.isNotEmpty(nodeResource.getAttachFileName()); + Map model = getModelForCacheDirective(nodeResource.getCacheDirective()); + streamer.streamContent(req, res, nodeResource.getNodeRef(), nodeResource.getPropertyQName(), attach, nodeResource.getAttachFileName(), model); } + } - private void setAttachment(final WebScriptResponse res, final String attachFileName) + private static Map getModelForCacheDirective(CacheDirective cacheDirective) { - if (StringUtils.isNotEmpty(attachFileName)) + if (cacheDirective != null) { - String headerValue = "attachment; filename=\"" + attachFileName + "\"; filename*=UTF-8''" + URLEncoder.encode(attachFileName); - res.setHeader(HDR_NAME_CONTENT_DISPOSITION, headerValue); + return Collections.singletonMap(ContentStreamer.KEY_CACHE_DIRECTIVE, (Object) cacheDirective); } + return null; } /** diff --git a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java index 693d51f3bf..a5a6ada815 100644 --- a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -2739,7 +2739,10 @@ public class NodeApiTest extends AbstractBaseApiTest Map responseHeaders = response.getHeaders(); assertNotNull(responseHeaders); assertEquals("attachment; filename=\"quick-1.txt\"; filename*=UTF-8''quick-1.txt", responseHeaders.get("Content-Disposition")); - assertNotNull(responseHeaders.get("Cache-Control")); + String cacheControl = responseHeaders.get("Cache-Control"); + assertNotNull(cacheControl); + assertTrue(cacheControl.contains("must-revalidate")); + assertTrue(cacheControl.contains("max-age=0")); assertNotNull(responseHeaders.get("Expires")); String lastModifiedHeader = responseHeaders.get(LAST_MODIFIED_HEADER); assertNotNull(lastModifiedHeader); diff --git a/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java b/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java index d81c72c773..3b851d65b3 100644 --- a/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/RenditionsTest.java @@ -21,6 +21,7 @@ package org.alfresco.rest.api.tests; import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -489,6 +490,9 @@ public class RenditionsTest extends AbstractBaseApiTest assertNotNull(response.getResponseAsBytes()); responseHeaders = response.getHeaders(); assertNotNull(responseHeaders); + String cacheControl = responseHeaders.get("Cache-Control"); + assertNotNull(cacheControl); + assertTrue(cacheControl.contains("must-revalidate")); assertNull(responseHeaders.get("Content-Disposition")); contentType = responseHeaders.get("Content-Type"); assertNotNull(contentType); @@ -536,6 +540,11 @@ public class RenditionsTest extends AbstractBaseApiTest assertNotNull(response.getResponseAsBytes()); responseHeaders = response.getHeaders(); assertNotNull(responseHeaders); + // Check the cache settings which have been set in the RenditionsImpl#getContent() + cacheControl = responseHeaders.get("Cache-Control"); + assertNotNull(cacheControl); + assertFalse(cacheControl.contains("must-revalidate")); + assertTrue(cacheControl.contains("max-age=31536000")); contentDisposition = responseHeaders.get("Content-Disposition"); assertNotNull(contentDisposition); assertTrue(contentDisposition.contains("filename=\"doclib\""));