diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml
index fc48eb0691..cfd3125057 100644
--- a/config/alfresco/application-context-highlevel.xml
+++ b/config/alfresco/application-context-highlevel.xml
@@ -16,6 +16,7 @@
+
diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml
index e05465c075..08d9ae6b8c 100644
--- a/config/alfresco/bootstrap-context.xml
+++ b/config/alfresco/bootstrap-context.xml
@@ -142,6 +142,7 @@
+
diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml
index 9ebc50cc63..f323277c29 100644
--- a/config/alfresco/dao/dao-context.xml
+++ b/config/alfresco/dao/dao-context.xml
@@ -41,6 +41,13 @@
+
+
+
+
+
+
+
patchDAO.#bean.dialect#
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-SubscriptionTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-SubscriptionTables.sql
new file mode 100644
index 0000000000..650c7268ad
--- /dev/null
+++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-SubscriptionTables.sql
@@ -0,0 +1,28 @@
+--
+-- Title: Subscription tables
+-- Database: MySQL InnoDB
+-- Since: V4.0 Schema 5011
+-- Author: Florian Mueller
+--
+-- Please contact support@alfresco.com if you need assistance with the upgrade.
+--
+
+CREATE TABLE alf_subscriptions (
+ user_node_id BIGINT NOT NULL,
+ node_id BIGINT NOT NULL,
+ PRIMARY KEY (user_node_id, node_id),
+ CONSTRAINT fk_alf_sub_user FOREIGN KEY (user_node_id) REFERENCES alf_node(id) ON DELETE CASCADE,
+ CONSTRAINT fk_alf_sub_node FOREIGN KEY (node_id) REFERENCES alf_node(id) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
+--
+-- Record script finish
+--
+DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-SubscriptionTables';
+INSERT INTO alf_applied_patch
+ (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
+ VALUES
+ (
+ 'patch.db-V4.0-SubscriptionTables', 'Manually executed script upgrade V4.0: Subscription Tables',
+ 0, 5010, -1, 5011, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed'
+ );
\ No newline at end of file
diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-SubscriptionTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-SubscriptionTables.sql
new file mode 100644
index 0000000000..f5a676feba
--- /dev/null
+++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-SubscriptionTables.sql
@@ -0,0 +1,28 @@
+--
+-- Title: Subscription tables
+-- Database: PostgreSQL
+-- Since: V4.0 Schema 5011
+-- Author: Florian Mueller
+--
+-- Please contact support@alfresco.com if you need assistance with the upgrade.
+--
+
+CREATE TABLE alf_subscriptions (
+ user_node_id INT8 NOT NULL,
+ node_id INT8 NOT NULL,
+ PRIMARY KEY (user_node_id, node_id),
+ CONSTRAINT fk_alf_sub_user FOREIGN KEY (user_node_id) REFERENCES alf_node(id) ON DELETE CASCADE,
+ CONSTRAINT fk_alf_sub_node FOREIGN KEY (node_id) REFERENCES alf_node(id) ON DELETE CASCADE
+);
+
+--
+-- Record script finish
+--
+DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-SubscriptionTables';
+INSERT INTO alf_applied_patch
+ (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
+ VALUES
+ (
+ 'patch.db-V4.0-SubscriptionTables', 'Manually executed script upgrade V4.0: Subscription Tables',
+ 0, 5010, -1, 5011, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed'
+ );
diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml
index 7d16488347..4cda53081f 100644
--- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml
+++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml
@@ -137,6 +137,11 @@ Inbound settings from iBatis
+
+
+
+
+
@@ -168,6 +173,7 @@ Inbound settings from iBatis
+
diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/subscriptions-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/subscriptions-common-SqlMap.xml
new file mode 100644
index 0000000000..d20e1dff89
--- /dev/null
+++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/subscriptions-common-SqlMap.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ insert into alf_subscriptions (user_node_id, node_id)
+ values (?, ?)
+
+
+
+ delete from alf_subscriptions where user_node_id = #{userNodeId} and node_id = #{nodeId}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml
index d8ce586f34..c182df62af 100644
--- a/config/alfresco/model/contentModel.xml
+++ b/config/alfresco/model/contentModel.xml
@@ -268,6 +268,10 @@
d:boolean
+
+ d:boolean
+
+
diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml
index 72db6397cb..07613be5cd 100644
--- a/config/alfresco/patch/patch-services-context.xml
+++ b/config/alfresco/patch/patch-services-context.xml
@@ -2871,4 +2871,15 @@
+
+ patch.db-V4.0-SubscriptionTables
+ patch.schemaUpgradeScript.description
+ 0
+ 5010
+ 5011
+
+ classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-SubscriptionTables.sql
+
+
+
diff --git a/config/alfresco/subscription-service-context.xml b/config/alfresco/subscription-service-context.xml
new file mode 100644
index 0000000000..2bcb253296
--- /dev/null
+++ b/config/alfresco/subscription-service-context.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.subscriptions.SubscriptionService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml
index fab2a3ee16..c190fd9c2f 100644
--- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml
+++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml
@@ -86,7 +86,8 @@
-
+
+
alfresco/extension/templates/activities
diff --git a/source/java/org/alfresco/repo/activities/ActivityPostServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityPostServiceImpl.java
index 76ce293a3d..a4a7d2d161 100644
--- a/source/java/org/alfresco/repo/activities/ActivityPostServiceImpl.java
+++ b/source/java/org/alfresco/repo/activities/ActivityPostServiceImpl.java
@@ -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
diff --git a/source/java/org/alfresco/repo/activities/ActivityType.java b/source/java/org/alfresco/repo/activities/ActivityType.java
index a76d60ccd7..1907fc73b2 100644
--- a/source/java/org/alfresco/repo/activities/ActivityType.java
+++ b/source/java/org/alfresco/repo/activities/ActivityType.java
@@ -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";
}
diff --git a/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java
index c95d9838b9..501d5c0e35 100644
--- a/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java
+++ b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java
@@ -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> activityTemplates = new HashMap>(10);
Map> siteConnectedUsers = new TreeMap>();
+ Map> followers = new TreeMap>();
Map> userFeedControls = new HashMap>();
Map templateCache = new TreeMap();
@@ -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 connectedUsers = siteConnectedUsers.get(thisSite);
- if (connectedUsers == null)
- {
+ // Recipients of this post
+ Set recipients = new HashSet();
+
+ // 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 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 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 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 selectPosts(ActivityPostEntity selector) throws SQLException;
-
+
public abstract List 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 getSiteMembers(RepoCtx ctx, String siteId) throws Exception
{
Set members = new HashSet();
@@ -481,12 +508,14 @@ public abstract class FeedTaskProcessor
return members;
}
+
+ protected abstract Set getFollowers(String userId) throws Exception;
protected boolean canRead(RepoCtx ctx, final String connectedUser, Map model) throws Exception
{
throw new UnsupportedOperationException("FeedTaskProcessor: Remote callback for 'canRead' not implemented");
}
-
+
protected Map> 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> getActivityTemplates(List allTemplateNames)
{
Map> activityTemplates = new HashMap>(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 templateCache, String fmTemplate, Configuration cfg, Map 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 getFeedControls(String connectedUser) throws SQLException
{
return selectUserFeedControls(connectedUser);
}
-
+
protected boolean acceptActivity(ActivityPostEntity activityPost, List feedControls)
{
if (feedControls == null)
@@ -632,7 +661,7 @@ public abstract class FeedTaskProcessor
return true;
}
-
+
protected void addMissingFormats(String activityType, List fmTemplates, List templatesToAdd)
{
for (String templateToAdd : templatesToAdd)
@@ -666,7 +695,7 @@ public abstract class FeedTaskProcessor
}
}
}
-
+
protected String getTemplateSubPath(String activityType)
{
return (! activityType.startsWith("/") ? "/" : "") + activityType.replace(".", "/");
diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java
index 2b49021b46..e2ef7cb843 100644
--- a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java
+++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java
@@ -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 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 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 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 selectUserFeedControls(String userId) throws SQLException
{
- return feedControlDAO.selectFeedControls(userId);
+ return feedControlDAO.selectFeedControls(userId);
}
-
+
@Override
protected Set 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 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 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()
{
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 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> 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 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 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 documentPaths = new ArrayList(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 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 getFollowers(String userId) throws Exception
+ {
+ Set result = new HashSet();
+
+ if (!subscriptionService.isSubscriptionListPrivate(userId))
+ {
+ PagingFollowingResults fr = subscriptionService.getFollowers(userId, new PagingRequest(1000000, null));
+
+ if (fr.getPage() != null)
+ {
+ result.addAll(fr.getPage());
+ }
+ }
+
+ return result;
+ }
}
diff --git a/source/java/org/alfresco/repo/domain/subscriptions/AbstractSubscriptionsDAO.java b/source/java/org/alfresco/repo/domain/subscriptions/AbstractSubscriptionsDAO.java
new file mode 100644
index 0000000000..4efcd49c1e
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/subscriptions/AbstractSubscriptionsDAO.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.domain.subscriptions;
+
+import org.alfresco.query.PagingRequest;
+import org.alfresco.repo.domain.node.NodeDAO;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.subscriptions.PagingFollowingResults;
+import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults;
+import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum;
+
+public abstract class AbstractSubscriptionsDAO implements SubscriptionsDAO
+{
+ protected NodeDAO nodeDAO;
+ protected PersonService personService;
+
+ public final void setNodeDAO(NodeDAO nodeDAO)
+ {
+ this.nodeDAO = nodeDAO;
+ }
+
+ public final void setPersonService(PersonService personService)
+ {
+ this.personService = personService;
+ }
+
+ @Override
+ public abstract PagingSubscriptionResults selectSubscriptions(String userId, SubscriptionItemTypeEnum type,
+ PagingRequest pagingRequest);
+
+ @Override
+ public abstract int countSubscriptions(String userId, SubscriptionItemTypeEnum type);
+
+ @Override
+ public abstract void insertSubscription(String userId, NodeRef node);
+
+ @Override
+ public abstract void deleteSubscription(String userId, NodeRef node);
+
+ @Override
+ public abstract boolean hasSubscribed(String userId, NodeRef node);
+
+ @Override
+ public abstract PagingFollowingResults selectFollowers(String userId, PagingRequest pagingRequest);
+
+ @Override
+ public abstract int countFollowers(String userId);
+
+ protected NodeRef getUserNodeRef(String userId)
+ {
+ return personService.getPerson(userId, false);
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionDAOTest.java b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionDAOTest.java
new file mode 100644
index 0000000000..9795007dce
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/subscriptions/SubscriptionDAOTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.repo.domain.subscriptions;
+
+import junit.framework.TestCase;
+
+import org.alfresco.query.PagingRequest;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.subscriptions.PagingFollowingResults;
+import org.alfresco.service.cmr.subscriptions.PagingSubscriptionResults;
+import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.springframework.context.ApplicationContext;
+
+public class SubscriptionDAOTest extends TestCase
+{
+ private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+ private TransactionService transactionService;
+ private RetryingTransactionHelper txnHelper;
+ private PersonService personService;
+
+ private SubscriptionsDAO subscriptionsDAO;
+
+ protected NodeRef getUserNodeRef(final String userId)
+ {
+ final PersonService ps = personService;
+
+ return AuthenticationUtil.runAs(new RunAsWork()
+ {
+ @Override
+ public NodeRef doWork() throws Exception
+ {
+ return ps.getPerson(userId);
+ }
+ }, AuthenticationUtil.getSystemUserName());
+ }
+
+ protected void insert(final String userId, final NodeRef node) throws Exception
+ {
+ RetryingTransactionCallback