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); + } + } + } + } + } + } }