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 728d0a5458
commit 508c880dea
6 changed files with 169 additions and 0 deletions

View File

@@ -1355,6 +1355,17 @@
</associations> </associations>
</aspect> </aspect>
<aspect name="cm:thumbnailModification">
<title>Thumbnail Modification Data</title>
<properties>
<property name="cm:lastThumbnailModification">
<title>Last thumbnail modifcation data</title>
<type>d:text</type>
<multiple>true</multiple>
</property>
</properties>
</aspect>
<!-- --> <!-- -->
<!-- EXIF --> <!-- EXIF -->
<!-- --> <!-- -->

View File

@@ -19,6 +19,8 @@
<property name="tenantService" ref="tenantService"/> <property name="tenantService" ref="tenantService"/>
<property name="siteService" ref="siteService"/> <property name="siteService" ref="siteService"/>
<property name="activityPostService" ref="activityPostService"/> <property name="activityPostService" ref="activityPostService"/>
<property name="nodeService" ref="NodeService" />
<property name="personService" ref="PersonService"/>
<property name="userNamesAreCaseSensitive" value="${user.name.caseSensitive}"/> <property name="userNamesAreCaseSensitive" value="${user.name.caseSensitive}"/>
<property name="maxFeedItems" value="${activities.feed.max.size}"/> <property name="maxFeedItems" value="${activities.feed.max.size}"/>
</bean> </bean>

View File

@@ -20,10 +20,14 @@ package org.alfresco.repo.activities;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.activities.feed.cleanup.FeedCleaner; import org.alfresco.repo.activities.feed.cleanup.FeedCleaner;
import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedDAO;
import org.alfresco.repo.domain.activities.ActivityFeedEntity; 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.ActivityPostService;
import org.alfresco.service.cmr.activities.ActivityService; import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.activities.FeedControl; 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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AuthorityService; 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.SiteInfo;
import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.json.JSONException; import org.json.JSONException;
@@ -62,6 +71,8 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean
private TenantService tenantService; private TenantService tenantService;
private SiteService siteService; private SiteService siteService;
private ActivityPostService activityPostService; private ActivityPostService activityPostService;
private PersonService personService;
private NodeService nodeService;
private int maxFeedItems = 100; private int maxFeedItems = 100;
@@ -112,6 +123,16 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean
this.activityPostService = activityPostService; this.activityPostService = activityPostService;
} }
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/*(non-Javadoc) /*(non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
@@ -254,6 +275,12 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean
List<ActivityFeedEntity> activityFeeds = feedDAO.selectUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers, minFeedId, maxFeedItems); List<ActivityFeedEntity> 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<String, NodeRef> userIdToAvatarNodeRefCache = new HashMap<String, NodeRef>();
for (ActivityFeedEntity activityFeed : activityFeeds) for (ActivityFeedEntity activityFeed : activityFeeds)
{ {
if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) { if (actvityFilter != null && !actvityFilter.contains(activityFeed.getActivityType())) {
@@ -264,6 +291,36 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean
continue; 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<AssociationRef> 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())); activityFeed.setSiteNetwork(tenantService.getBaseName(activityFeed.getSiteNetwork()));
result.add(activityFeed); result.add(activityFeed);
} }

View File

@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.alfresco.repo.activities.feed.FeedTaskProcessor; import org.alfresco.repo.activities.feed.FeedTaskProcessor;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.JSONtoFmModel; import org.alfresco.util.JSONtoFmModel;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; 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_ID = "id";
public static final String KEY_ACTIVITY_FEED_POST_DATE = "postDate"; 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_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_USERID = "feedUserId";
public static final String KEY_ACTIVITY_FEED_SITE = "siteNetwork"; public static final String KEY_ACTIVITY_FEED_SITE = "siteNetwork";
public static final String KEY_ACTIVITY_FEED_TYPE = "activityType"; public static final String KEY_ACTIVITY_FEED_TYPE = "activityType";
@@ -49,6 +51,7 @@ public class ActivityFeedEntity
private String activitySummaryFormat; private String activitySummaryFormat;
private String feedUserId; private String feedUserId;
private String postUserId; private String postUserId;
private NodeRef postUserAvatarNodeRef;
private String siteNetwork; private String siteNetwork;
private String appTool; private String appTool;
private Date postDate; private Date postDate;
@@ -155,6 +158,16 @@ public class ActivityFeedEntity
this.feedDate = feedDate; this.feedDate = feedDate;
} }
public NodeRef getPostUserAvatarNodeRef()
{
return postUserAvatarNodeRef;
}
public void setPostUserAvatarNodeRef(NodeRef postUserAvatarNodeRef)
{
this.postUserAvatarNodeRef = postUserAvatarNodeRef;
}
public String getAppTool() public String getAppTool()
{ {
return appTool; return appTool;
@@ -172,6 +185,10 @@ public class ActivityFeedEntity
jo.put(KEY_ACTIVITY_FEED_ID, id); jo.put(KEY_ACTIVITY_FEED_ID, id);
jo.put(KEY_ACTIVITY_FEED_POST_USERID, postUserId); 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)); jo.put(KEY_ACTIVITY_FEED_POST_DATE, ISO8601DateFormat.format(postDate));
if (getFeedUserId() != null) { jo.put(KEY_ACTIVITY_FEED_USERID, getFeedUserId()); } // eg. site feed if (getFeedUserId() != null) { jo.put(KEY_ACTIVITY_FEED_USERID, getFeedUserId()); } // eg. site feed

View File

@@ -7,6 +7,7 @@ import java.io.Serializable;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -301,6 +302,10 @@ public class JSONConversionComponent
dateObj.put("iso8601", JSONObject.escape(ISO8601DateFormat.format((Date)value))); dateObj.put("iso8601", JSONObject.escape(ISO8601DateFormat.format((Date)value)));
propertiesJSON.put(key, dateObj); propertiesJSON.put(key, dateObj);
} }
else if (value instanceof List)
{
propertiesJSON.put(key, value);
}
else else
{ {
propertiesJSON.put(key, value.toString()); propertiesJSON.put(key, value.toString());

View File

@@ -189,6 +189,9 @@ public class ThumbnailServiceImpl implements ThumbnailService,
// When a thumbnail succeeds, we must delete any existing thumbnail failure nodes. // When a thumbnail succeeds, we must delete any existing thumbnail failure nodes.
String thumbnailName = (String) nodeService.getProperty(childAssoc.getChildRef(), ContentModel.PROP_NAME); 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 // In fact there should only be zero or one such failedThumbnails
Map<String, FailedThumbnailInfo> failures = getFailedThumbnails(childAssoc.getParentRef()); Map<String, FailedThumbnailInfo> failures = getFailedThumbnails(childAssoc.getParentRef());
FailedThumbnailInfo existingFailedThumbnail = failures.get(thumbnailName); FailedThumbnailInfo existingFailedThumbnail = failures.get(thumbnailName);
@@ -610,4 +613,78 @@ public class ThumbnailServiceImpl implements ThumbnailService,
} }
} }
} }
/**
* <p>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.</p>
* @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<String> 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<String>) 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<String>();
thumbnailMods.add(lastModifiedValue);
// Add the aspect with the new property...
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
properties.put(ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA, (Serializable) thumbnailMods);
nodeService.addAspect(parentNode, ContentModel.ASPECT_THUMBNAIL_MODIFICATION, properties);
}
}
}
}
}
}
} }