Initial Subscription Cervice check-in

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28425 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Florian Mü
2011-06-16 11:57:04 +00:00
parent 2445f01771
commit a9eb35e67f
31 changed files with 2426 additions and 127 deletions

View File

@@ -235,7 +235,7 @@ public class ActivityPostServiceImpl implements ActivityPostService
private String getCurrentUser()
{
String userId = AuthenticationUtil.getFullyAuthenticatedUser();
String userId = AuthenticationUtil.getRunAsUser();
if ((userId != null) && (! userId.equals(AuthenticationUtil.SYSTEM_USER_NAME)) && (! userNamesAreCaseSensitive))
{
// user names are not case-sensitive

View File

@@ -19,12 +19,12 @@
package org.alfresco.repo.activities;
public interface ActivityType
{
{
// pre-defined alfresco activity types
// generic fallback (if specific template is missing)
public final String GENERIC_FALLBACK = "org.alfresco.generic";
// site membership
public final String SITE_USER_JOINED = "org.alfresco.site.user-joined";
public final String SITE_USER_REMOVED = "org.alfresco.site.user-left";
@@ -32,4 +32,6 @@ public interface ActivityType
public final String SITE_GROUP_ADDED = "org.alfresco.site.group-added";
public final String SITE_GROUP_REMOVED = "org.alfresco.site.group-removed";
public final String SITE_GROUP_ROLE_UPDATE = "org.alfresco.site.group-role-changed";
public final String SUBSCRIPTIONS_SUBSCRIBE = "org.alfresco.subscriptions.subscribed";
public final String SUBSCRIPTIONS_FOLLOW = "org.alfresco.subscriptions.followed";
}

View File

@@ -63,6 +63,7 @@ import freemarker.template.TemplateException;
public abstract class FeedTaskProcessor
{
private static final Log logger = LogFactory.getLog(FeedTaskProcessor.class);
public static final String FEED_FORMAT_JSON = "json";
public static final String FEED_FORMAT_ATOMENTRY = "atomentry";
@@ -117,6 +118,7 @@ public abstract class FeedTaskProcessor
Map<String, List<String>> activityTemplates = new HashMap<String, List<String>>(10);
Map<String, Set<String>> siteConnectedUsers = new TreeMap<String, Set<String>>();
Map<String, Set<String>> followers = new TreeMap<String, Set<String>>();
Map<String, List<FeedControlEntity>> userFeedControls = new HashMap<String, List<FeedControlEntity>>();
Map<String, Template> templateCache = new TreeMap<String, Template>();
@@ -207,15 +209,6 @@ public abstract class FeedTaskProcessor
String thisSite = (activityPost.getSiteNetwork() != null ? activityPost.getSiteNetwork() : "");
if (thisSite.length() == 0)
{
// note: although we allow posts without site id - we currently require site context to generate feeds for site members (hence skip here with warning)
// (also Share currently only posts activities within site context)
logger.warn(">>> Skipping activity post " + activityPost.getId() + " since no site");
updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.PROCESSED);
continue;
}
model.put(ActivityFeedEntity.KEY_ACTIVITY_FEED_TYPE, activityPost.getActivityType());
model.put(ActivityFeedEntity.KEY_ACTIVITY_FEED_SITE, thisSite);
model.put("userId", activityPost.getUserId());
@@ -224,49 +217,84 @@ public abstract class FeedTaskProcessor
model.put("xmldate", new ISO8601DateFormatMethod());
model.put("repoEndPoint", ctx.getRepoEndPoint());
// Get the members of this site - save hammering the repository by reusing cached site members
Set<String> connectedUsers = siteConnectedUsers.get(thisSite);
if (connectedUsers == null)
{
// Recipients of this post
Set<String> recipients = new HashSet<String>();
// Add site members to recipient list
if (thisSite.length() > 0)
{
// Get the members of this site - save hammering the repository by reusing cached site members
Set<String> connectedUsers = siteConnectedUsers.get(thisSite);
if (connectedUsers == null)
{
try
{
// Repository callback to get site members
connectedUsers = getSiteMembers(ctx, thisSite);
connectedUsers.add(""); // add empty posting userid - to represent site feed !
}
catch(Exception e)
{
logger.error("Skipping activity post " + activityPost.getId() + " since failed to get site members: " + e);
updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.ERROR);
continue;
}
// Cache them for future use in this same invocation
siteConnectedUsers.put(thisSite, connectedUsers);
}
recipients.addAll(connectedUsers);
}
// Add followers to recipient list
Set<String> followerUsers = followers.get(activityPost.getUserId());
if(followerUsers == null) {
try
{
// Repository callback to get site members
connectedUsers = getSiteMembers(ctx, thisSite);
connectedUsers.add(""); // add empty posting userid - to represent site feed !
followerUsers = getFollowers(activityPost.getUserId());
}
catch(Exception e)
{
logger.error("Skipping activity post " + activityPost.getId() + " since failed to get site members: " + e);
logger.error("Skipping activity post " + activityPost.getId() + " since failed to get followers: " + e);
updatePostStatus(activityPost.getId(), ActivityPostEntity.STATUS.ERROR);
continue;
}
// Cache them for future use in this same invocation
siteConnectedUsers.put(thisSite, connectedUsers);
followers.put(activityPost.getUserId(), followerUsers);
}
recipients.addAll(followerUsers);
if(recipients.size() == 0) {
if (logger.isDebugEnabled())
{
logger.debug("No recipients for activity post " + activityPost.getId() + ".");
}
return;
}
try
{
startTransaction();
if (logger.isTraceEnabled())
{
logger.trace("Process: " + connectedUsers.size() + " candidate connections for activity post " + activityPost.getId());
logger.trace("Process: " + recipients.size() + " candidate connections for activity post " + activityPost.getId());
}
int excludedConnections = 0;
for (String connectedUser : connectedUsers)
for (String recipient : recipients)
{
List<FeedControlEntity> feedControls = null;
if (! connectedUser.equals(""))
if (! recipient.equals(""))
{
// Get user's feed controls
feedControls = userFeedControls.get(connectedUser);
feedControls = userFeedControls.get(recipient);
if (feedControls == null)
{
feedControls = getFeedControls(connectedUser);
userFeedControls.put(connectedUser, feedControls);
feedControls = getFeedControls(recipient);
userFeedControls.put(recipient, feedControls);
}
}
@@ -278,7 +306,7 @@ public abstract class FeedTaskProcessor
else
{
// read permission check
if (! canRead(ctx, connectedUser, model))
if (! canRead(ctx, recipient, model))
{
excludedConnections++;
continue;
@@ -306,7 +334,7 @@ public abstract class FeedTaskProcessor
ActivityFeedEntity feed = new ActivityFeedEntity();
// Generate activity feed summary
feed.setFeedUserId(connectedUser);
feed.setFeedUserId(recipient);
feed.setPostUserId(postingUserId);
feed.setActivityType(activityType);
@@ -356,7 +384,7 @@ public abstract class FeedTaskProcessor
if (logger.isDebugEnabled())
{
logger.debug("Processed: " + (connectedUsers.size() - excludedConnections) + " connections for activity post " + activityPost.getId() + " (excluded " + excludedConnections + ")");
logger.debug("Processed: " + (recipients.size() - excludedConnections) + " connections for activity post " + activityPost.getId() + " (excluded " + excludedConnections + ")");
}
}
finally
@@ -381,24 +409,23 @@ public abstract class FeedTaskProcessor
logger.info(sb.toString());
}
}
public abstract void startTransaction() throws SQLException;
public abstract void commitTransaction() throws SQLException;
public abstract void rollbackTransaction() throws SQLException;
public abstract void endTransaction() throws SQLException;
public abstract List<ActivityPostEntity> selectPosts(ActivityPostEntity selector) throws SQLException;
public abstract List<FeedControlEntity> selectUserFeedControls(String userId) throws SQLException;
public abstract long insertFeedEntry(ActivityFeedEntity feed) throws SQLException;
public abstract int updatePostStatus(long id, ActivityPostEntity.STATUS status) throws SQLException;
protected String callWebScript(String urlString, String ticket) throws MalformedURLException, URISyntaxException, IOException
{
URL url = new URL(urlString);
@@ -450,7 +477,7 @@ public abstract class FeedTaskProcessor
return result;
}
protected Set<String> getSiteMembers(RepoCtx ctx, String siteId) throws Exception
{
Set<String> members = new HashSet<String>();
@@ -481,12 +508,14 @@ public abstract class FeedTaskProcessor
return members;
}
protected abstract Set<String> getFollowers(String userId) throws Exception;
protected boolean canRead(RepoCtx ctx, final String connectedUser, Map<String, Object> model) throws Exception
{
throw new UnsupportedOperationException("FeedTaskProcessor: Remote callback for 'canRead' not implemented");
}
protected Map<String, List<String>> getActivityTypeTemplates(String repoEndPoint, String ticket, String subPath) throws Exception
{
StringBuffer sbUrl = new StringBuffer();
@@ -516,7 +545,7 @@ public abstract class FeedTaskProcessor
return getActivityTemplates(allTemplateNames);
}
protected Map<String, List<String>> getActivityTemplates(List<String> allTemplateNames)
{
Map<String, List<String>> activityTemplates = new HashMap<String, List<String>>(10);
@@ -557,21 +586,21 @@ public abstract class FeedTaskProcessor
return activityTemplates;
}
protected Configuration getFreemarkerConfiguration(RepoCtx ctx)
{
Configuration cfg = new Configuration();
cfg.setObjectWrapper(new DefaultObjectWrapper());
// custom template loader
cfg.setTemplateLoader(new TemplateWebScriptLoader(ctx.getRepoEndPoint(), ctx.getTicket()));
// TODO review i18n
cfg.setLocalizedLookup(false);
return cfg;
}
protected String processFreemarker(Map<String, Template> templateCache, String fmTemplate, Configuration cfg, Map<String, Object> model) throws IOException, TemplateException, Exception
{
// Save on lots of modification date checking by caching templates locally
@@ -587,12 +616,12 @@ public abstract class FeedTaskProcessor
return textWriter.toString();
}
protected List<FeedControlEntity> getFeedControls(String connectedUser) throws SQLException
{
return selectUserFeedControls(connectedUser);
}
protected boolean acceptActivity(ActivityPostEntity activityPost, List<FeedControlEntity> feedControls)
{
if (feedControls == null)
@@ -632,7 +661,7 @@ public abstract class FeedTaskProcessor
return true;
}
protected void addMissingFormats(String activityType, List<String> fmTemplates, List<String> templatesToAdd)
{
for (String templateToAdd : templatesToAdd)
@@ -666,7 +695,7 @@ public abstract class FeedTaskProcessor
}
}
}
protected String getTemplateSubPath(String activityType)
{
return (! activityType.startsWith("/") ? "/" : "") + activityType.replace(".", "/");

View File

@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.query.PagingRequest;
import org.alfresco.repo.activities.feed.FeedTaskProcessor;
import org.alfresco.repo.activities.feed.RepoCtx;
import org.alfresco.repo.activities.post.lookup.PostLookup;
@@ -46,6 +47,8 @@ import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.subscriptions.PagingFollowingResults;
import org.alfresco.service.cmr.subscriptions.SubscriptionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
@@ -64,117 +67,123 @@ import freemarker.template.DefaultObjectWrapper;
public class LocalFeedTaskProcessor extends FeedTaskProcessor implements ApplicationContextAware
{
private static final Log logger = LogFactory.getLog(LocalFeedTaskProcessor.class);
private ActivityPostDAO postDAO;
private ActivityFeedDAO feedDAO;
private FeedControlDAO feedControlDAO;
// can call locally (instead of remote repo callback)
private SiteService siteService;
private NodeService nodeService;
private ContentService contentService;
private PermissionService permissionService;
private SubscriptionService subscriptionService;
private String defaultEncoding;
private List<String> templateSearchPaths;
private boolean useRemoteCallbacks;
private ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
public void setPostDAO(ActivityPostDAO postDAO)
{
this.postDAO = postDAO;
}
public void setFeedDAO(ActivityFeedDAO feedDAO)
{
this.feedDAO = feedDAO;
}
public void setFeedControlDAO(FeedControlDAO feedControlDAO)
{
this.feedControlDAO = feedControlDAO;
}
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
public void setSubscriptionService(SubscriptionService subscriptionService)
{
this.subscriptionService = subscriptionService;
}
public void setDefaultEncoding(String defaultEncoding)
{
this.defaultEncoding = defaultEncoding;
}
public void setTemplateSearchPaths(List<String> templateSearchPaths)
{
this.templateSearchPaths = templateSearchPaths;
}
public void setUseRemoteCallbacks(boolean useRemoteCallbacks)
{
this.useRemoteCallbacks = useRemoteCallbacks;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.resolver = applicationContext;
}
public void startTransaction() throws SQLException
{
// NOOP
}
public void commitTransaction() throws SQLException
{
// NOOP
}
public void rollbackTransaction() throws SQLException
{
// NOOP
}
public void endTransaction() throws SQLException
{
// NOOP
// NOOP
}
public List<ActivityPostEntity> selectPosts(ActivityPostEntity selector) throws SQLException
{
return postDAO.selectPosts(selector);
}
public long insertFeedEntry(ActivityFeedEntity feed) throws SQLException
{
return feedDAO.insertFeedEntry(feed);
}
public int updatePostStatus(long id, ActivityPostEntity.STATUS status) throws SQLException
{
return postDAO.updatePostStatus(id, status);
}
public List<FeedControlEntity> selectUserFeedControls(String userId) throws SQLException
{
return feedControlDAO.selectFeedControls(userId);
return feedControlDAO.selectFeedControls(userId);
}
@Override
protected Set<String> getSiteMembers(final RepoCtx ctx, final String siteId) throws Exception
{
@@ -182,7 +191,7 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
{
// as per 3.0, 3.1
return super.getSiteMembers(ctx, siteId);
}
}
else
{
// optimise for non-remote implementation - override remote repo callback (to "List Site Memberships" web script) with embedded call
@@ -194,12 +203,12 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
if ((siteId != null) && (siteId.length() != 0))
{
Map<String, String> mapResult = siteService.listMembers(siteId, null, null, 0, true);
if ((mapResult != null) && (mapResult.size() != 0))
{
for (String userName : mapResult.keySet())
{
if (! ctx.isUserNamesAreCaseSensitive())
if (!ctx.isUserNamesAreCaseSensitive())
{
userName = userName.toLowerCase();
}
@@ -207,13 +216,13 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
}
}
}
return members;
}
}, AuthenticationUtil.getSystemUserName());
}
}
protected boolean canRead(RepoCtx ctx, final String connectedUser, Map<String, Object> model) throws Exception
{
if (useRemoteCallbacks)
@@ -228,17 +237,17 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
// if permission service not configured then fallback (ie. no read permission check)
return true;
}
String nodeRefStr = (String)model.get(PostLookup.JSON_NODEREF);
String nodeRefStr = (String) model.get(PostLookup.JSON_NODEREF);
if (nodeRefStr == null)
{
nodeRefStr = (String)model.get(PostLookup.JSON_NODEREF_PARENT);
nodeRefStr = (String) model.get(PostLookup.JSON_NODEREF_PARENT);
}
if (nodeRefStr != null)
{
final NodeRef nodeRef = new NodeRef(nodeRefStr);
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Boolean>()
{
public Boolean doWork() throws Exception
@@ -247,16 +256,16 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
}
}, AuthenticationUtil.getSystemUserName());
}
return true;
}
}
private boolean canReadImpl(final String connectedUser, final NodeRef nodeRef) throws Exception
{
// check for read permission
long start = System.currentTimeMillis();
try
{
// note: deleted node does not exist (hence no permission, although default permission check would return true which is problematic)
@@ -264,34 +273,34 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
if (nodeService.exists(nodeRef))
{
checkNodeRef = nodeRef;
}
}
else
{
// TODO: require ghosting - this is temp workaround (we should not rely on archive - may be permanently deleted, ie. not archived or already purged)
NodeRef archiveNodeRef = new NodeRef(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, nodeRef.getId());
if (! nodeService.exists(archiveNodeRef))
if (!nodeService.exists(archiveNodeRef))
{
return false;
}
checkNodeRef = archiveNodeRef;
}
if (connectedUser.equals(""))
{
// site feed (public site)
Set<AccessPermission> perms = permissionService.getAllSetPermissions(checkNodeRef);
for (AccessPermission perm : perms)
{
if (perm.getAuthority().equals(PermissionService.ALL_AUTHORITIES) &&
perm.getAuthorityType().equals(AuthorityType.EVERYONE) &&
perm.getPermission().equals(PermissionService.READ_PERMISSIONS) &&
if (perm.getAuthority().equals(PermissionService.ALL_AUTHORITIES) &&
perm.getAuthorityType().equals(AuthorityType.EVERYONE) &&
perm.getPermission().equals(PermissionService.READ_PERMISSIONS) &&
perm.getAccessStatus().equals(AccessStatus.ALLOWED))
{
return true;
}
}
return false;
}
}
else
{
// user feed
@@ -308,11 +317,11 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
{
if (logger.isDebugEnabled())
{
logger.debug("canRead: " + nodeRef + " in "+(System.currentTimeMillis()-start)+" msecs");
logger.debug("canRead: " + nodeRef + " in " + (System.currentTimeMillis() - start) + " msecs");
}
}
}
@Override
protected Map<String, List<String>> getActivityTypeTemplates(String repoEndPoint, String ticket, String subPath) throws Exception
{
@@ -320,32 +329,32 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
{
// as per 3.0, 3.1
return super.getActivityTypeTemplates(repoEndPoint, ticket, subPath);
}
}
else
{
// optimisation - override remote repo callback (to "Activities Templates" web script) with local/embedded call
String path = "/";
String templatePattern = "*.ftl";
if ((subPath != null) && (subPath.length() > 0))
{
subPath = subPath + "*";
int idx = subPath.lastIndexOf("/");
if (idx != -1)
{
path = subPath.substring(0, idx);
templatePattern = subPath.substring(idx+1) + ".ftl";
templatePattern = subPath.substring(idx + 1) + ".ftl";
}
}
List<String> allTemplateNames = getDocumentPaths(path, false, templatePattern);
return getActivityTemplates(allTemplateNames);
}
}
@Override
protected Configuration getFreemarkerConfiguration(RepoCtx ctx)
{
@@ -353,21 +362,21 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
{
// as per 3.0, 3.1
return super.getFreemarkerConfiguration(ctx);
}
}
else
{
Configuration cfg = new Configuration();
cfg.setObjectWrapper(new DefaultObjectWrapper());
cfg.setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, contentService, defaultEncoding));
// TODO review i18n
cfg.setLocalizedLookup(false);
return cfg;
}
}
// Helper to get template document paths
private List<String> getDocumentPaths(String path, boolean includeSubPaths, String documentPattern)
{
@@ -375,24 +384,24 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
{
path = "/";
}
if (! path.startsWith("/"))
if (!path.startsWith("/"))
{
path = "/" + path;
}
if (! path.endsWith("/"))
if (!path.endsWith("/"))
{
path = path + "/";
}
if ((documentPattern == null) || (documentPattern.length() == 0))
{
documentPattern = "*";
}
List<String> documentPaths = new ArrayList<String>(0);
for (String classPath : templateSearchPaths)
{
final StringBuilder pattern = new StringBuilder(128);
@@ -400,20 +409,20 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
.append(path)
.append((includeSubPaths ? "**/" : ""))
.append(documentPattern);
try
{
documentPaths.addAll(getPaths(pattern.toString(), classPath));
}
}
catch (IOException e)
{
// Note: Ignore: no documents found
}
}
return documentPaths;
}
// Helper to return a list of resource document paths based on a search pattern.
private List<String> getPaths(String pattern, String classPath) throws IOException
{
@@ -422,7 +431,7 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
for (Resource resource : resources)
{
String resourcePath = resource.getURL().toExternalForm();
int idx = resourcePath.lastIndexOf(classPath);
if (idx != -1)
{
@@ -437,4 +446,21 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor implements Applica
}
return documentPaths;
}
protected Set<String> getFollowers(String userId) throws Exception
{
Set<String> result = new HashSet<String>();
if (!subscriptionService.isSubscriptionListPrivate(userId))
{
PagingFollowingResults fr = subscriptionService.getFollowers(userId, new PagingRequest(1000000, null));
if (fr.getPage() != null)
{
result.addAll(fr.getPage());
}
}
return result;
}
}