MOB-822 - Activity feed generator - optimise to use embedded calls (as alternative to existing remote repo callbacks)

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14163 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jan Vonka 2009-05-01 12:57:04 +00:00
parent 46a2ffcb82
commit 3aa7e15332
11 changed files with 954 additions and 78 deletions

View File

@ -9,6 +9,7 @@
<property name="feedControlDAO" ref="feedControlDAO"/>
<property name="authorityService" ref="AuthorityService"/>
<property name="tenantService" ref="tenantService"/>
<property name="siteService" ref="siteService"/>
<property name="userNamesAreCaseSensitive" value="${user.name.caseSensitive}"/>
<property name="feedGenerator" ref="feedGenerator"/>
<property name="maxFeedItems" value="${activities.feed.max.size}"/>
@ -64,6 +65,21 @@
<property name="postDAO" ref="postDAO"/>
<property name="feedDAO" ref="feedDAO"/>
<property name="feedControlDAO" ref="feedControlDAO"/>
<property name="useRemoteCallbacks">
<value>false</value>
</property>
<property name="siteService" ref="SiteService"/>
<property name="nodeService" ref="NodeService"/>
<property name="contentService" ref="ContentService"/>
<property name="templateSearchPaths">
<list>
<value>alfresco/extension/templates/activities</value>
<value>alfresco/templates/activities</value>
</list>
</property>
</bean>
</beans>

View File

@ -38,11 +38,14 @@ import org.alfresco.repo.domain.activities.ActivityPostEntity;
import org.alfresco.repo.domain.activities.FeedControlDAO;
import org.alfresco.repo.domain.activities.FeedControlEntity;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.activities.FeedControl;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.logging.Log;
@ -69,6 +72,7 @@ public class ActivityServiceImpl implements ActivityService
private FeedControlDAO feedControlDAO;
private AuthorityService authorityService;
private FeedGenerator feedGenerator;
private SiteService siteService;
private TenantService tenantService;
@ -116,6 +120,11 @@ public class ActivityServiceImpl implements ActivityService
this.tenantService = tenantService;
}
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
/* (non-Javadoc)
* @see org.alfresco.service.cmr.activities.ActivityService#postActivity(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
@ -310,6 +319,15 @@ public class ActivityServiceImpl implements ActivityService
feedUserId = feedUserId.toLowerCase();
}
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
if (! ((currentUser == null) ||
(currentUser.equals(AuthenticationUtil.getSystemUserName())) ||
(authorityService.isAdminAuthority(currentUser)) ||
(currentUser.equals(feedUserId))))
{
throw new AccessDeniedException("Unable to get user feed entries for '" + feedUserId + "' - currently logged in as '" + currentUser +"'");
}
try
{
List<ActivityFeedEntity> activityFeeds = null;
@ -361,6 +379,15 @@ public class ActivityServiceImpl implements ActivityService
try
{
if (siteService != null)
{
SiteInfo siteInfo = siteService.getSite(siteId);
if (siteInfo == null)
{
throw new AccessDeniedException("No such site: " + siteId);
}
}
siteId = tenantService.getName(siteId);
List<ActivityFeedEntity> activityFeeds = feedDAO.selectSiteFeedEntries(siteId, format);

View File

@ -0,0 +1,545 @@
/*
* Copyright (C) 2005-2009 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.activities;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.activities.feed.FeedGenerator;
import org.alfresco.repo.activities.feed.local.LocalFeedTaskProcessor;
import org.alfresco.repo.activities.post.lookup.PostLookup;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.activities.FeedControl;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.PropertyMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
/**
* Simple Activity Service unit test using site (membership) activities
*
* @author janv
*/
public class SiteActivityTest extends TestCase
{
private static Log logger = LogFactory.getLog(SiteActivityTest.class);
private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext();
private SiteService siteService;
private ActivityService activityService;
private AuthenticationService authenticationService;
private PersonService personService;
private PostLookup postLookup;
private FeedGenerator feedGenerator;
//
// Test config & data
//
// Location of activity type templates (for site activities)
private static final String TEST_TEMPLATES_LOCATION = "activities"; // assumes test-resources is on classpath
// Test users
private static final String ADMIN_USER = "admin";
private static final String ADMIN_PW = "admin";
private static String user1 = null;
private static String user2 = null;
private static String user3 = null;
private static String user4 = null;
private static final String USER_PW = "password";
// Test sites
private static String site1 = null;
private static String site2 = null;
private static String site3 = null;
// AppToolId for site membership activities
private static String appToolId = "siteService"; // refer to SiteService
private static boolean membersAddedUpdated = false;
private static boolean membersRemoved = false;
private static boolean controlsCreated = false;
public SiteActivityTest()
{
}
@Override
protected void setUp() throws Exception
{
super.setUp();
String testid = ""+System.currentTimeMillis();
// Get the required services
this.activityService = (ActivityService)applicationContext.getBean("activityService");
this.siteService = (SiteService)applicationContext.getBean("SiteService");
this.authenticationService = (AuthenticationService)applicationContext.getBean("AuthenticationService");
this.personService = (PersonService)applicationContext.getBean("PersonService");
this.postLookup = (PostLookup)applicationContext.getBean("postLookup");
this.feedGenerator = (FeedGenerator)applicationContext.getBean("feedGenerator");
LocalFeedTaskProcessor feedProcessor = (LocalFeedTaskProcessor)applicationContext.getBean("feedTaskProcessor");
List<String> templateSearchPaths = new ArrayList<String>(1);
templateSearchPaths.add(TEST_TEMPLATES_LOCATION);
feedProcessor.setTemplateSearchPaths(templateSearchPaths);
feedProcessor.setUseRemoteCallbacks(false);
site1 = "test_site1_" + testid;
site2 = "test_site2_" + testid;
site3 = "test_site3_" + testid;
user1 = "test_user1_" + testid;
user2 = "test_user2_" + testid;
user3 = "test_user3_" + testid;
user4 = "test_user4_" + testid;
// create users
login(ADMIN_USER, ADMIN_PW);
createUser(user1, USER_PW);
createUser(user2, USER_PW);
createUser(user3, USER_PW);
createUser(user4, USER_PW);
// create sites
// create public site
createSite(site1, true);
// create private sites
createSite(site2, false);
createSite(site3, false);
}
@Override
protected void tearDown() throws Exception
{
login(ADMIN_USER, ADMIN_PW);
deleteUser(user1);
deleteUser(user2);
deleteUser(user3);
deleteUser(user4);
membersAddedUpdated = false;
membersRemoved = false;
controlsCreated = false;
super.tearDown();
}
protected void createSite(String siteId, boolean isPublic) throws Exception
{
siteService.createSite("myPreset", siteId, "myTitle", "myDescription", (isPublic ? SiteVisibility.PUBLIC : SiteVisibility.PRIVATE));
if (logger.isDebugEnabled())
{
logger.debug("createdSite: " + siteId);
}
}
public void testGetSiteFeedsBefore() throws Exception
{
login(ADMIN_USER, ADMIN_PW);
getSiteFeed(site1, 0);
getSiteFeed(site2, 0); // site 2 is private, but accessible to admins
getSiteFeed(site3, 0); // site 3 is private, but accessible to admins
login(user4, USER_PW);
getSiteFeed(site1, 0); // site 1 is public, hence site feed is accessible to any user of the system
try
{
getSiteFeed(site2, 0); // site 2 is private, hence only accessible to members or admins
fail("Site feed for private site should not be accessible to non-admin / non-member");
}
catch (AccessDeniedException ade)
{
// ignore
}
try
{
getSiteFeed(site3, 0); // site 3 is private, hence only accessible to members or admins
fail("Site feed for private site should not be accessible to non-admin / non-member");
}
catch (AccessDeniedException ade)
{
// ignore
}
}
protected void getSiteFeed(String siteId, int expectedCount) throws Exception
{
assertEquals(expectedCount, activityService.getSiteFeedEntries(siteId, "json").size());
}
public void testGetUserFeedsBefore() throws Exception
{
// as admin
login(ADMIN_USER, ADMIN_PW);
getUserFeed(user1, true, 0);
getUserFeed(user2, true, 0);
getUserFeed(user3, true, 0);
getUserFeed(user4, true, 0);
// as user1
login(user1, USER_PW);
getUserFeed(user1, false, 0);
// as user2
login(user2, USER_PW);
try
{
getUserFeed(user1, true, 0);
fail("User feed should only be accessible to user or an admin");
}
catch (AccessDeniedException ade)
{
// ignore
}
// as user1 - with filter args ...
login(user1, USER_PW);
getUserFeed(null, site1, false, false, false, 0);
getUserFeed(null, site2, false, false, false, 0);
getUserFeed(null, site3, false, false, false, 0);
getUserFeed(null, null, false, true, false, 0);
getUserFeed(null, null, false, false, true, 0);
getUserFeed(null, null, false, true, true, 0);
}
protected void getUserFeed(String userId, boolean isAdmin, int expectedCount) throws Exception
{
getUserFeed(userId, null, isAdmin, false, false, expectedCount);
}
protected void getUserFeed(String userId, String siteId, boolean isAdmin, boolean excludeThisUser, boolean excludeOtherUsers, int expectedCount) throws Exception
{
if (userId == null)
{
userId = AuthenticationUtil.getFullyAuthenticatedUser();
}
assertEquals(expectedCount, activityService.getUserFeedEntries(userId, "json", siteId, excludeThisUser, excludeOtherUsers).size());
}
public void testUserFeedControls() throws Exception
{
if (! controlsCreated)
{
// user 1 opts out of all activities for site 1
login(user1, USER_PW);
addFeedControl(site1, null);
// user 2 opts out of site membership activities (across all sites)
login(user2, USER_PW);
addFeedControl(null, appToolId);
// user 3 opts out of site membership activities for site 1 only
login(user3, USER_PW);
addFeedControl(site1, appToolId);
// TODO add more here, once we have more appToolIds
controlsCreated = true;
}
}
public void testAddAndUpdateMemberships() throws Exception
{
if (! membersAddedUpdated)
{
login(ADMIN_USER, ADMIN_PW);
addAndUpdateMemberships(site1, true); // public site, include all users
addAndUpdateMemberships(site2, true); // private site, include all users
addAndUpdateMemberships(site3, false); // private site, do not include user 4
generateFeed();
membersAddedUpdated = true;
}
}
public void testGetSiteFeedsAfterAddAndUpdateMemberships() throws Exception
{
testAddAndUpdateMemberships();
login(ADMIN_USER, ADMIN_PW);
getSiteFeed(site1, 8); // 8 = 4 users, each with 1 join, 1 role change
getSiteFeed(site2, 8); // 8 = 4 users, each with 1 join, 1 role change
getSiteFeed(site3, 6); // 6 = 3 users, each with 1 join, 1 role change (not user 4)
login(user4, USER_PW);
getSiteFeed(site1, 8);
getSiteFeed(site2, 8); // site 2 is private, user 4 is a member
try
{
getSiteFeed(site3, 0); // site 3 is private, user 4 is not a member
fail("Site feed for private site should not be accessible to non-admin / non-member");
}
catch (AccessDeniedException ade)
{
// ignore
}
}
public void testRemoveMemberships() throws Exception
{
if (! membersRemoved)
{
testAddAndUpdateMemberships();
login(ADMIN_USER, ADMIN_PW);
removeMemberships(site1, true);
removeMemberships(site2, true);
removeMemberships(site3, false);
generateFeed();
membersRemoved = true;
}
}
protected void addAndUpdateMemberships(String siteId, boolean includeUser4) throws Exception
{
// add member -> join site
addMembership(siteId, user1, SiteModel.SITE_CONSUMER);
addMembership(siteId, user2, SiteModel.SITE_MANAGER);
addMembership(siteId, user3, SiteModel.SITE_COLLABORATOR);
if (includeUser4) { addMembership(siteId, user4, SiteModel.SITE_CONSUMER); }
// update member -> change role
updateMembership(siteId, user1, SiteModel.SITE_MANAGER);
updateMembership(siteId, user2, SiteModel.SITE_COLLABORATOR);
updateMembership(siteId, user3, SiteModel.SITE_CONSUMER);
if (includeUser4) { updateMembership(siteId, user4, SiteModel.SITE_COLLABORATOR); }
}
protected void removeMemberships(String siteId, boolean includeUser4) throws Exception
{
// remove member -> leave site
removeMembership(siteId, user1);
removeMembership(siteId, user2);
removeMembership(siteId, user3);
if (includeUser4) { removeMembership(siteId, user4); }
}
private void addFeedControl(String siteId, String appToolId) throws Exception
{
// set feed control for current user
activityService.setFeedControl(new FeedControl(siteId, appToolId));
}
public void testGetSiteFeedsAfterRemoveMemberships() throws Exception
{
testAddAndUpdateMemberships();
testRemoveMemberships();
login(ADMIN_USER, ADMIN_PW);
getSiteFeed(site1, 12); // 12 = 4 users, each with 1 join, 1 role change, 1 leave
getSiteFeed(site2, 12); // 12 = 4 users, each with 1 join, 1 role change, 1 leave
getSiteFeed(site3, 9); // 9 = 3 users, each with 1 join, 1 role change, 1 leave (not user 4)
login(user4, USER_PW);
getSiteFeed(site1, 12);
try
{
getSiteFeed(site2, 0); // site 2 is private, user 4 is no longer a member
fail("Site feed for private site should not be accessible to non-admin / non-member");
}
catch (AccessDeniedException ade)
{
// ignore
}
try
{
getSiteFeed(site3, 0); // site 3 is private, user 4 was never a member
fail("Site feed for private site should not be accessible to non-admin / non-member");
}
catch (AccessDeniedException ade)
{
// ignore
}
}
public void testGetUserFeedsAfter() throws Exception
{
testUserFeedControls();
testAddAndUpdateMemberships();
testRemoveMemberships();
// as admin
login(ADMIN_USER, ADMIN_PW);
// site 1, with 4 users, each with 1 join, 1 role change = 4x2 = 8
// site 2, with 4 users, each with 1 join, 1 role change = 4x2 = 8
// site 3, with 3 users, each with 1 join, 1 role change = 3x2 = 6
// user 1 belongs to 3 sites = (2x8)+(1x6) = 22
// user 2 belongs to 3 sites = (2x8)+(1x6) = 22
// user 3 belongs to 3 sites = (2x8)+(1x6) = 22
// user 4 belongs to 2 sites = (2x8) = 16
getUserFeed(user1, true, 14); // 14 = (22 - 8) due to feed control - exclude site 1
getUserFeed(user2, true, 0); // 0 = due to feed control - exclude site membership activities (across all sites)
getUserFeed(user3, true, 14); // 14 = (22 - 8) due to feed control - exclude site membership activities for site 1
getUserFeed(user4, true, 16); // 16 = no feed control
// as user1
login(user1, USER_PW);
getUserFeed(user1, false, 14);
// as user2
login(user2, USER_PW);
try
{
getUserFeed(user1, true, 14);
fail("User feed should only be accessible to user or an admin");
}
catch (AccessDeniedException ade)
{
// ignore
}
// as user1 - with filter args ...
login(user1, USER_PW);
getUserFeed(null, site1, false, false, false, 0);
getUserFeed(null, site2, false, false, false, 8);
getUserFeed(null, site3, false, false, false, 6);
getUserFeed(null, null, false, false, false, 14); // no filter
getUserFeed(null, null, false, true, false, 14); // exclude any from user1
getUserFeed(null, null, false, false, true, 0); // exclude all except user1
getUserFeed(null, null, false, true, true, 0); // exclude all (NOOP)
// TODO - add more (eg. other non-admin user activities)
}
private void addMembership(String siteId, String userName, String role) throws Exception
{
updateMembership(siteId, userName, role);
}
private void updateMembership(String siteId, String userName, String role) throws Exception
{
siteService.setMembership(siteId, userName, role);
}
private void removeMembership(String siteId, String userName) throws Exception
{
siteService.removeMembership(siteId, userName);
}
protected void createUser(String userName, String password)
{
if (authenticationService.authenticationExists(userName) == false)
{
authenticationService.createAuthentication(userName, password.toCharArray());
PropertyMap ppOne = new PropertyMap(4);
ppOne.put(ContentModel.PROP_USERNAME, userName);
ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName");
ppOne.put(ContentModel.PROP_LASTNAME, "lastName");
ppOne.put(ContentModel.PROP_EMAIL, "email@email.com");
ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle");
personService.createPerson(ppOne);
}
}
protected void deleteUser(String userName)
{
if (authenticationService.authenticationExists(userName) == true)
{
personService.deletePerson(userName);
authenticationService.deleteAuthentication(userName);
}
}
private void login(String username, String password)
{
AuthenticationUtil.setFullyAuthenticatedUser(username);
}
private void generateFeed() throws Exception
{
postLookup.execute();
feedGenerator.execute();
}
}

View File

@ -82,6 +82,8 @@ public abstract class FeedTaskProcessor
public void process(int jobTaskNode, long minSeq, long maxSeq, RepoCtx ctx) throws Exception
{
long startTime = System.currentTimeMillis();
if (logger.isDebugEnabled())
{
logger.debug(">>> Process: jobTaskNode '" + jobTaskNode + "' from seq '" + minSeq + "' to seq '" + maxSeq + "' on this node from grid job.");
@ -168,6 +170,14 @@ public abstract class FeedTaskProcessor
}
activityTemplates.put(baseActivityType, fmTemplates);
if (logger.isTraceEnabled())
{
for (String fmTemplate : fmTemplates)
{
logger.trace("For activityType '"+activityType+"' found activity type template: "+fmTemplate);
}
}
}
if (fmTemplates.size() == 0)
@ -232,9 +242,9 @@ public abstract class FeedTaskProcessor
{
startTransaction();
if (logger.isDebugEnabled())
if (logger.isTraceEnabled())
{
logger.debug(">>> Process: " + connectedUsers.size() + " candidate connections for activity post " + activityPost.getId());
logger.trace(">>> Process: " + connectedUsers.size() + " candidate connections for activity post " + activityPost.getId());
}
int excludedConnections = 0;
@ -335,7 +345,7 @@ public abstract class FeedTaskProcessor
}
finally
{
logger.info(">>> Generated " + totalGenerated + " activity feed entries for " + (activityPosts == null ? 0 : activityPosts.size()) + " activity posts");
logger.info(">>> Generated " + totalGenerated + " activity feed entries for " + (activityPosts == null ? 0 : activityPosts.size()) + " activity posts (in " + (System.currentTimeMillis() - startTime) + " msecs)");
}
}
@ -428,7 +438,7 @@ public abstract class FeedTaskProcessor
{
userName = userName.toLowerCase();
}
members.add(person.getString("userName"));
members.add(userName);
}
}
}
@ -459,16 +469,20 @@ public abstract class FeedTaskProcessor
for (int i = 0; i < ja.length(); i++)
{
String name = ja.getString(i);
if (! name.contains(" (Working Copy)."))
{
allTemplateNames.add(name);
}
}
return getActivityTemplates(allTemplateNames);
}
protected Map<String, List<String>> getActivityTemplates(List<String> allTemplateNames)
{
Map<String, List<String>> activityTemplates = new HashMap<String, List<String>>(10);
for (String template : allTemplateNames)
{
if (! template.contains(" (Working Copy)."))
{
// assume template path = <path>/<base-activityType>.<format>.ftl
// and base-activityType can contain "."
@ -498,6 +512,7 @@ public abstract class FeedTaskProcessor
}
}
}
}
return activityTemplates;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2008 Alfresco Software Limited.
* Copyright (C) 2005-2009 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -25,7 +25,6 @@
package org.alfresco.repo.activities.feed.local;
import java.util.Collection;
import java.util.Date;
import org.alfresco.repo.activities.feed.AbstractFeedGenerator;
import org.alfresco.repo.activities.feed.FeedGridJob;
@ -74,7 +73,7 @@ public class LocalFeedGenerator extends AbstractFeedGenerator
logger.debug(">>> Execute job cycle: " + gridName + " (maxSeq: " + maxSequence + ")");
}
long startTime = new Date().getTime();
long startTime = System.currentTimeMillis();
// TODO ... or push this upto to job scheduler ... ?
AuthenticationUtil.runAs(new RunAsWork<Object>()
@ -102,11 +101,9 @@ public class LocalFeedGenerator extends AbstractFeedGenerator
job.execute();
}
long endTime = new Date().getTime();
if (logger.isDebugEnabled())
{
logger.debug(">>> Finish job cycle: " + gridName + " (time taken (secs) = " + ((endTime - startTime) / 1000) + ")");
logger.debug(">>> Finish job cycle: " + gridName + " (in " + (System.currentTimeMillis() - startTime) + " msecs)");
}
return true;
}

View File

@ -24,28 +24,57 @@
*/
package org.alfresco.repo.activities.feed.local;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.repo.activities.feed.FeedTaskProcessor;
import org.alfresco.repo.activities.feed.RepoCtx;
import org.alfresco.repo.domain.activities.ActivityFeedDAO;
import org.alfresco.repo.domain.activities.ActivityFeedEntity;
import org.alfresco.repo.domain.activities.ActivityPostDAO;
import org.alfresco.repo.domain.activities.ActivityPostEntity;
import org.alfresco.repo.domain.activities.FeedControlDAO;
import org.alfresco.repo.domain.activities.FeedControlEntity;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.template.ClassPathRepoTemplateLoader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.site.SiteService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import com.ibatis.sqlmap.client.SqlMapClient;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
/**
* The local (ie. not grid) feed task processor is responsible for processing the individual feed job
*/
public class LocalFeedTaskProcessor extends FeedTaskProcessor
{
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 String defaultEncoding;
private List<String> templateSearchPaths;
private boolean useRemoteCallbacks;
// used to start/end/commit transaction
// note: currently assumes that all dao services are configured with this mapper / data source
private SqlMapClient sqlMapper;
@ -65,6 +94,36 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor
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 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 setSqlMapClient(SqlMapClient sqlMapper)
{
this.sqlMapper = sqlMapper;
@ -104,4 +163,169 @@ public class LocalFeedTaskProcessor extends FeedTaskProcessor
{
return feedControlDAO.selectFeedControls(userId);
}
@Override
protected Set<String> getSiteMembers(final RepoCtx ctx, final String siteId) throws Exception
{
if (useRemoteCallbacks)
{
// 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
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Set<String>>()
{
public Set<String> doWork() throws Exception
{
Set<String> members = new HashSet<String>();
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())
{
userName = userName.toLowerCase();
}
members.add(userName);
}
}
}
return members;
}
}, AuthenticationUtil.getSystemUserName());
}
}
@Override
protected Map<String, List<String>> getActivityTypeTemplates(String repoEndPoint, String ticket, String subPath) throws Exception
{
if (useRemoteCallbacks)
{
// 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";
}
}
List<String> allTemplateNames = getDocumentPaths(path, false, templatePattern);
return getActivityTemplates(allTemplateNames);
}
}
@Override
protected Configuration getFreemarkerConfiguration(RepoCtx ctx)
{
if (useRemoteCallbacks)
{
// 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)
{
if ((path == null) || (path.length() == 0))
{
path = "/";
}
if (! path.startsWith("/"))
{
path = "/" + path;
}
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);
pattern.append("classpath*:").append(classPath)
.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
{
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(pattern);
List<String> documentPaths = new ArrayList<String>(resources.length);
for (Resource resource : resources)
{
String resourcePath = resource.getURL().toExternalForm();
int idx = resourcePath.lastIndexOf(classPath);
if (idx != -1)
{
String documentPath = resourcePath.substring(idx);
documentPath = documentPath.replace('\\', '/');
if (logger.isTraceEnabled())
{
logger.trace("Item resource path: " + resourcePath + " , item path: " + documentPath);
}
documentPaths.add(documentPath);
}
}
return documentPaths;
}
}

View File

@ -0,0 +1,17 @@
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>${name!""}</title>
<link rel="alternate" type="text/html"
<#if nodeRef?? && nodeRef != "">
href="${repoEndPoint}/d/d/${nodeRef?replace("://","/")}/${name!""}"
<#else>
href="${repoEndPoint}"
</#if>
/>
<icon></icon>
<id>${id}</id>
<updated>${xmldate(date)}</updated>
<summary>${userId!""} ${activityType!""} ${displayPath!""} <#if siteNetwork?? && siteNetwork != "">(${siteNetwork} site)</#if></summary>
<author>
<name>${userId!""}</name>
</author>
</entry>

View File

@ -0,0 +1,2 @@
<#-- default JSON rendering is to pass-through - assumes activityData is posted in JSON format -->
${activityData!""}

View File

@ -0,0 +1,11 @@
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>${memberFirstName?xml}<#if memberLastName??> ${memberLastName?xml}</#if> joined ${siteNetwork?xml} site</title>
<link rel="alternate" type="text/html"/>
<icon></icon>
<id>${id}</id>
<updated>${xmldate(date)}</updated>
<summary>${memberFirstName?xml}<#if memberLastName??> ${memberLastName?xml}</#if> joined ${siteNetwork?xml} site (with role ${role})</summary>
<author>
<name>${userId}</name>
</author>
</entry>

View File

@ -0,0 +1,11 @@
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>${memberFirstName?xml}<#if memberLastName??> ${memberLastName?xml}</#if> left ${siteNetwork?xml} site</title>
<link rel="alternate" type="text/html"/>
<icon></icon>
<id>${id}</id>
<updated>${xmldate(date)}</updated>
<summary>${memberFirstName?xml}<#if memberLastName??> ${memberLastName?xml}</#if> left ${siteNetwork?xml} site</summary>
<author>
<name>${userId}</name>
</author>
</entry>

View File

@ -0,0 +1,11 @@
<entry xmlns='http://www.w3.org/2005/Atom'>
<title>${memberFirstName?xml}<#if memberLastName??> ${memberLastName?xml}</#if> role changed for ${siteNetwork?xml} site</title>
<link rel="alternate" type="text/html"/>
<icon></icon>
<id>${id}</id>
<updated>${xmldate(date)}</updated>
<summary>${memberFirstName?xml}<#if memberLastName??> ${memberLastName?xml}</#if> role changed to ${role} for ${siteNetwork?xml} site</summary>
<author>
<name>${userId}</name>
</author>
</entry>