diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/site/membership/membership.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/site/membership/membership.lib.ftl index 07ab257dc6..729f88fe42 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/site/membership/membership.lib.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/site/membership/membership.lib.ftl @@ -24,7 +24,9 @@ "firstName": "${authority.properties.firstName!""}", "lastName": "${authority.properties.lastName!""}", <#if authority.assocs["cm:avatar"]??> - "avatar": "${"api/node/" + authority.assocs["cm:avatar"][0].nodeRef?string?replace('://','/') + "/content/thumbnails/avatar"}", + <#assign avatarNodeRef>${authority.assocs["cm:avatar"][0].nodeRef?string?replace('://','/')} + "avatar": "${"api/node/" + avatarNodeRef + "/content/thumbnails/avatar"}", + "avatarNode": "${avatarNodeRef}", <#if authority.properties.jobtitle??> "jobtitle": "${authority.properties.jobtitle}", diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml index c2108d234c..3727d6adef 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.desc.xml @@ -7,6 +7,7 @@ Please note that Alfresco does not currently use the filename template-arg and that it will be ignored. Therefore a GET to these URLs will return the same resource as to the equivalent URLs without it. + /api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}?c={queueforcecreate?}&ph={placeholder?}&lastModified={modified?} /api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}?c={queueforcecreate?}&ph={placeholder?} /api/path/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}?c={queueforcecreate?}&ph={placeholder?} /api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}/{filename}?c={queueforcecreate?}&ph={placeholder?} diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js index 12249311c0..82fa1d6e24 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js @@ -1,5 +1,17 @@ function main() { + // Indicate whether or not the thumbnail can be cached by the browser. Caching is allowed if the lastModified + // argument is provided as this is an indication of request uniqueness and therefore the browser will have + // the latest thumbnail image. + if (args.lastModified != null) + { + model.allowBrowserToCache = "true"; + } + else + { + model.allowBrowserToCache = "false"; + } + // Get the node from the URL var pathSegments = url.match.split("/"); var reference = [ url.templateArgs.store_type, url.templateArgs.store_id ].concat(url.templateArgs.id.split("/")); diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/item.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/item.lib.ftl index 7b7eaa6bc9..d311d9a15f 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/item.lib.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary/item.lib.ftl @@ -47,6 +47,13 @@ "modifiedOn": "<@dateFormat node.properties.modified />", "modifiedBy": "${modifiedBy}", "modifiedByUser": "${modifiedByUser}", + <#if node.hasAspect("cm:thumbnailModification")> + <#list node.properties.lastThumbnailModification as thumbnailMod> + <#if thumbnailMod?contains("doclib")> + "lastThumbnailModification": "${thumbnailMod}", + + + "lockedBy": "${lockedBy}", "lockedByUser": "${lockedByUser}", "size": "${node.size?c}", diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.desc.xml index 914ca97401..e20c0bc1bf 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.desc.xml @@ -3,6 +3,10 @@ Returns a user avatar image in the format specified by the thumbnailname, or the "avatar" preset if omitted. + /slingshot/profile/avatar/avatar + /slingshot/profile/avatar/avatar/thumbnail/{thumbnailname} + /slingshot/profile/avatar/{store_type}/{store_id}/{id}/thumbnail/{thumbnailname} + /slingshot/profile/avatar/{store_type}/{store_id}/{id} /slingshot/profile/avatar/{username}/thumbnail/{thumbnailname} /slingshot/profile/avatar/{username} argument diff --git a/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.js b/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.js index 04ed94a728..f4b278a1aa 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.js +++ b/config/alfresco/templates/webscripts/org/alfresco/slingshot/profile/avatar.get.js @@ -23,49 +23,78 @@ function getPlaceholder(thumbnailName) function main() { var userName = url.templateArgs.username, - thumbnailName = url.templateArgs.thumbnailname || "avatar", - person = people.getPerson(userName); + thumbnailName = url.templateArgs.thumbnailname || "avatar", + avatarNode; - if (person == null) + // If there is no store type, store id or id on the request then this WebScript has most likely been requested + // for a user with no avatar image so we will just return the placeholder image. + if (userName == null && url.templateArgs.store_type == null && url.templateArgs.store_id == null && url.templateArgs.id == null) { - // Stream the placeholder image + // If there is no userName or nodeRef data then we want to return the browser cacheable placeholder... model.contentPath = getPlaceholder(thumbnailName); + model.allowBrowserToCache = "true"; return; } - - // Retrieve the avatar NodeRef for this person, if there is one. - var avatarAssoc = person.assocs["cm:avatar"]; - - if (avatarAssoc != null) + else if (url.templateArgs.store_type == null && url.templateArgs.store_id == null && url.templateArgs.id == null) { - var avatarNode = avatarAssoc[0]; - if (avatarNode != null) + // There is no nodeRef data but there is a username... this should return the user image that needs revalidation + var person = people.getPerson(userName); + if (person == null) { - // Get the thumbnail - var thumbnail = avatarNode.getThumbnail(thumbnailName); - if (thumbnail == null || thumbnail.size == 0) + // Stream the placeholder image + model.contentPath = getPlaceholder(thumbnailName); + return; + } + else + { + // Retrieve the avatar NodeRef for this person, if there is one. + var avatarAssoc = person.assocs["cm:avatar"]; + if (avatarAssoc != null) { - // Remove broken thumbnail - if (thumbnail != null) - { - thumbnail.remove(); - } - - // Force the creation of the thumbnail - thumbnail = avatarNode.createThumbnail(thumbnailName, false); - if (thumbnail != null) - { - model.contentNode = thumbnail; - return; - } + avatarNode = avatarAssoc[0]; } - else + } + } + else if (userName == null) + { + // There is no user name but there is nodeREf data... this should return the image that CAN be cached by the browser + model.allowBrowserToCache = "true"; + avatarNode = search.findNode(url.templateArgs.store_type + "://" + url.templateArgs.store_id + "/" + url.templateArgs.id); + if (avatarNode == null) + { + // Stream the placeholder image if the avatar node cannot be found. + model.contentPath = getPlaceholder(thumbnailName); + return; + } + } + + // Get the thumbnail for the avatar... + if (avatarNode != null) + { + // Get the thumbnail + var thumbnail = avatarNode.getThumbnail(thumbnailName); + if (thumbnail == null || thumbnail.size == 0) + { + // Remove broken thumbnail + if (thumbnail != null) + { + thumbnail.remove(); + } + + // Force the creation of the thumbnail + thumbnail = avatarNode.createThumbnail(thumbnailName, false); + if (thumbnail != null) { - // Place the details of the thumbnail into the model, this will be used to stream the content to the client model.contentNode = thumbnail; return; - } + } } + else + { + // Place the details of the thumbnail into the model, this will be used to stream the content to the client + model.contentNode = thumbnail; + return; + } } // Stream the placeholder image diff --git a/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java b/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java index 6c6fedd27f..6176da2949 100644 --- a/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java +++ b/source/java/org/alfresco/repo/web/scripts/content/StreamContent.java @@ -197,12 +197,12 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw } // Stream the content - streamContent(req, res, nodeRef, propertyQName, attach); + streamContent(req, res, nodeRef, propertyQName, attach, model); } else { // Stream the content - streamContent(req, res, contentPath, attach); + streamContent(req, res, contentPath, attach, model); } } } @@ -309,10 +309,33 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attach Indicates whether the content should be streamed as an attachment or not * @throws IOException */ - protected void streamContent(WebScriptRequest req, WebScriptResponse res, NodeRef nodeRef, QName propertyQName, - boolean attach) throws IOException + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + NodeRef nodeRef, + QName propertyQName, + boolean attach) throws IOException { - streamContent(req, res, nodeRef, propertyQName, attach, null); + streamContent(req, res, nodeRef, propertyQName, attach, null, null); + } + + /** + * 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 + * @throws IOException + */ + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + NodeRef nodeRef, + QName propertyQName, + boolean attach, + Map model) throws IOException + { + streamContent(req, res, nodeRef, propertyQName, attach, null, model); } /** @@ -326,8 +349,33 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attachFileName Optional file name to use when attach is true * @throws IOException */ - protected void streamContent(WebScriptRequest req, WebScriptResponse res, NodeRef nodeRef, QName propertyQName, - boolean attach, String attachFileName) throws IOException + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + NodeRef nodeRef, + QName propertyQName, + boolean attach, + String attachFileName) throws IOException + { + streamContent(req, res, nodeRef, propertyQName, attach, attachFileName, null); + } + /** + * 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 + */ + protected 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 + ")"); @@ -381,7 +429,7 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw } // Stream the content - streamContentImpl(req, res, reader, attach, modified, modified == null ? null : String.valueOf(modified.getTime()), attachFileName); + streamContentImpl(req, res, reader, attach, modified, modified == null ? null : String.valueOf(modified.getTime()), attachFileName, model); } /** @@ -393,10 +441,30 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attach Indicates whether the content should be streamed as an attachment or not * @throws IOException */ - protected void streamContent(WebScriptRequest req, WebScriptResponse res, String resourcePath, - boolean attach) throws IOException + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + String resourcePath, + boolean attach) throws IOException { - streamContent(req, res, resourcePath, attach, null); + streamContent(req, res, resourcePath, attach, null, null); + } + + /** + * Streams content back to client from a given resource path. + * + * @param req The request + * @param res The response + * @param resourcePath The classpath resource path the content is required for + * @param attach Indicates whether the content should be streamed as an attachment or not + * @throws IOException + */ + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + String resourcePath, + boolean attach, + Map model) throws IOException + { + streamContent(req, res, resourcePath, attach, null, model); } /** @@ -409,8 +477,30 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attachFileName Optional file name to use when attach is true * @throws IOException */ - protected void streamContent(WebScriptRequest req, WebScriptResponse res, String resourcePath, - boolean attach, String attachFileName) throws IOException + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + String resourcePath, + boolean attach, + String attachFileName) throws IOException + { + streamContent(req, res, resourcePath, attach, attachFileName, null); + } + /** + * Streams content back to client from a given resource path. + * + * @param req The request + * @param res The response + * @param resourcePath The classpath resource path the content is required for. + * @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 + */ + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + String resourcePath, + boolean attach, + String attachFileName, + Map model) throws IOException { if (logger.isDebugEnabled()) logger.debug("Retrieving content from resource path " + resourcePath + " (attach: " + attach + ")"); @@ -437,7 +527,7 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw FileCopyUtils.copy(is, os); // stream the contents of the file, but using the modifiedDate of the original resource. - streamContent(req, res, file, resourceLastModified, attach, attachFileName); + streamContent(req, res, file, resourceLastModified, attach, attachFileName, model); } /** @@ -449,10 +539,30 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attach Indicates whether the content should be streamed as an attachment or not * @throws IOException */ - protected void streamContent(WebScriptRequest req, WebScriptResponse res, File file, boolean attach) - throws IOException + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + File file, + boolean attach) throws IOException { - streamContent(req, res, file, attach, null); + streamContent(req, res, file, attach, null, null); + } + + /** + * Streams content back to client from a given resource path. + * + * @param req The request + * @param res The response + * @param resourcePath The resource path the content is required for + * @param attach Indicates whether the content should be streamed as an attachment or not + * @throws IOException + */ + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + File file, + boolean attach, + Map model) throws IOException + { + streamContent(req, res, file, attach, null, model); } /** @@ -466,10 +576,34 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attachFileName Optional file name to use when attach is true * @throws IOException */ - protected void streamContent(WebScriptRequest req, WebScriptResponse res, File file, boolean attach, - String attachFileName) throws IOException + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + File file, + boolean attach, + String attachFileName) throws IOException { - streamContent(req, res, file, null, attach, attachFileName); + streamContent(req, res, file, null, attach, attachFileName, null); + } + + /** + * Streams content back to client from a given File. The Last-Modified header will reflect the + * given file's modification timestamp. + * + * @param req The request + * @param res The response + * @param file The file whose content is to be streamed. + * @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 + */ + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + File file, + boolean attach, + String attachFileName, + Map model) throws IOException + { + streamContent(req, res, file, null, attach, attachFileName, model); } /** @@ -484,8 +618,34 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attachFileName Optional file name to use when attach is true * @throws IOException */ - protected void streamContent(WebScriptRequest req, WebScriptResponse res, File file, Long modifiedTime, - boolean attach, String attachFileName) throws IOException + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + File file, + Long modifiedTime, + boolean attach, + String attachFileName) throws IOException + { + streamContent(req, res, file, modifiedTime, attach, attachFileName, null); + } + /** + * Streams content back to client from a given File. + * + * @param req The request + * @param res The response + * @param file The file whose content is to be streamed. + * @param modifiedTime The modified datetime to use for the streamed content. If null the + * file's timestamp will be used. + * @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 + */ + protected void streamContent(WebScriptRequest req, + WebScriptResponse res, + File file, + Long modifiedTime, + boolean attach, + String attachFileName, + Map model) throws IOException { if (logger.isDebugEnabled()) logger.debug("Retrieving content from file " + file.getAbsolutePath() + " (attach: " + attach + ")"); @@ -507,8 +667,7 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw long lastModified = modifiedTime == null ? file.lastModified() : modifiedTime; Date lastModifiedDate = new Date(lastModified); - streamContentImpl(req, res, reader, attach, lastModifiedDate, - String.valueOf(lastModifiedDate.getTime()), attachFileName); + streamContentImpl(req, res, reader, attach, lastModifiedDate, String.valueOf(lastModifiedDate.getTime()), attachFileName, model); } /** @@ -523,8 +682,36 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw * @param attachFileName Optional file name to use when attach is true * @throws IOException */ - protected void streamContentImpl(WebScriptRequest req, WebScriptResponse res, ContentReader reader, boolean attach, - Date modified, String eTag, String attachFileName) throws IOException + protected void streamContentImpl(WebScriptRequest req, + WebScriptResponse res, + ContentReader reader, + boolean attach, + Date modified, + String eTag, + String attachFileName) throws IOException + { + streamContentImpl(req, res, reader, attach, modified, eTag, attachFileName, null); + } + /** + * Stream content implementation + * + * @param req The request + * @param res The response + * @param reader The reader + * @param attach Indicates whether the content should be streamed as an attachment or not + * @param modified Modified date of content + * @param eTag ETag to use + * @param attachFileName Optional file name to use when attach is true + * @throws IOException + */ + protected void streamContentImpl(WebScriptRequest req, + WebScriptResponse res, + ContentReader reader, + boolean attach, + Date modified, + String eTag, + String attachFileName, + Map model) throws IOException { setAttachment(res, attach, attachFileName); @@ -548,13 +735,7 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw res.setHeader("Content-Length", Long.toString(reader.getSize())); // set caching - Cache cache = new Cache(); - cache.setNeverCache(false); - cache.setMustRevalidate(true); - cache.setMaxAge(0L); - cache.setLastModified(modified); - cache.setETag(eTag); - res.setCache(cache); + setResponseCache(res, modified, eTag, model); // get the content and stream directly to the response output stream // assuming the repository is capable of streaming in chunks, this should allow large files @@ -576,6 +757,36 @@ public class StreamContent extends AbstractWebScript implements ResourceLoaderAw } } + /** + * Set the cache settings on the response + * + * @param res + * @param modified + * @param eTag + */ + 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")) + { + cache.setNeverCache(false); + cache.setMustRevalidate(true); + cache.setMaxAge(0L); + cache.setLastModified(modified); + cache.setETag(eTag); + } + else + { + cache.setNeverCache(false); + cache.setMustRevalidate(false); + cache.setMaxAge(Long.MAX_VALUE); + cache.setLastModified(modified); + cache.setETag(eTag); + res.setCache(cache); + } + res.setCache(cache); + } + /** * Set attachment header * diff --git a/source/java/org/alfresco/repo/web/scripts/subscriptions/AbstractSubscriptionServiceWebScript.java b/source/java/org/alfresco/repo/web/scripts/subscriptions/AbstractSubscriptionServiceWebScript.java index 7896e3aefe..1610e67498 100644 --- a/source/java/org/alfresco/repo/web/scripts/subscriptions/AbstractSubscriptionServiceWebScript.java +++ b/source/java/org/alfresco/repo/web/scripts/subscriptions/AbstractSubscriptionServiceWebScript.java @@ -26,6 +26,7 @@ import java.util.List; import org.alfresco.model.ContentModel; import org.alfresco.query.PagingRequest; +import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.NoSuchPersonException; @@ -173,6 +174,18 @@ public abstract class AbstractSubscriptionServiceWebScript extends AbstractWebSc statusTimeJson.put("iso8601", ISO8601DateFormat.format(statusTime)); result.put("userStatusTime", statusTimeJson); } + + // Get the avatar for the user id if one is available + List assocRefs = this.nodeService.getTargetAssocs(node, ContentModel.ASSOC_AVATAR); + if (!assocRefs.isEmpty()) + { + NodeRef avatarNodeRef = assocRefs.get(0).getTargetRef(); + result.put("avatar", avatarNodeRef.toString()); + } + else + { + result.put("avatar", "avatar"); // This indicates to just use a placeholder + } return result; }