Merged THOR1_SPRINTS to HEAD

Performance improvement: prevent unnecessary 304 revalidation requests for thumbnails in detailed view of My-Documents and Recently Modified Documents dashlets
   Fixed bean config problem (caused by r34662)
   Fix build break
   Refactored revalidation code to remove previously added WebScripts that are now surplus to requirements
   Performance improvement: prevent unnecessary 304 revalidation for avatars on site colleagues dashlet
   Performance improvement: prevent unnecessary 304 revalidation for avatars on following/follwers pages
   Performance improvement: prevent unnecessary 304 revalidation for avatars in activity feeds
   Performance improvement: prevent unecessary 304 revalidation for user avatar thumbnails in header WebScript
   Prevent 304 revalidations for unchanged thumbnails in document library, web preview and search

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@34698 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Draper
2012-03-22 16:44:48 +00:00
parent 65b08c4805
commit 262f7c8da6
8 changed files with 343 additions and 64 deletions

View File

@@ -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('://','/')}</#assign>
"avatar": "${"api/node/" + avatarNodeRef + "/content/thumbnails/avatar"}",
"avatarNode": "${avatarNodeRef}",
</#if>
<#if authority.properties.jobtitle??>
"jobtitle": "${authority.properties.jobtitle}",

View File

@@ -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.
</description>
<url>/api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}?c={queueforcecreate?}&amp;ph={placeholder?}&amp;lastModified={modified?}</url>
<url>/api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}?c={queueforcecreate?}&amp;ph={placeholder?}</url>
<url>/api/path/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}?c={queueforcecreate?}&amp;ph={placeholder?}</url>
<url>/api/node/{store_type}/{store_id}/{id}/content{property}/thumbnails/{thumbnailname}/{filename}?c={queueforcecreate?}&amp;ph={placeholder?}</url>

View File

@@ -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("/"));

View File

@@ -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}",
</#if>
</#list>
</#if>
"lockedBy": "${lockedBy}",
"lockedByUser": "${lockedByUser}",
"size": "${node.size?c}",

View File

@@ -3,6 +3,10 @@
<description>
Returns a user avatar image in the format specified by the thumbnailname, or the "avatar" preset if omitted.
</description>
<url>/slingshot/profile/avatar/avatar</url>
<url>/slingshot/profile/avatar/avatar/thumbnail/{thumbnailname}</url>
<url>/slingshot/profile/avatar/{store_type}/{store_id}/{id}/thumbnail/{thumbnailname}</url>
<url>/slingshot/profile/avatar/{store_type}/{store_id}/{id}</url>
<url>/slingshot/profile/avatar/{username}/thumbnail/{thumbnailname}</url>
<url>/slingshot/profile/avatar/{username}</url>
<format default="">argument</format>

View File

@@ -24,21 +24,51 @@ function main()
{
var userName = url.templateArgs.username,
thumbnailName = url.templateArgs.thumbnailname || "avatar",
person = people.getPerson(userName);
avatarNode;
// 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)
{
// 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;
}
else if (url.templateArgs.store_type == null && url.templateArgs.store_id == null && url.templateArgs.id == 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)
{
// 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)
{
var avatarNode = avatarAssoc[0];
avatarNode = avatarAssoc[0];
}
}
}
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
@@ -66,7 +96,6 @@ function main()
return;
}
}
}
// Stream the placeholder image
model.contentPath = getPlaceholder(thumbnailName);

View File

@@ -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,
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<String, Object> 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 <code>true</code>
* @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 <code>true</code>
* @throws IOException
*/
protected void streamContent(WebScriptRequest req,
WebScriptResponse res,
NodeRef nodeRef,
QName propertyQName,
boolean attach,
String attachFileName,
Map<String, Object> 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,
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<String, Object> 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 <code>true</code>
* @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 <code>true</code>
* @throws IOException
*/
protected void streamContent(WebScriptRequest req,
WebScriptResponse res,
String resourcePath,
boolean attach,
String attachFileName,
Map<String, Object> 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<String, Object> 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 <code>true</code>
* @throws IOException
*/
protected void streamContent(WebScriptRequest req, WebScriptResponse res, File file, boolean attach,
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 <code>true</code>
* @throws IOException
*/
protected void streamContent(WebScriptRequest req,
WebScriptResponse res,
File file,
boolean attach,
String attachFileName,
Map<String, Object> 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 <code>true</code>
* @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 <tt>null</tt> 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 <code>true</code>
* @throws IOException
*/
protected void streamContent(WebScriptRequest req,
WebScriptResponse res,
File file,
Long modifiedTime,
boolean attach,
String attachFileName,
Map<String, Object> 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 <code>true</code>
* @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 <code>true</code>
* @throws IOException
*/
protected void streamContentImpl(WebScriptRequest req,
WebScriptResponse res,
ContentReader reader,
boolean attach,
Date modified,
String eTag,
String attachFileName,
Map<String, Object> 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<String, Object> 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
*

View File

@@ -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;
@@ -174,6 +175,18 @@ public abstract class AbstractSubscriptionServiceWebScript extends AbstractWebSc
result.put("userStatusTime", statusTimeJson);
}
// Get the avatar for the user id if one is available
List<AssociationRef> 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;
}