From 508c880dea8846ac3e7a26bebe509c8d9f3e4616 Mon Sep 17 00:00:00 2001 From: David Draper Date: Thu, 22 Mar 2012 16:44:48 +0000 Subject: [PATCH] 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 --- config/alfresco/model/contentModel.xml | 11 +++ .../default/activities-feed-context.xml | 2 + .../repo/activities/ActivityServiceImpl.java | 57 ++++++++++++++ .../domain/activities/ActivityFeedEntity.java | 17 ++++ .../jscript/app/JSONConversionComponent.java | 5 ++ .../repo/thumbnail/ThumbnailServiceImpl.java | 77 +++++++++++++++++++ 6 files changed, 169 insertions(+) diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index 3485eb18ba..23952804b8 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -1355,6 +1355,17 @@ + + Thumbnail Modification Data + + + Last thumbnail modifcation data + d:text + true + + + + diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml index 72c3c8caaf..c816d437b7 100644 --- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml @@ -19,6 +19,8 @@ + + diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java index a4e794035a..9499794379 100644 --- a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -20,10 +20,14 @@ package org.alfresco.repo.activities; import java.sql.SQLException; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; import org.alfresco.repo.activities.feed.cleanup.FeedCleaner; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; @@ -35,11 +39,16 @@ import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.activities.ActivityPostService; import org.alfresco.service.cmr.activities.ActivityService; import org.alfresco.service.cmr.activities.FeedControl; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONException; @@ -62,6 +71,8 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean private TenantService tenantService; private SiteService siteService; private ActivityPostService activityPostService; + private PersonService personService; + private NodeService nodeService; private int maxFeedItems = 100; @@ -112,6 +123,16 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean this.activityPostService = activityPostService; } + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + /*(non-Javadoc) * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() @@ -253,6 +274,12 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean } List activityFeeds = feedDAO.selectUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers, minFeedId, maxFeedItems); + + // Create a local cache just for this method to map IDs of users to their avatar NodeRef. This + // is local to the method because we only want to cache per request - there is not point in keeping + // an instance cache because the data will become stale if a user changes their avatar. + Map userIdToAvatarNodeRefCache = new HashMap(); + for (ActivityFeedEntity activityFeed : activityFeeds) { @@ -264,6 +291,36 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean continue; } + // In order to prevent unnecessary 304 revalidations on user avatars in the activity stream the + // activity posting user avatars will be retrieved and added to the activity feed. This will enable + // avatars to be requested using the unique nodeRef which can be safely cached by the browser and + // improve performance... + if (userIdToAvatarNodeRefCache.containsKey(activityFeed.getPostUserId())) + { + // If we've previously cached the users avatar, or if we've determine that the user doesn't + // have an avatar then use the cached data. + activityFeed.setPostUserAvatarNodeRef(userIdToAvatarNodeRefCache.get(activityFeed.getPostUserId())); + } + else + { + // Get the avatar for the user id, set it in the activity feed and update the cache + NodeRef avatarNodeRef = null; + NodeRef postPerson = this.personService.getPerson(activityFeed.getPostUserId()); + List assocRefs = this.nodeService.getTargetAssocs(postPerson, ContentModel.ASSOC_AVATAR); + if (!assocRefs.isEmpty()) + { + avatarNodeRef = assocRefs.get(0).getTargetRef(); + activityFeed.setPostUserAvatarNodeRef(avatarNodeRef); + } + else + { + activityFeed.setPostUserAvatarNodeRef(null); + } + + // Update the cache (setting null if there is no avatar for the user)... + userIdToAvatarNodeRefCache.put(activityFeed.getPostUserId(), avatarNodeRef); + } + activityFeed.setSiteNetwork(tenantService.getBaseName(activityFeed.getSiteNetwork())); result.add(activityFeed); } diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java index e92aa028ff..7727dfd43b 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedEntity.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import org.alfresco.repo.activities.feed.FeedTaskProcessor; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.JSONtoFmModel; import org.json.JSONException; import org.json.JSONObject; @@ -37,6 +38,7 @@ public class ActivityFeedEntity public static final String KEY_ACTIVITY_FEED_ID = "id"; public static final String KEY_ACTIVITY_FEED_POST_DATE = "postDate"; public static final String KEY_ACTIVITY_FEED_POST_USERID = "postUserId"; + public static final String KEY_ACTIVITY_FEED_POST_USER_AVATAR_NODE = "postUserAvatar"; public static final String KEY_ACTIVITY_FEED_USERID = "feedUserId"; public static final String KEY_ACTIVITY_FEED_SITE = "siteNetwork"; public static final String KEY_ACTIVITY_FEED_TYPE = "activityType"; @@ -49,6 +51,7 @@ public class ActivityFeedEntity private String activitySummaryFormat; private String feedUserId; private String postUserId; + private NodeRef postUserAvatarNodeRef; private String siteNetwork; private String appTool; private Date postDate; @@ -155,6 +158,16 @@ public class ActivityFeedEntity this.feedDate = feedDate; } + public NodeRef getPostUserAvatarNodeRef() + { + return postUserAvatarNodeRef; + } + + public void setPostUserAvatarNodeRef(NodeRef postUserAvatarNodeRef) + { + this.postUserAvatarNodeRef = postUserAvatarNodeRef; + } + public String getAppTool() { return appTool; @@ -172,6 +185,10 @@ public class ActivityFeedEntity jo.put(KEY_ACTIVITY_FEED_ID, id); jo.put(KEY_ACTIVITY_FEED_POST_USERID, postUserId); + if (postUserAvatarNodeRef != null) + { + jo.put(KEY_ACTIVITY_FEED_POST_USER_AVATAR_NODE, postUserAvatarNodeRef.toString()); + } jo.put(KEY_ACTIVITY_FEED_POST_DATE, ISO8601DateFormat.format(postDate)); if (getFeedUserId() != null) { jo.put(KEY_ACTIVITY_FEED_USERID, getFeedUserId()); } // eg. site feed diff --git a/source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java b/source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java index 32ec674257..d498ab007b 100644 --- a/source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java +++ b/source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java @@ -7,6 +7,7 @@ import java.io.Serializable; import java.text.MessageFormat; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -301,6 +302,10 @@ public class JSONConversionComponent dateObj.put("iso8601", JSONObject.escape(ISO8601DateFormat.format((Date)value))); propertiesJSON.put(key, dateObj); } + else if (value instanceof List) + { + propertiesJSON.put(key, value); + } else { propertiesJSON.put(key, value.toString()); diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java index 86151dc432..5dcc2e669a 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java @@ -189,6 +189,9 @@ public class ThumbnailServiceImpl implements ThumbnailService, // When a thumbnail succeeds, we must delete any existing thumbnail failure nodes. String thumbnailName = (String) nodeService.getProperty(childAssoc.getChildRef(), ContentModel.PROP_NAME); + // Update the parent node with the thumbnail update... + addThumbnailModificationData(childAssoc.getChildRef(), thumbnailName); + // In fact there should only be zero or one such failedThumbnails Map failures = getFailedThumbnails(childAssoc.getParentRef()); FailedThumbnailInfo existingFailedThumbnail = failures.get(thumbnailName); @@ -610,4 +613,78 @@ public class ThumbnailServiceImpl implements ThumbnailService, } } } + + /** + *

Updates the parent of the supplied {@link NodeRef} to ensure that it has the "cm:thumbnailModification" aspect + * and sets the last modification data for it.

+ * @param nodeRef A {@link NodeRef} representing a thumbnail to provide last modification data for. + */ + @SuppressWarnings("unchecked") + public void addThumbnailModificationData(NodeRef nodeRef, String thumbnailName) + { + if (nodeService.exists(nodeRef)) + { + if (thumbnailName != null && !nodeRef.toString().endsWith(thumbnailName)) + { + Date modified = (Date)nodeService.getProperty(nodeRef, ContentModel.PROP_MODIFIED); + if (modified != null) + { + // Get the last modified value as a timestamp... + Long timestamp = modified.getTime(); + + // Create the value we want to set... + String lastModifiedValue = thumbnailName + ":" + timestamp; + + // Get the parent node (there should be only one) and apply the aspect and + // set the property to indicate which thumbnail the checksum refers to... + for (ChildAssociationRef parent: nodeService.getParentAssocs(nodeRef)) + { + List thumbnailMods = null; + + NodeRef parentNode = parent.getParentRef(); + if (nodeService.hasAspect(parentNode, ContentModel.ASPECT_THUMBNAIL_MODIFICATION)) + { + // The node already has the aspect, check to see if the current thumbnail modification exists... + thumbnailMods = (List) nodeService.getProperty(parentNode, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA); + + // If we have previously set last modified thumbnail data then it will exist as part of the multi-value + // property. The value will consist of the "cm:thumbnailName" value delimited with a ":" and then the + // timestamp. We need to find the appropriate entry in the multivalue property and then update it + String target = null; + for (String currThumbnailMod: thumbnailMods) + { + if (currThumbnailMod.startsWith(thumbnailName)) + { + target = currThumbnailMod; + } + } + + // Remove the previous value + if (target != null) + { + thumbnailMods.remove(target); + } + + // Add the timestamp... + thumbnailMods.add(lastModifiedValue); + + // Set the property... + nodeService.setProperty(parentNode, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA, (Serializable) thumbnailMods); + } + else + { + // If the aspect has not previously been added then we'll need to set it now... + thumbnailMods = new ArrayList(); + thumbnailMods.add(lastModifiedValue); + + // Add the aspect with the new property... + Map properties = new HashMap(); + properties.put(ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA, (Serializable) thumbnailMods); + nodeService.addAspect(parentNode, ContentModel.ASPECT_THUMBNAIL_MODIFICATION, properties); + } + } + } + } + } + } }