diff --git a/config/alfresco/activities/activities-SqlMapConfig.xml b/config/alfresco/activities/activities-SqlMapConfig.xml new file mode 100644 index 0000000000..e3b29239fe --- /dev/null +++ b/config/alfresco/activities/activities-SqlMapConfig.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/activities/activities-feed-context.xml b/config/alfresco/activities/activities-feed-context.xml new file mode 100644 index 0000000000..535b2e83d4 --- /dev/null +++ b/config/alfresco/activities/activities-feed-context.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + true + + + + + classpath:alfresco/activities/activities-SqlMapConfig.xml + + + + + + + + + + + + + + + + + + + + 20160 + + + + + + + + 30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityFeed.xml b/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityFeed.xml new file mode 100644 index 0000000000..040d7d0752 --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityFeed.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into alf_activity_feed (activity_type, activity_summary, activity_format, feed_user_id, post_user_id, post_date, post_id, site_network, app_tool, feed_date) + values (#activityType#, #activitySummary#, #activitySummaryFormat#, #feedUserId#, #postUserId#, #postDate#, #postId#, #siteNetwork#, #appTool#, #feedDate#) + + + + CALL IDENTITY() + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityFeedControl.xml b/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityFeedControl.xml new file mode 100644 index 0000000000..d1b553a72c --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityFeedControl.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + insert into alf_activity_feed_control (feed_user_id, site_network, app_tool, last_modified) + values (#feedUserId#, #siteNetwork#, #appTool#, #lastModified#) + + + + CALL IDENTITY() + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityPost.xml b/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityPost.xml new file mode 100755 index 0000000000..2d479a6d32 --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.HSQLDialect/ActivityPost.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into alf_activity_post (status, activity_data, post_user_id, post_date, activity_type, site_network, app_tool, job_task_node, last_modified) + values (#status#, #activityData#, #userId#, #postDate#, #activityType#, #siteNetwork#, #appTool#, #jobTaskNode#, #lastModified#) + + + + CALL IDENTITY() + + + + + + + + + + update alf_activity_post set status = #status#, activity_data=#activityData#, site_network=#siteNetwork#, last_modified=#lastModified# + where sequence_id = #id# + and status != #status# + + + + update alf_activity_post set status = #status#, last_modified=#lastModified# + where sequence_id = #id# + and status != #status# + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityFeed.xml b/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityFeed.xml new file mode 100644 index 0000000000..0e046c8840 --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityFeed.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into alf_activity_feed (activity_type, activity_summary, activity_format, feed_user_id, post_user_id, post_date, post_id, site_network, app_tool, feed_date) + values (#activityType#, #activitySummary#, #activitySummaryFormat#, #feedUserId#, #postUserId#, #postDate#, #postId#, #siteNetwork#, #appTool#, #feedDate#) + + + + SELECT LAST_INSERT_ID() AS value + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityFeedControl.xml b/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityFeedControl.xml new file mode 100644 index 0000000000..b0ac596003 --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityFeedControl.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + insert into alf_activity_feed_control (feed_user_id, site_network, app_tool, last_modified) + values (#feedUserId#, #siteNetwork#, #appTool#, #lastModified#) + + + + SELECT LAST_INSERT_ID() AS value + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityPost.xml b/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityPost.xml new file mode 100755 index 0000000000..1c080740e8 --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.MySQLInnoDBDialect/ActivityPost.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into alf_activity_post (status, activity_data, post_user_id, post_date, activity_type, site_network, app_tool, job_task_node, last_modified) + values (#status#, #activityData#, #userId#, #postDate#, #activityType#, #siteNetwork#, #appTool#, #jobTaskNode#, #lastModified#) + + + + SELECT LAST_INSERT_ID() AS value + + + + + + + + + + update alf_activity_post set status = #status#, activity_data=#activityData#, site_network=#siteNetwork#, last_modified=#lastModified# + where sequence_id = #id# + and status != #status# + + + + update alf_activity_post set status = #status#, last_modified=#lastModified# + where sequence_id = #id# + and status != #status# + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityFeed.xml b/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityFeed.xml new file mode 100644 index 0000000000..176f84f708 --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityFeed.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select alf_activity_feed_seq.nextval as value from dual + + + insert into alf_activity_feed (id, activity_type, activity_summary, activity_format, feed_user_id, post_user_id, post_date, post_id, site_network, app_tool, feed_date) + values (#id#, #activityType#, #activitySummary#, #activitySummaryFormat#, #feedUserId#, #postUserId#, #postDate#, #postId#, #siteNetwork#, #appTool#, #feedDate#) + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityFeedControl.xml b/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityFeedControl.xml new file mode 100644 index 0000000000..d8afdccf6b --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityFeedControl.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + select alf_activity_feed_control_seq.nextval as value from dual + + + insert into alf_activity_feed_control (id, feed_user_id, site_network, app_tool, last_modified) + values (#id#, #feedUserId#, #siteNetwork#, #appTool#, #lastModified#) + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityPost.xml b/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityPost.xml new file mode 100755 index 0000000000..b2f5dc0895 --- /dev/null +++ b/config/alfresco/activities/org.hibernate.dialect.Oracle9Dialect/ActivityPost.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select alf_activity_post_seq.nextval as value from dual + + + insert into alf_activity_post (sequence_id, status, activity_data, post_user_id, post_date, activity_type, site_network, app_tool, job_task_node, last_modified) + values (#id#, #status#, #activityData#, #userId#, #postDate#, #activityType#, #siteNetwork#, #appTool#, #jobTaskNode#, #lastModified#) + + + + + + + + + update alf_activity_post set status = #status#, activity_data=#activityData#, site_network=#siteNetwork#, last_modified=#lastModified# + where sequence_id = #id# + and status != #status# + + + + update alf_activity_post set status = #status#, last_modified=#lastModified# + where sequence_id = #id# + and status != #status# + + + \ No newline at end of file diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 1668af9ed5..9a37fee7c6 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -12,7 +12,8 @@ - + + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 0dc044d49e..9977930c08 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -43,6 +43,7 @@ classpath:alfresco/dbscripts/create/2.2/${db.script.dialect}/AlfrescoPostCreate-2.2-MappedFKIndexes.sql classpath:alfresco/dbscripts/create/2.2/${db.script.dialect}/AlfrescoPostCreate-2.2-Extra.sql + classpath:alfresco/dbscripts/create/3.0/${db.script.dialect}/create-activities-tables.sql @@ -59,6 +60,7 @@ + diff --git a/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.HSQLDialect/create-activities-tables.sql b/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.HSQLDialect/create-activities-tables.sql new file mode 100644 index 0000000000..8abb15689b --- /dev/null +++ b/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.HSQLDialect/create-activities-tables.sql @@ -0,0 +1,59 @@ +-- +-- Title: Activities Schema +-- Database: HSQL +-- Since: V3.0.0 Schema +-- +-- Note: The Activities schema is NOT managed by Hibernate +-- + + +CREATE TABLE alf_activity_post ( + sequence_id bigint generated by default as identity (start with 1), + post_date timestamp NOT NULL, + status varchar(10) NOT NULL, + activity_data varchar(4000) NOT NULL, + post_user_id varchar(255) NOT NULL, + job_task_node integer NOT NULL, + site_network varchar(255) default NULL, + app_tool varchar(36) default NULL, + activity_type varchar(255) NOT NULL, + last_modified timestamp NOT NULL, + primary key (sequence_id) +); + +CREATE INDEX jobtasknode_idx on alf_activity_post(job_task_node); +CREATE INDEX status_idx on alf_activity_post(status); + + +CREATE TABLE alf_activity_feed ( + id bigint generated by default as identity (start with 1), + post_id bigint default NULL, + post_date timestamp NOT NULL, + activity_summary varchar(4000) default NULL, + feed_user_id varchar(255) NOT NULL, + activity_type varchar(255) NOT NULL, + activity_format varchar(10) default NULL, + site_network varchar(255) default NULL, + app_tool varchar(36) default NULL, + post_user_id varchar(255) NOT NULL, + feed_date timestamp NOT NULL, + primary key (id) +); + +CREATE INDEX postdate_idx ON alf_activity_feed(post_date); +CREATE INDEX feeduserid_idx ON alf_activity_feed(feed_user_id); +CREATE INDEX postuserid_idx ON alf_activity_feed(post_user_id); +CREATE INDEX sitenetwork_idx ON alf_activity_feed(site_network); +CREATE INDEX activityformat_idx ON alf_activity_feed(activity_format); + + +CREATE TABLE alf_activity_feed_control ( + id bigint generated by default as identity (start with 1), + feed_user_id varchar(255) NOT NULL, + site_network varchar(255) NOT NULL, + app_tool varchar(36) default NULL, + last_modified timestamp NOT NULL, + primary key (id) +); + +CREATE INDEX feedcontroluserid_idx ON alf_activity_feed_control(feed_user_id); diff --git a/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.MySQLInnoDBDialect/create-activities-tables.sql b/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.MySQLInnoDBDialect/create-activities-tables.sql new file mode 100644 index 0000000000..8698e3ebb6 --- /dev/null +++ b/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.MySQLInnoDBDialect/create-activities-tables.sql @@ -0,0 +1,56 @@ +-- +-- Title: Activities Schema +-- Database: MySQL +-- Since: V3.0.0 Schema +-- +-- Note: The Activities schema is NOT managed by Hibernate +-- + + +CREATE TABLE alf_activity_post ( + sequence_id bigint NOT NULL auto_increment, + post_date timestamp NOT NULL, + status varchar(10) NOT NULL, + activity_data varchar(4000) NOT NULL, + post_user_id varchar(255) NOT NULL, + job_task_node int(11) NOT NULL, + site_network varchar(255) default NULL, + app_tool varchar(36) default NULL, + activity_type varchar(255) NOT NULL, + last_modified timestamp NOT NULL, + PRIMARY KEY (sequence_id), + KEY jobtasknode_idx (job_task_node), + KEY status_idx (status) +) type=InnoDB; + + +CREATE TABLE alf_activity_feed ( + id bigint NOT NULL auto_increment, + post_id bigint default NULL, + post_date timestamp NOT NULL, + activity_summary varchar(4000) default NULL, + feed_user_id varchar(255) NOT NULL, + activity_type varchar(255) NOT NULL, + activity_format varchar(10) default NULL, + site_network varchar(255) default NULL, + app_tool varchar(36) default NULL, + post_user_id varchar(255) NOT NULL, + feed_date timestamp NOT NULL, + PRIMARY KEY (id), + KEY postdate_idx (post_date), + KEY feeduserid_idx (feed_user_id), + KEY postuserid_idx (post_user_id), + KEY sitenetwork_idx (site_network), + KEY activityformat_idx (activity_format) +) type=InnoDB; + + +CREATE TABLE alf_activity_feed_control ( + id bigint NOT NULL auto_increment, + feed_user_id varchar(255) NOT NULL, + site_network varchar(255) NOT NULL, + app_tool varchar(36) NOT NULL, + last_modified timestamp NOT NULL, + PRIMARY KEY (id), + KEY feedcontroluserid_idx (feed_user_id) +) type=InnoDB; diff --git a/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.Oracle9Dialect/create-activities-tables.sql b/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.Oracle9Dialect/create-activities-tables.sql new file mode 100644 index 0000000000..34b9f389ff --- /dev/null +++ b/config/alfresco/dbscripts/create/3.0/org.hibernate.dialect.Oracle9Dialect/create-activities-tables.sql @@ -0,0 +1,65 @@ +-- +-- Title: Activities Schema +-- Database: Oracle +-- Since: V3.0.0 Schema +-- +-- Note: The Activities schema is NOT managed by Hibernate +-- + + +CREATE TABLE alf_activity_post ( + sequence_id number(19,0) NOT NULL, + post_date timestamp NOT NULL, + status varchar2(10) NOT NULL, + activity_data varchar2(4000) NOT NULL, + post_user_id varchar2(255) NOT NULL, + job_task_node number(19,0) NOT NULL, + site_network varchar2(255) default NULL, + app_tool varchar2(36) default NULL, + activity_type varchar2(255) NOT NULL, + last_modified timestamp NOT NULL, + primary key (sequence_id) +); + +CREATE SEQUENCE alf_activity_post_seq START WITH 1 INCREMENT BY 1; + +CREATE INDEX jobtasknode_idx on alf_activity_post(job_task_node); +CREATE INDEX status_idx on alf_activity_post(status); + + +CREATE TABLE alf_activity_feed ( + id number(19,0) NOT NULL, + post_id number(19,0) default NULL, + post_date timestamp NOT NULL, + activity_summary varchar2(4000) default NULL, + feed_user_id varchar2(255) default NULL, + activity_type varchar2(255) NOT NULL, + activity_format varchar2(10) default NULL, + site_network varchar2(255) default NULL, + app_tool varchar2(36) default NULL, + post_user_id varchar2(255) NOT NULL, + feed_date timestamp NOT NULL, + primary key (id) +); + +CREATE SEQUENCE alf_activity_feed_seq START WITH 1 INCREMENT BY 1; + +CREATE INDEX postdate_idx ON alf_activity_feed(post_date); +CREATE INDEX feeduserid_idx ON alf_activity_feed(feed_user_id); +CREATE INDEX postuserid_idx ON alf_activity_feed(post_user_id); +CREATE INDEX sitenetwork_idx ON alf_activity_feed(site_network); +CREATE INDEX activityformat_idx ON alf_activity_feed(activity_format); + + +CREATE TABLE alf_activity_feed_control ( + id number(19,0) NOT NULL, + feed_user_id varchar2(255) NOT NULL, + site_network varchar2(255) NOT NULL, + app_tool varchar2(36) NOT NULL, + last_modified timestamp NOT NULL, + primary key (id) +); + +CREATE SEQUENCE alf_activity_feed_control_seq START WITH 1 INCREMENT BY 1; + +CREATE INDEX feedcontroluserid_idx ON alf_activity_feed_control(feed_user_id); diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 9bb9b99495..a8020fd571 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1468,4 +1468,15 @@ + + patch.db-V3.0-0-CreateActivitiesTables + patch.schemaUpgradeScript.description + 0 + 125 + 126 + + classpath:alfresco/dbscripts/create/3.0/${db.script.dialect}/create-activities-tables.sql + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 48de829525..7690195da7 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -205,3 +205,6 @@ avm.remote.idlestream.timeout=30000 # ECM content usages/quotas system.usages.enabled=true + +# Repository endpoint - used by Activity Service +repo.remote.endpoint.url=http://localhost:8080/alfresco/service \ No newline at end of file diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index 0297ce19dc..6de99529e2 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -354,4 +354,120 @@ + + + + + + org.alfresco.repo.activities.feed.cleanup.FeedCleanupJob + + + + + + + + + + + + + + + + + + 5 + + + 10 + + + + + + org.alfresco.repo.activities.feed.FeedGeneratorJob + + + + + + + + + + + + + + + + + + + 0 + + + 30000 + + + + + + + + org.alfresco.repo.activities.post.lookup.PostLookupJob + + + + + + + + + + + + + + + + + + 1 + + + 15000 + + + + + + + + org.alfresco.repo.activities.post.cleanup.PostCleanupJob + + + + + + + + + + + + + + + + + + 10 + + + 10 + + + \ No newline at end of file diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index 48520eb01b..263c3e3799 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -167,4 +167,13 @@ + + + activities + + + + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 366ad9ab30..d4b663dcce 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=125 +version.schema=126 diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java new file mode 100644 index 0000000000..d0ca31c9b3 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2005-2008 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.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.activities.feed.ActivityFeedDAO; +import org.alfresco.repo.activities.feed.ActivityFeedDaoService; +import org.alfresco.repo.activities.feed.FeedGenerator; +import org.alfresco.repo.activities.feed.control.FeedControlDAO; +import org.alfresco.repo.activities.feed.control.FeedControlDaoService; +import org.alfresco.repo.activities.post.ActivityPostDAO; +import org.alfresco.repo.activities.post.ActivityPostDaoService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +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.namespace.QName; +import org.alfresco.util.JSONtoFmModel; +import org.alfresco.util.ParameterCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; + +/** + * Activity Service Implementation + * + * @author janv + */ +public class ActivityServiceImpl implements ActivityService +{ + private static final Log logger = LogFactory.getLog(ActivityServiceImpl.class); + + private static final int MAX_LEN_USER_ID = 255; // needs to match schema: feed_user_id, post_user_id + private static final int MAX_LEN_SITE_ID = 255; // needs to match schema: site_network + private static final int MAX_LEN_ACTIVITY_TYPE = 255; // needs to match schema: activity_type + private static final int MAX_LEN_ACTIVITY_DATA = 4000; // needs to match schema: activity_data + private static final int MAX_LEN_APP_TOOL_ID = 36; // needs to match schema: app_tool + + private ActivityPostDaoService postDaoService; + private ActivityFeedDaoService feedDaoService; + private FeedControlDaoService feedControlDaoService; + private AuthorityService authorityService; + private FeedGenerator feedGenerator; + + private int maxFeedItems = 100; + + private boolean userNamesAreCaseSensitive = false; + + public void setMaxFeedItems(int maxFeedItems) + { + this.maxFeedItems = maxFeedItems; + } + + public void setUserNamesAreCaseSensitive(boolean userNamesAreCaseSensitive) + { + this.userNamesAreCaseSensitive = userNamesAreCaseSensitive; + } + + public void setPostDaoService(ActivityPostDaoService postDaoService) + { + this.postDaoService = postDaoService; + } + + public void setFeedDaoService(ActivityFeedDaoService feedDaoService) + { + this.feedDaoService = feedDaoService; + } + + public void setFeedControlDaoService(FeedControlDaoService feedControlDaoService) + { + this.feedControlDaoService = feedControlDaoService; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setFeedGenerator(FeedGenerator feedGenerator) + { + this.feedGenerator = feedGenerator; + } + + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#postActivity(java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + public void postActivity(String activityType, String network, String appTool, String activityData) + { + postActivity(activityType, network, appTool, activityData, ActivityPostDAO.STATUS.POSTED); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#postActivity(java.lang.String, java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef) + */ + public void postActivity(String activityType, String network, String appTool, NodeRef nodeRef) + { + ParameterCheck.mandatory("nodeRef", nodeRef); + + StringBuffer sb = new StringBuffer(); + sb.append("{").append("\"nodeRef\":\"").append(nodeRef.toString()).append("\"").append("}"); + + postActivity(activityType, network, appTool, sb.toString(), ActivityPostDAO.STATUS.PENDING); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#postActivity(java.lang.String, java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public void postActivity(String activityType, String network, String appTool, NodeRef nodeRef, String name) + { + ParameterCheck.mandatory("nodeRef", nodeRef); + + StringBuffer sb = new StringBuffer(); + sb.append("{").append("\"nodeRef\":\"").append(nodeRef.toString()).append("\"").append(",") + .append("\"name\":\"").append(name).append("\"") + .append("}"); + + postActivity(activityType, network, appTool, sb.toString(), ActivityPostDAO.STATUS.PENDING); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#postActivity(java.lang.String, java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String, org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef) + */ + public void postActivity(String activityType, String network, String appTool, NodeRef nodeRef, String name, QName typeQName, NodeRef parentNodeRef) + { + // primarily for delete node activities - eg. delete document, delete folder + + ParameterCheck.mandatory("nodeRef", nodeRef); + ParameterCheck.mandatory("typeQName", typeQName); + ParameterCheck.mandatory("parentNodeRef", parentNodeRef); + + StringBuffer sb = new StringBuffer(); + sb.append("{").append("\"nodeRef\":\"").append(nodeRef.toString()).append("\"").append(",") + .append("\"name\":\"").append(name).append("\"").append(",") + .append("\"typeQName\":\"").append(typeQName.toPrefixString()).append("\"").append(",") // TODO toPrefixString does not return prefix ??!! + .append("\"parentNodeRef\":\"").append(parentNodeRef.toString()).append("\"") + .append("}"); + + postActivity(activityType, network, appTool, sb.toString(), ActivityPostDAO.STATUS.PENDING); + } + + private void postActivity(String activityType, String siteNetwork, String appTool, String activityData, ActivityPostDAO.STATUS status) + { + String currentUser = AuthenticationUtil.getCurrentUserName(); + + try + { + // optional - default to empty string + if (siteNetwork == null) + { + siteNetwork = ""; + } + else if (siteNetwork.length() > MAX_LEN_SITE_ID) + { + throw new AlfrescoRuntimeException("Invalid site network - exceeds " + MAX_LEN_SITE_ID + " chars: " + siteNetwork); + } + + // optional - default to empty string + if (appTool == null) + { + appTool = ""; + } + else if (appTool.length() > MAX_LEN_APP_TOOL_ID) + { + throw new AlfrescoRuntimeException("Invalid app tool - exceeds " + MAX_LEN_APP_TOOL_ID + " chars: " + appTool); + } + + // required + ParameterCheck.mandatoryString("activityType", activityType); + + if (activityType.length() > MAX_LEN_ACTIVITY_TYPE) + { + throw new AlfrescoRuntimeException("Invalid activity type - exceeds " + MAX_LEN_ACTIVITY_TYPE + " chars: " + activityType); + } + + // optional - default to empty string + if (activityData == null) + { + activityData = ""; + } + else if (activityType.length() > MAX_LEN_ACTIVITY_DATA) + { + throw new AlfrescoRuntimeException("Invalid activity data - exceeds " + MAX_LEN_ACTIVITY_DATA + " chars: " + activityData); + } + + // required + ParameterCheck.mandatoryString("currentUser", currentUser); + + if (currentUser.length() > MAX_LEN_USER_ID) + { + throw new AlfrescoRuntimeException("Invalid user - exceeds " + MAX_LEN_USER_ID + " chars: " + currentUser); + } + else if ((! currentUser.equals(AuthenticationUtil.SYSTEM_USER_NAME)) && (! userNamesAreCaseSensitive)) + { + // user names are case-insensitive + currentUser = currentUser.toLowerCase(); + } + } + catch (AlfrescoRuntimeException e) + { + // log error and throw exception + logger.error(e); + throw e; + } + + try + { + Date postDate = new Date(); + ActivityPostDAO activityPost = new ActivityPostDAO(); + activityPost.setUserId(currentUser); + activityPost.setSiteNetwork(siteNetwork); + activityPost.setAppTool(appTool); + activityPost.setActivityData(activityData); + activityPost.setActivityType(activityType); + activityPost.setPostDate(postDate); + activityPost.setStatus(status.toString()); + activityPost.setLastModified(postDate); + + // hash the userid to generate a job task node + int nodeCount = feedGenerator.getEstimatedGridSize(); + int userHashCode = currentUser.hashCode(); + int nodeHash = (userHashCode % nodeCount) + 1; + + activityPost.setJobTaskNode(nodeHash); + + try + { + long postId = postDaoService.insertPost(activityPost); + + if (logger.isDebugEnabled()) + { + activityPost.setId(postId); + logger.debug("Posted: " + activityPost); + } + } + catch (SQLException e) + { + throw new AlfrescoRuntimeException("Failed to post activity: " + e, e); + } + } + catch (AlfrescoRuntimeException e) + { + // log error, subsume exception + logger.error(e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#getUserFeedEntries(java.lang.String, java.lang.String, java.lang.String) + */ + public List> getUserFeedEntries(String feedUserId, String format, String siteId) + { + // NOTE: siteId is optional + ParameterCheck.mandatoryString("feedUserId", feedUserId); + ParameterCheck.mandatoryString("format", format); + + List> activityFeedModels = new ArrayList>(); + + if (! userNamesAreCaseSensitive) + { + feedUserId = feedUserId.toLowerCase(); + } + + String currentUser = AuthenticationUtil.getCurrentUserName(); + if (currentUser != null) + { + if ((! authorityService.isAdminAuthority(currentUser)) && (! currentUser.equals(feedUserId))) + { + throw new AlfrescoRuntimeException("Unable to get feed entries for '" + feedUserId + "' - currently logged in as '" + currentUser +"'"); + } + + try + { + List activityFeeds = null; + if (siteId != null) + { + activityFeeds = feedDaoService.selectUserFeedEntries(feedUserId, format, siteId); + } + else + { + activityFeeds = feedDaoService.selectUserFeedEntries(feedUserId, format); + } + + int count = 0; + for (ActivityFeedDAO activityFeed : activityFeeds) + { + count++; + if (count > maxFeedItems) + { + break; + } + activityFeedModels.add(JSONtoFmModel.convertJSONObjectToMap(activityFeed.getJSONString())); + } + } + catch (SQLException se) + { + throw new AlfrescoRuntimeException("Unable to get user feed entries: " + se.getMessage()); + } + catch (JSONException je) + { + throw new AlfrescoRuntimeException("Unable to get user feed entries: " + je.getMessage()); + } + } + else + { + throw new AlfrescoRuntimeException("Unable to get user feed entries - current user is null"); + } + + return activityFeedModels; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#getSiteFeedEntries(java.lang.String, java.lang.String) + */ + public List> getSiteFeedEntries(String siteId, String format) + { + ParameterCheck.mandatoryString("siteId", siteId); + ParameterCheck.mandatoryString("format", format); + + List> activityFeedModels = new ArrayList>(); + + String currentUser = AuthenticationUtil.getCurrentUserName(); + if (currentUser != null) + { + // TODO - check whether site is public or private, if private, check whether user is member or admin - authorityService.isAdminAuthority(currentUser)) + try + { + List activityFeeds = feedDaoService.selectSiteFeedEntries(siteId, format); + + int count = 0; + for (ActivityFeedDAO activityFeed : activityFeeds) + { + count++; + if (count > maxFeedItems) + { + break; + } + activityFeedModels.add(JSONtoFmModel.convertJSONObjectToMap(activityFeed.getJSONString())); + } + } + catch (SQLException se) + { + throw new AlfrescoRuntimeException("Unable to get site feed entries: " + se.getMessage()); + } + catch (JSONException je) + { + throw new AlfrescoRuntimeException("Unable to get site feed entries: " + je.getMessage()); + } + } + else + { + throw new AlfrescoRuntimeException("Unable to get site feed entries - current user is null"); + } + + return activityFeedModels; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#setFeedControl(org.alfresco.service.cmr.activities.FeedControl) + */ + public void setFeedControl(FeedControl feedControl) + { + ParameterCheck.mandatory("feedControl", feedControl); + + String userId = AuthenticationUtil.getCurrentUserName(); + if (! userNamesAreCaseSensitive) + { + userId = userId.toLowerCase(); + } + + try + { + if (! existsFeedControl(feedControl)) + { + feedControlDaoService.insertFeedControl(new FeedControlDAO(userId, feedControl)); + } + } + catch (SQLException e) + { + throw new AlfrescoRuntimeException("Failed to set feed control: " + e, e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#getFeedControls() + */ + public List getFeedControls() + { + String userId = AuthenticationUtil.getCurrentUserName(); + return getFeedControlsImpl(userId); + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#getFeedControls(java.lang.String) + */ + public List getFeedControls(String userId) + { + ParameterCheck.mandatoryString("userId", userId); + String currentUser = AuthenticationUtil.getCurrentUserName(); + + if ((currentUser == null) || ((! currentUser.equals(AuthenticationUtil.getSystemUserName())) && (! currentUser.equals(userId)) && (! authorityService.isAdminAuthority(currentUser)))) + { + throw new AlfrescoRuntimeException("Current user " + currentUser + " is not permitted to get feed controls for " + userId); + } + + return getFeedControlsImpl(userId); + } + + private List getFeedControlsImpl(String userId) + { + ParameterCheck.mandatoryString("userId", userId); + + if (! userNamesAreCaseSensitive) + { + userId = userId.toLowerCase(); + } + + try + { + List feedControlDaos = feedControlDaoService.selectFeedControls(userId); + List feedControls = new ArrayList(feedControlDaos.size()); + for (FeedControlDAO feedControlDao : feedControlDaos) + { + feedControls.add(feedControlDao.getFeedControl()); + } + + return feedControls; + } + catch (SQLException e) + { + throw new AlfrescoRuntimeException("Failed to set feed control: " + e, e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#deleteFeedControl(org.alfresco.service.cmr.activities.FeedControl) + */ + public void unsetFeedControl(FeedControl feedControl) + { + ParameterCheck.mandatory("feedControl", feedControl); + + String userId = AuthenticationUtil.getCurrentUserName(); + if (! userNamesAreCaseSensitive) + { + userId = userId.toLowerCase(); + } + + try + { + feedControlDaoService.deleteFeedControl(new FeedControlDAO(userId, feedControl)); + } + catch (SQLException e) + { + throw new AlfrescoRuntimeException("Failed to set feed control: " + e, e); + } + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.activities.ActivityService#existsFeedControl(java.lang.String, org.alfresco.service.cmr.activities.FeedControl) + */ + public boolean existsFeedControl(FeedControl feedControl) + { + ParameterCheck.mandatory("feedControl", feedControl); + + String userId = AuthenticationUtil.getCurrentUserName(); + + if (! userNamesAreCaseSensitive) + { + userId = userId.toLowerCase(); + } + + try + { + long id = feedControlDaoService.selectFeedControl(new FeedControlDAO(userId, feedControl)); + return (id != -1); + } + catch (SQLException e) + { + throw new AlfrescoRuntimeException("Failed to query feed control: " + e, e); + } + } +} diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImplTest.java b/source/java/org/alfresco/repo/activities/ActivityServiceImplTest.java new file mode 100644 index 0000000000..8253d5d5cf --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImplTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2005-2008 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.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.jscript.ClasspathScriptLocation; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.activities.FeedControl; +import org.alfresco.service.cmr.repository.ScriptLocation; +import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.util.BaseSpringTest; + +/** + * Activity Service Implementation unit test + * + * @author janv + */ +public class ActivityServiceImplTest extends BaseSpringTest +{ + private ActivityService activityService; + private ScriptService scriptService; + private AuthenticationService authenticationService; + + private static final String ADMIN_UN = "admin"; + private static final String ADMIN_PW = "admin"; + + private static final String USER_UN = "bob"; + private static final String USER_PW = "bob"; + + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + + // Get the required services + this.activityService = (ActivityService)this.applicationContext.getBean("ActivityService"); + this.scriptService = (ScriptService)this.applicationContext.getBean("ScriptService"); + + this.authenticationService = (AuthenticationService)applicationContext.getBean("authenticationService"); + + authenticationService.authenticate(ADMIN_UN, ADMIN_PW.toCharArray()); + } + + protected void onTearDownInTransaction() throws Exception + { + authenticationService.clearCurrentSecurityContext(); + } + + public void testPostValidActivities() throws Exception + { + this.activityService.postActivity("org.alfresco.testActivityType1", null, null, ""); + + this.activityService.postActivity("org.alfresco.testActivityType2", "", "", ""); + + this.activityService.postActivity("org.alfresco.testActivityType3", "site1", "appToolA", "{ \"var1\" : \"val1\" }"); + } + + public void testPostInvalidActivities() throws Exception + { + try + { + this.activityService.postActivity("", "", "", null, ""); + fail("invalid post activity"); + } + catch (IllegalArgumentException iae) + { + assertTrue(iae.getMessage().contains("nodeRef is a mandatory parameter")); + } + + try + { + this.activityService.postActivity("", "", "", ""); + fail("invalid post activity"); + } + catch (IllegalArgumentException iae) + { + assertTrue(iae.getMessage().contains("activityType is a mandatory parameter")); + } + } + + public void testGetEmptySiteFeed() throws Exception + { + authenticationService.clearCurrentSecurityContext(); + + if(! authenticationService.authenticationExists(USER_UN)) + { + authenticationService.createAuthentication(USER_UN, USER_PW.toCharArray()); + } + authenticationService.authenticate(USER_UN, USER_PW.toCharArray()); + + List> siteFeedEntries = this.activityService.getSiteFeedEntries("unknown site", "some format"); + + assertNotNull(siteFeedEntries); + assertTrue(siteFeedEntries.isEmpty()); + } + + public void testGetEmptyUserFeed() throws Exception + { + + List> userFeedEntries = this.activityService.getUserFeedEntries("unknown user", "some format", null); + + assertNotNull(userFeedEntries); + assertTrue(userFeedEntries.isEmpty()); + } + + public void testJSAPI() throws Exception + { + ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/activities/script/test_activityService.js"); + String result = (String)this.scriptService.executeScript(location, new HashMap(0)); + + // Check the result and fail if message returned + if (result != null && result.length() != 0) + { + fail("The activity service test JS script failed: " + result); + } + } + + public void testFeedControls() throws Exception + { + List feedControls = this.activityService.getFeedControls(USER_UN); + assertNotNull(feedControls); + assertTrue(feedControls.isEmpty()); + + authenticationService.clearCurrentSecurityContext(); + + if(! authenticationService.authenticationExists(USER_UN)) + { + authenticationService.createAuthentication(USER_UN, USER_PW.toCharArray()); + } + authenticationService.authenticate(USER_UN, USER_PW.toCharArray()); + + feedControls = this.activityService.getFeedControls(); + assertNotNull(feedControls); + assertTrue(feedControls.isEmpty()); + + assertFalse(this.activityService.existsFeedControl(new FeedControl("mySite1", "appTool1"))); + + this.activityService.setFeedControl(new FeedControl("mySite1", null)); + this.activityService.setFeedControl(new FeedControl("mySite1", "appTool1")); + this.activityService.setFeedControl(new FeedControl(null, "appTool2")); + + feedControls = this.activityService.getFeedControls(); + assertEquals(3, feedControls.size()); + + feedControls = this.activityService.getFeedControls(USER_UN); + assertEquals(3, feedControls.size()); + + assertTrue(this.activityService.existsFeedControl(new FeedControl("mySite1", "appTool1"))); + + this.activityService.unsetFeedControl(new FeedControl("mySite1", "appTool1")); + + assertFalse(this.activityService.existsFeedControl(new FeedControl("mySite1", "appTool1"))); + + feedControls = this.activityService.getFeedControls(); + assertEquals(2, feedControls.size()); + + this.activityService.unsetFeedControl(new FeedControl("mySite1", null)); + this.activityService.unsetFeedControl(new FeedControl(null, "appTool2")); + + feedControls = this.activityService.getFeedControls(); + assertEquals(0, feedControls.size()); + } +} diff --git a/source/java/org/alfresco/repo/activities/ActivityType.java b/source/java/org/alfresco/repo/activities/ActivityType.java new file mode 100644 index 0000000000..d799100402 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ActivityType.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2008 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; + +public interface ActivityType +{ + // pre-defined alfresco activity types + + // generic fallback (if specific template is missing) + public final String GENERIC_FALLBACK = "org.alfresco.generic"; +} diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java new file mode 100644 index 0000000000..e74c3aeb96 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import org.alfresco.repo.activities.post.ActivityPostDaoService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.VmShutdownListener; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.JobExecutionException; + +/** + * Implementations of the abstract feed generator component are responsible for generating activity feed entries + */ +public abstract class AbstractFeedGenerator implements FeedGenerator +{ + private static Log logger = LogFactory.getLog(AbstractFeedGenerator.class); + + private static VmShutdownListener vmShutdownListener = new VmShutdownListener(AbstractFeedGenerator.class.getName()); + + private int maxItemsPerCycle = 100; + + private ActivityPostDaoService postDaoService; + private AuthenticationService authenticationService; + + private String repoEndPoint; // http://hostname:port/webapp (eg. http://localhost:8080/alfresco) + + private RepoCtx ctx = null; + + private boolean busy; + + public void setPostDaoService(ActivityPostDaoService postDaoService) + { + this.postDaoService = postDaoService; + } + + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setRepoEndPoint(String repoEndPoint) + { + this.repoEndPoint = repoEndPoint; + } + + public void setMaxItemsPerCycle(int maxItemsPerCycle) + { + this.maxItemsPerCycle = maxItemsPerCycle; + } + + public int getMaxItemsPerCycle() + { + return this.maxItemsPerCycle; + } + + public ActivityPostDaoService getPostDaoService() + { + return this.postDaoService; + } + + public AuthenticationService getAuthenticationService() + { + return this.authenticationService; + } + + public RepoCtx getWebScriptsCtx() + { + return this.ctx; + } + + public void init() throws Exception + { + ctx = new RepoCtx(repoEndPoint); + + busy = false; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + private void checkProperties() + { + PropertyCheck.mandatory(this, "postDaoService", postDaoService); + } + + + public void execute() throws JobExecutionException + { + if (busy) + { + logger.warn("Still busy ..."); + return; + } + + checkProperties(); + try + { + // run at least one job cycle + boolean moreWork = true; + while (moreWork) + { + moreWork = generate(); + } + } + catch (Throwable e) + { + // If the VM is shutting down, then ignore + if (vmShutdownListener.isVmShuttingDown()) + { + // Ignore + } + else + { + logger.error("Exception during generation of feeds", e); + } + } + finally + { + busy = false; + } + } + + protected abstract boolean generate() throws Exception; +} diff --git a/source/java/org/alfresco/repo/activities/feed/ActivityFeedDAO.java b/source/java/org/alfresco/repo/activities/feed/ActivityFeedDAO.java new file mode 100644 index 0000000000..6c3e030308 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/ActivityFeedDAO.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import java.util.Date; + +import org.json.JSONException; +import org.json.JSONStringer; + +/** + * Activity Feed DAO + */ +public class ActivityFeedDAO +{ + private long id; // internal DB-generated id + private String activityType; + private String activitySummary; + private String activitySummaryFormat; + private String feedUserId; + private String postUserId; + private String siteNetwork; + private String appTool; + private Date postDate; + private Date feedDate; // for debug + private long postId; // for debug - not an explicit FK constraint, could be used to implement re-generate + + + public long getId() + { + return id; + } + + public void setId(long id) + { + this.id = id; + } + + public String getActivitySummary() + { + return activitySummary; + } + + public void setActivitySummary(String summary) + { + this.activitySummary = summary; + } + + public String getFeedUserId() + { + return feedUserId; + } + + public void setFeedUserId(String userid) + { + this.feedUserId = userid; + } + + public String getPostUserId() + { + return postUserId; + } + + public void setPostUserId(String userid) + { + this.postUserId = userid; + } + + public String getActivitySummaryFormat() + { + return activitySummaryFormat; + } + + public void setActivitySummaryFormat(String format) + { + this.activitySummaryFormat = format; + } + + public String getSiteNetwork() + { + return siteNetwork; + } + + public void setSiteNetwork(String siteNetwork) + { + this.siteNetwork = siteNetwork; + } + + public String getActivityType() + { + return activityType; + } + public void setActivityType(String activityType) + { + this.activityType = activityType; + } + + public Date getPostDate() + { + return postDate; + } + + public void setPostDate(Date postDate) + { + this.postDate = postDate; + } + + public long getPostId() + { + return postId; + } + + public void setPostId(long postId) + { + this.postId = postId; + } + + public Date getFeedDate() + { + return feedDate; + } + + public void setFeedDate(Date feedDate) + { + this.feedDate = feedDate; + } + + public String getAppTool() + { + return appTool; + } + + public void setAppTool(String appTool) + { + this.appTool = appTool; + } + + public String getJSONString() throws JSONException + { + return new JSONStringer() + .object() + .key("postUserId") + .value(postUserId) + .key("postDate") + .value(postDate) + .key("feedUserId") + .value(feedUserId) + .key("siteNetwork") + .value(siteNetwork) + .key("activityType") + .value(activityType) + .key("activitySummary") + .value(activitySummary) + .key("activitySummaryFormat") + .value(activitySummaryFormat) + .endObject().toString(); + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/ActivityFeedDaoService.java b/source/java/org/alfresco/repo/activities/feed/ActivityFeedDaoService.java new file mode 100644 index 0000000000..0a4442ca64 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/ActivityFeedDaoService.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.alfresco.repo.activities.ibatis.ActivityDaoService; + +/** + * Interface for activity feed DAO service + */ +public interface ActivityFeedDaoService extends ActivityDaoService +{ + public long insertFeedEntry(ActivityFeedDAO activityFeed) throws SQLException; + + public int deleteFeedEntries(Date keepDate) throws SQLException; + + public List selectUserFeedEntries(String feedUserId, String format) throws SQLException; + + public List selectUserFeedEntries(String feedUserId, String format, String siteId) throws SQLException; + + public List selectSiteFeedEntries(String siteUserId, String format) throws SQLException; +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedGenerator.java b/source/java/org/alfresco/repo/activities/feed/FeedGenerator.java new file mode 100644 index 0000000000..fca3a889e0 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedGenerator.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import org.quartz.JobExecutionException; + +/** + * Interface for feed generator + */ +public interface FeedGenerator +{ + public void execute() throws JobExecutionException; + + public int getEstimatedGridSize(); +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedGeneratorJob.java b/source/java/org/alfresco/repo/activities/feed/FeedGeneratorJob.java new file mode 100644 index 0000000000..c3c97a6b29 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedGeneratorJob.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Executes scheduled feed generator quartz-job - refer to scheduled-jobs-context.xml + */ +public class FeedGeneratorJob implements Job +{ + public FeedGeneratorJob() + { + } + + /** + * Calls the feed generator to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the feed cleaner to use + Object feedGeneratorObj = jobData.get("feedGenerator"); + if (feedGeneratorObj == null || !(feedGeneratorObj instanceof FeedGenerator)) + { + throw new AlfrescoRuntimeException( + "FeedGeneratorObj data must contain valid 'feedGenerator' reference"); + } + FeedGenerator feedGenerator = (FeedGenerator)feedGeneratorObj; + feedGenerator.execute(); + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedGridJob.java b/source/java/org/alfresco/repo/activities/feed/FeedGridJob.java new file mode 100644 index 0000000000..ac6c31d99e --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedGridJob.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import java.io.Serializable; + +/** + * Interface for feed grid job + */ +public interface FeedGridJob +{ + public void setArgument(JobSettings arg); + + public JobSettings getArgument(); + + public Serializable execute() throws Exception; +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java new file mode 100644 index 0000000000..2467322fd6 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedTaskProcessor.java @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.repo.activities.feed.control.FeedControlDAO; +import org.alfresco.repo.activities.post.ActivityPostDAO; +import org.alfresco.repo.template.ISO8601DateFormatMethod; +import org.alfresco.util.Base64; +import org.alfresco.util.JSONtoFmModel; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import freemarker.cache.URLTemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.DefaultObjectWrapper; +import freemarker.template.Template; +import freemarker.template.TemplateException; + +/** + * Responsible for processing the individual task + */ +public abstract class FeedTaskProcessor +{ + private static final Log logger = LogFactory.getLog(FeedTaskProcessor.class); + + private static final String defaultFormat = "text"; + private static final String[] formats = {"atomentry", "rss", "json", "html", "xml", defaultFormat}; + + private static final String URL_SERVICE_SITES = "/api/sites"; + private static final String URL_MEMBERSHIPS = "/memberships"; + + private static final String URL_SERVICE_TEMPLATES = "/api/activities/templates"; + private static final String URL_SERVICE_TEMPLATE = "/api/activities/template"; + + + public void process(int jobTaskNode, long minSeq, long maxSeq, RepoCtx ctx) throws Exception + { + if (logger.isDebugEnabled()) + { + logger.debug(">>> Process: jobTaskNode '" + jobTaskNode + "' from seq '" + minSeq + "' to seq '" + maxSeq + "' on this node from grid job."); + } + + ActivityPostDAO selector = new ActivityPostDAO(); + selector.setJobTaskNode(jobTaskNode); + selector.setMinId(minSeq); + selector.setMaxId(maxSeq); + selector.setStatus(ActivityPostDAO.STATUS.POSTED.toString()); + + String ticket = ctx.getTicket(); + + List activityPosts = null; + int totalGenerated = 0; + + try + { + activityPosts = selectPosts(selector); + + if (logger.isDebugEnabled()) { logger.debug(">>> Process: " + activityPosts.size() + " activity posts"); } + + Configuration cfg = getFreemarkerConfiguration(ctx); + + Map> activityTemplates = new HashMap>(10); + + // for each activity post ... + for (ActivityPostDAO activityPost : activityPosts) + { + String postingUserId = activityPost.getUserId(); + String activityType = activityPost.getActivityType(); + + // eg. org.alfresco.folder.added -> added + String baseActivityType = getBaseActivityType(activityType); + + List fmTemplates = activityTemplates.get(baseActivityType); + + if (fmTemplates == null) + { + // eg. org.alfresco.folder.added -> /org/alfresco/folder/added (note: the leading slash) + String templateSubPath = getTemplateSubPath(activityType); + + fmTemplates = new ArrayList(0); + while (true) + { + int idx = templateSubPath.lastIndexOf("/"); + if (idx != -1) + { + templateSubPath = templateSubPath.substring(0, idx); + Map> templates = null; + try + { + // Repository callback to get list of FreeMarker templates for given activity type + templates = getActivityTypeTemplates(ctx.getRepoEndPoint(), ticket, templateSubPath+"/"); + } + catch (FileNotFoundException fnfe) + { + // ignore - path does not exist + } + if (templates != null) + { + if (templates.get(baseActivityType) != null) + { + // add templates, if format not already included + addMissingFormats(activityType, fmTemplates, templates.get(baseActivityType)); + } + + // special fallback case + if (templates.get("generic") != null) + { + // add templates, if format not already included + addMissingFormats(activityType, fmTemplates, templates.get("generic")); + } + } + } + else + { + break; + } + } + + activityTemplates.put(baseActivityType, fmTemplates); + } + + if (fmTemplates.size() == 0) + { + logger.error(">>> Skipping activity post " + activityPost.getId() + " since no specific/generic templates for activityType: " + activityType ); + updatePostStatus(activityPost.getId(), ActivityPostDAO.STATUS.ERROR); + continue; + } + + Map model = null; + try + { + model = JSONtoFmModel.convertJSONObjectToMap(activityPost.getActivityData()); + } + catch(JSONException je) + { + logger.error(">>> Skipping activity post " + activityPost.getId() + " due to invalid activity data: " + je); + updatePostStatus(activityPost.getId(), ActivityPostDAO.STATUS.ERROR); + continue; + } + + model.put("activityType", activityPost.getActivityType()); + model.put("siteNetwork", activityPost.getSiteNetwork()); + model.put("userId", activityPost.getUserId()); + model.put("id", activityPost.getId()); + model.put("date", activityPost.getPostDate()); // post date rather than time that feed is generated + model.put("xmldate", new ISO8601DateFormatMethod()); + model.put("repoEndPoint", ctx.getRepoEndPoint()); + + Set connectedUsers = null; + if ((activityPost.getSiteNetwork() == null) || (activityPost.getSiteNetwork().length() == 0)) + { + connectedUsers = new HashSet(1); + } + else + { + try + { + // Repository callback to get site members + connectedUsers = getSiteMembers(ctx, activityPost.getSiteNetwork()); + } + catch(Exception e) + { + logger.error(">>> Skipping activity post " + activityPost.getId() + " since failed to get site members: " + e); + updatePostStatus(activityPost.getId(), ActivityPostDAO.STATUS.ERROR); + continue; + } + } + + connectedUsers.add(""); // add empty posting userid - to represent site feed ! + + try + { + startTransaction(); + + if (logger.isDebugEnabled()) + { + logger.debug(">>> Process: " + connectedUsers.size() + " candidate connections for activity post " + activityPost.getId()); + } + + int excludedConnections = 0; + + for (String connectedUser : connectedUsers) + { + List feedControls = null; + if (! connectedUser.equals("")) + { + feedControls = getFeedControls(connectedUser); + } + + // filter based on opt-out feed controls (if any) + if (! acceptActivity(activityPost, feedControls)) + { + excludedConnections++; + } + else + { + for (String fmTemplate : fmTemplates) + { + // determine format - based on template naming convention + String formatFound = null; + for (String format : formats) + { + if (fmTemplate.contains("."+format+".")) + { + formatFound = format; + break; + } + } + + if (formatFound == null) + { + formatFound = defaultFormat; + logger.warn("Unknown format for: " + fmTemplate + " default to '"+formatFound+"'"); + } + + ActivityFeedDAO feed = new ActivityFeedDAO(); + + // Generate activity feed summary + feed.setFeedUserId(connectedUser); + feed.setPostUserId(postingUserId); + feed.setActivityType(activityType); + + if (formatFound.equals("json")) + { + // allows generic JSON template to simply pass straight through + model.put("activityData", activityPost.getActivityData()); + } + + String activitySummary = processFreemarker(fmTemplate, cfg, model); + if (! activitySummary.equals("")) + { + feed.setActivitySummary(activitySummary); + feed.setActivitySummaryFormat(formatFound); + feed.setSiteNetwork(activityPost.getSiteNetwork()); + feed.setAppTool(activityPost.getAppTool()); + feed.setPostDate(activityPost.getPostDate()); + feed.setPostId(activityPost.getId()); + feed.setFeedDate(new Date()); + + // Insert activity feed + insertFeedEntry(feed); // ignore returned feedId + + totalGenerated++; + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Empty template result for activityType '" + activityType + "' using format '" + formatFound + "' hence skip feed entry (activity post " + activityPost.getId() + ")"); + } + } + } + } + } + + updatePostStatus(activityPost.getId(), ActivityPostDAO.STATUS.PROCESSED); + + commitTransaction(); + + if (logger.isDebugEnabled()) + { + logger.debug(">>> Processed: " + (connectedUsers.size() - excludedConnections) + " connections for activity post " + activityPost.getId() + " (excluded " + excludedConnections + ")"); + } + } + finally + { + endTransaction(); + } + } + } + catch(SQLException se) + { + logger.error(se); + throw se; + } + finally + { + logger.info(">>> Generated " + totalGenerated + " activity feed entries for " + (activityPosts == null ? 0 : activityPosts.size()) + " activity posts"); + } + } + + public abstract void startTransaction() throws SQLException; + + public abstract void commitTransaction() throws SQLException; + + public abstract void endTransaction() throws SQLException; + + public abstract List selectPosts(ActivityPostDAO selector) throws SQLException; + + public abstract List selectUserFeedControls(String userId) throws SQLException; + + public abstract long insertFeedEntry(ActivityFeedDAO feed) throws SQLException; + + public abstract int updatePostStatus(long id, ActivityPostDAO.STATUS status) throws SQLException; + + + protected String callWebScript(String urlString, String ticket) throws MalformedURLException, URISyntaxException, IOException + { + URL url = new URL(urlString); + + if (logger.isDebugEnabled()) + { + logger.debug(">>> Request URI: " + url.toURI()); + } + + HttpURLConnection conn = (HttpURLConnection)url.openConnection(); + conn.setRequestMethod("GET"); + + if (ticket != null) + { + // add Base64 encoded authorization header + // refer to: http://wiki.alfresco.com/wiki/Web_Scripts_Framework#HTTP_Basic_Authentication + conn.addRequestProperty("Authorization", "Basic " + Base64.encodeBytes(ticket.getBytes())); + } + + String result = null; + InputStream is = null; + BufferedReader br = null; + + try + { + is = conn.getInputStream(); + br = new BufferedReader(new InputStreamReader(is)); + + String line = null; + StringBuffer sb = new StringBuffer(); + while(((line = br.readLine()) !=null)) { + sb.append(line); + } + + result = sb.toString(); + + if (logger.isDebugEnabled()) + { + int responseCode = conn.getResponseCode(); + logger.debug(">>> Response code: " + responseCode); + } + } + finally + { + if (br != null) { br.close(); }; + if (is != null) { is.close(); }; + } + + return result; + } + + protected Set getSiteMembers(RepoCtx ctx, String siteId) throws Exception + { + Set members = new HashSet(); + if ((siteId != null) && (siteId.length() != 0)) + { + StringBuffer sbUrl = new StringBuffer(); + sbUrl.append(ctx.getRepoEndPoint()). + append(URL_SERVICE_SITES).append("/").append(siteId).append(URL_MEMBERSHIPS); + + String jsonArrayResult = callWebScript(sbUrl.toString(), ctx.getTicket()); + if ((jsonArrayResult != null) && (jsonArrayResult.length() != 0)) + { + JSONArray ja = new JSONArray(jsonArrayResult); + for (int i = 0; i < ja.length(); i++) + { + JSONObject member = (JSONObject)ja.get(i); + JSONObject person = (JSONObject)member.getJSONObject("person"); + members.add(person.getString("userName")); + } + } + } + + return members; + } + + protected Map> getActivityTypeTemplates(String repoEndPoint, String ticket, String subPath) throws Exception + { + StringBuffer sbUrl = new StringBuffer(); + sbUrl.append(repoEndPoint).append(URL_SERVICE_TEMPLATES).append("?p=").append(subPath).append("*").append("&format=json"); + + String jsonArrayResult = null; + try + { + jsonArrayResult = callWebScript(sbUrl.toString(), ticket); + } + catch (FileNotFoundException e) + { + return null; + } + + List allTemplateNames = new ArrayList(10); + + if ((jsonArrayResult != null) && (jsonArrayResult.length() != 0)) + { + JSONArray ja = new JSONArray(jsonArrayResult); + for (int i = 0; i < ja.length(); i++) + { + String name = ja.getString(i); + if (! name.contains(" (Working Copy).")) + { + allTemplateNames.add(name); + } + } + } + + Map> activityTemplates = new HashMap>(10); + + for (String template : allTemplateNames) + { + // assume template path = /..ftl + // and base-activityType can contain "." + + String baseActivityType = template; + int idx1 = baseActivityType.lastIndexOf("/"); + if (idx1 != -1) + { + baseActivityType = baseActivityType.substring(idx1+1); + } + + int idx2 = baseActivityType.lastIndexOf("."); + if (idx2 != -1) + { + int idx3 = baseActivityType.substring(0, idx2).lastIndexOf("."); + if (idx3 != -1) + { + baseActivityType = baseActivityType.substring(0, idx3); + + List activityTypeTemplateList = activityTemplates.get(baseActivityType); + if (activityTypeTemplateList == null) + { + activityTypeTemplateList = new ArrayList(1); + activityTemplates.put(baseActivityType, activityTypeTemplateList); + } + activityTypeTemplateList.add(template); + } + } + } + + 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(String fmTemplate, Configuration cfg, Map model) throws IOException, TemplateException, Exception + { + Template myTemplate = cfg.getTemplate(fmTemplate); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Writer out = new OutputStreamWriter(bos); + myTemplate.process(model, out); + out.flush(); + + return new String(bos.toByteArray()); + } + + protected List getFeedControls(String connectedUser) throws SQLException + { + // TODO cache for this run + return selectUserFeedControls(connectedUser); + } + + protected boolean acceptActivity(ActivityPostDAO activityPost, List feedControls) + { + if (feedControls == null) + { + return true; + } + + for (FeedControlDAO feedControl : feedControls) + { + if ((feedControl.getSiteNetwork() == null) && (feedControl.getAppTool() != null)) + { + if (feedControl.getAppTool().equals(activityPost.getAppTool())) + { + // exclude this appTool (across sites) + return false; + } + } + else if ((feedControl.getAppTool() == null) && (feedControl.getSiteNetwork() != null)) + { + if (feedControl.getSiteNetwork().equals(activityPost.getSiteNetwork())) + { + // exclude this site (across appTools) + return false; + } + } + else if ((feedControl.getSiteNetwork() != null) && (feedControl.getAppTool() != null)) + { + if ((feedControl.getSiteNetwork().equals(activityPost.getSiteNetwork())) && + (feedControl.getAppTool().equals(activityPost.getAppTool()))) + { + // exclude this appTool for this site + return false; + } + } + } + + return true; + } + + protected void addMissingFormats(String activityType, List fmTemplates, List templatesToAdd) + { + for (String templateToAdd : templatesToAdd) + { + int idx1 = templateToAdd.lastIndexOf("."); + if (idx1 != -1) + { + int idx2 = templateToAdd.substring(0, idx1).lastIndexOf("."); + if (idx2 != -1) + { + String templateFormat = templateToAdd.substring(idx2+1, idx1); + + boolean found = false; + for (String fmTemplate : fmTemplates) + { + if (fmTemplate.contains("."+templateFormat+".")) + { + found = true; + } + } + + if (! found) + { + if (logger.isDebugEnabled()) + { + logger.debug(">>> Add template '" + templateToAdd + "' for type '" + activityType + "'"); + } + fmTemplates.add(templateToAdd); + } + } + } + } + } + + protected String getTemplateSubPath(String activityType) + { + return (! activityType.startsWith("/") ? "/" : "") + activityType.replace(".", "/"); + } + + protected String getBaseActivityType(String activityType) + { + String[] parts = activityType.split("\\."); + + return (parts.length != 0 ? parts[parts.length-1] : ""); + } + + protected class TemplateWebScriptLoader extends URLTemplateLoader + { + private String repoEndPoint; + private String ticketId; + + public TemplateWebScriptLoader(String repoEndPoint, String ticketId) + { + this.repoEndPoint = repoEndPoint; + this.ticketId = ticketId; + } + + public URL getURL(String templatePath) + { + try + { + StringBuffer sb = new StringBuffer(); + sb.append(this.repoEndPoint). + append(URL_SERVICE_TEMPLATE).append("?p=").append(templatePath). + append("&format=text"). + append("&alf_ticket=").append(ticketId); + + if (logger.isDebugEnabled()) + { + logger.debug(">>> getURL: " + sb.toString()); + } + + return new URL(sb.toString()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/FeedTaskSplit.java b/source/java/org/alfresco/repo/activities/feed/FeedTaskSplit.java new file mode 100644 index 0000000000..87061c6ad3 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/FeedTaskSplit.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Responsible for splitting the feed task into feed jobs (to be executed locally or on a grid) + */ +public class FeedTaskSplit +{ + private static Log logger = LogFactory.getLog(FeedTaskSplit.class); + + public Collection split(int gridSize, JobSettings splitSettings) + { + long maxSequence = splitSettings.getMaxSeq(); + + if (logger.isDebugEnabled()) + { + logger.debug("split: start - gridSize = " + gridSize + ", maxSequence = " + maxSequence); + } + + long minSequence = maxSequence - splitSettings.getMaxItemsPerCycle() + 1; + + splitSettings.setMinSeq((minSequence >= 0L ? minSequence : 0L)); + + List jobs = new ArrayList(gridSize); + + int maxNodeHash = splitSettings.getJobTaskNode(); + + // note: gridSize may change between runs, hence use maximum node hash/bucket for this cycle + for (int n = 1; n <= maxNodeHash; n++) + { + // every job gets its own copy of the jobSettings (with different nodeHash) as an argument. + JobSettings jobSettings = splitSettings.clone(); + jobSettings.setJobTaskNode(n); + + jobs.add(jobSettings); + } + + return jobs; + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/JobSettings.java b/source/java/org/alfresco/repo/activities/feed/JobSettings.java new file mode 100644 index 0000000000..d87cc9fbff --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/JobSettings.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import java.io.Serializable; + +/** + * Job settings passed from grid task to grid job + */ +public class JobSettings implements Serializable +{ + public static final long serialVersionUID = -3896042917378679686L; + + private int jobTaskNode; + private long maxSeq; + private long minSeq; + private RepoCtx ctx; + private int maxItemsPerCycle; + + public int getJobTaskNode() + { + return jobTaskNode; + } + + public void setJobTaskNode(int jobTaskNode) + { + this.jobTaskNode = jobTaskNode; + } + + public long getMaxSeq() + { + return maxSeq; + } + + public void setMaxSeq(long maxSeq) + { + this.maxSeq = maxSeq; + } + + public long getMinSeq() + { + return minSeq; + } + + public void setMinSeq(long minSeq) + { + this.minSeq = minSeq; + } + + public RepoCtx getWebScriptsCtx() { + return ctx; + } + + public void setWebScriptsCtx(RepoCtx ctx) { + this.ctx = ctx; + } + + public int getMaxItemsPerCycle() + { + return maxItemsPerCycle; + } + + public void setMaxItemsPerCycle(int maxItemsPerCycle) + { + this.maxItemsPerCycle = maxItemsPerCycle; + } + + public JobSettings clone() + { + JobSettings js = new JobSettings(); + js.setMaxItemsPerCycle(this.maxItemsPerCycle); + js.setMaxSeq(this.maxSeq); + js.setMinSeq(this.minSeq); + js.setJobTaskNode(this.jobTaskNode); + js.setWebScriptsCtx(this.ctx); // note: shallow copy + return js; + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/RepoCtx.java b/source/java/org/alfresco/repo/activities/feed/RepoCtx.java new file mode 100644 index 0000000000..f57e0d025a --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/RepoCtx.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2008 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.feed; + +import java.io.Serializable; + +/** + * Repository context passed from grid task to grid job + */ +public class RepoCtx implements Serializable +{ + private String repoEndPoint; // http://hostname:port/webapp (eg. http://localhost:8080/alfresco) + private String ticket; + + public static final long serialVersionUID = -3896042917378679686L; + + public RepoCtx(String repoEndPoint) + { + this.repoEndPoint = repoEndPoint.endsWith("/") ? repoEndPoint.substring(0, repoEndPoint.length()-1) : repoEndPoint; + } + + public String getRepoEndPoint() { + return repoEndPoint; + } + + public String getTicket() + { + return ticket; + } + + public void setTicket(String ticket) + { + this.ticket = ticket; + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java new file mode 100644 index 0000000000..0c87bb74af --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2008 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.feed.cleanup; + +import java.sql.SQLException; +import java.util.Date; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.activities.feed.ActivityFeedDaoService; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.VmShutdownListener; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.JobExecutionException; + +/** + * The feed cleaner component is responsible for purging 'obsolete' activity feed entries + */ +public class FeedCleaner +{ + private static Log logger = LogFactory.getLog(FeedCleaner.class); + + private static VmShutdownListener vmShutdownListener = new VmShutdownListener(FeedCleaner.class.getName()); + + private int maxAgeMins = 0; + + private ActivityFeedDaoService feedDaoService; + + public void setFeedDaoService(ActivityFeedDaoService feedDaoService) + { + this.feedDaoService = feedDaoService; + } + + public void setMaxAgeMins(int mins) + { + this.maxAgeMins = mins; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + private void checkProperties() + { + PropertyCheck.mandatory(this, "feedDaoService", feedDaoService); + + // check the max age + if (maxAgeMins <= 0) + { + throw new AlfrescoRuntimeException("Property 'maxAgeMins' must be greater than 0"); + } + } + + public void execute() throws JobExecutionException + { + checkProperties(); + try + { + long nowTimeOffset = new Date().getTime(); + long keepTimeOffset = nowTimeOffset - (maxAgeMins*60*1000); // millsecs = mins * 60 secs * 1000 msecs + Date keepDate = new Date(keepTimeOffset); + + // clean old entries + int deletedCount = feedDaoService.deleteFeedEntries(keepDate); + + if (logger.isDebugEnabled()) + { + + logger.debug("Cleaned " + deletedCount + " entries (upto " + keepDate + ", max age " + maxAgeMins + " mins)"); + } + } + catch (SQLException e) + { + logger.error("Exception during cleanup of feeds", e); + throw new JobExecutionException(e); + } + catch (Throwable e) + { + // If the VM is shutting down, then ignore + if (vmShutdownListener.isVmShuttingDown()) + { + // Ignore + } + else + { + logger.error("Exception during cleanup of feeds", e); + } + } + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanupJob.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanupJob.java new file mode 100644 index 0000000000..69faac4a07 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanupJob.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2008 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.feed.cleanup; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Executes scheduled feed cleaner quartz-job - refer to scheduled-jobs-context.xml + */ +public class FeedCleanupJob implements Job +{ + public FeedCleanupJob() + { + } + + /** + * Calls the feed cleaner to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the feed cleaner to use + Object feedCleanerObj = jobData.get("feedCleaner"); + if (feedCleanerObj == null || !(feedCleanerObj instanceof FeedCleaner)) + { + throw new AlfrescoRuntimeException( + "FeedCleanupJob data must contain valid 'feedCleaner' reference"); + } + FeedCleaner feedCleaner = (FeedCleaner)feedCleanerObj; + feedCleaner.execute(); + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/control/FeedControlDAO.java b/source/java/org/alfresco/repo/activities/feed/control/FeedControlDAO.java new file mode 100644 index 0000000000..a9552e9fe0 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/control/FeedControlDAO.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2008 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.feed.control; + +import java.util.Date; + +import org.alfresco.service.cmr.activities.FeedControl; + +/** + * Activity Feed Control DAO + */ +public class FeedControlDAO +{ + private long id; // internal DB-generated id + private String feedUserId; + private String siteNetwork; + private String appTool; + + private Date lastModified; // when inserted + + // TODO - review - deleted feed controls are not kept and available feed controls are currently retrieved during generation, hence + // it is possible for a feed control to be applied even if lastModified is greater than postDate - could check the date ! + // it is also possible for a feed control to not be applied if it is deleted just after the post - would need to keep, at least until next generation + + public FeedControlDAO() + { + } + + public FeedControlDAO(String feedUserId) + { + this.feedUserId = feedUserId; + } + + public FeedControlDAO(String feedUserId, FeedControl feedControl) + { + this.feedUserId = feedUserId; + this.siteNetwork = feedControl.getSiteId(); + this.appTool = feedControl.getAppToolId(); + } + + public FeedControl getFeedControl() + { + return new FeedControl(this.siteNetwork, this.appTool); + } + + public long getId() + { + return id; + } + + public void setId(long id) + { + this.id = id; + } + + public String getSiteNetwork() + { + return siteNetwork; + } + + public void setSiteNetwork(String siteNetwork) + { + this.siteNetwork = siteNetwork; + } + + public String getAppTool() + { + return appTool; + } + + public void setAppTool(String appTool) + { + this.appTool = appTool; + } + + public String getFeedUserId() + { + return feedUserId; + } + + public void setFeedUserId(String feedUserId) + { + this.feedUserId = feedUserId; + } + + public Date getLastModified() + { + return lastModified; + } + + public void setLastModified(Date lastModified) + { + this.lastModified = lastModified; + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/control/FeedControlDaoService.java b/source/java/org/alfresco/repo/activities/feed/control/FeedControlDaoService.java new file mode 100644 index 0000000000..d05df58b9a --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/control/FeedControlDaoService.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2005-2008 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.feed.control; + +import java.sql.SQLException; +import java.util.List; + +/** + * Interface for user activity feed controls DAO service + */ +public interface FeedControlDaoService +{ + public long insertFeedControl(FeedControlDAO activityFeedControl) throws SQLException; + + public int deleteFeedControl(FeedControlDAO activityFeedControl) throws SQLException; + + public List selectFeedControls(String userId) throws SQLException; + + public long selectFeedControl(FeedControlDAO activityFeedControl) throws SQLException; +} diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedGenerator.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedGenerator.java new file mode 100644 index 0000000000..d8a7d50896 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedGenerator.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2005-2008 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.feed.local; + +import java.util.Collection; +import java.util.Date; + +import org.alfresco.repo.activities.feed.AbstractFeedGenerator; +import org.alfresco.repo.activities.feed.FeedGridJob; +import org.alfresco.repo.activities.feed.FeedTaskProcessor; +import org.alfresco.repo.activities.feed.JobSettings; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The local (ie. not grid) feed generator component is responsible for generating feed entries + */ +public class LocalFeedGenerator extends AbstractFeedGenerator +{ + private static Log logger = LogFactory.getLog(LocalFeedGenerator.class); + + private FeedTaskProcessor feedTaskProcessor; + + public void setFeedTaskProcessor(FeedTaskProcessor feedTaskProcessor) + { + this.feedTaskProcessor = feedTaskProcessor; + } + + public int getEstimatedGridSize() + { + return 1; + } + + public void init() throws Exception + { + super.init(); + } + + protected boolean generate() throws Exception + { + Long maxSequence = getPostDaoService().getMaxActivitySeq(); + Integer maxNodeHash = getPostDaoService().getMaxNodeHash(); + + String gridName = "local"; + + if (maxSequence != null) + { + if (logger.isDebugEnabled()) + { + logger.debug(">>> Execute job cycle: " + gridName + " (maxSeq: " + maxSequence + ")"); + } + + long startTime = new Date().getTime(); + + // TODO ... or push this upto to job scheduler ... ? + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() + { + getWebScriptsCtx().setTicket(getAuthenticationService().getCurrentTicket()); + return null; + } + }, AuthenticationUtil.getSystemUserName()); // need web scripts to support System-level authentication ... see RepositoryContainer ! + + JobSettings js = new JobSettings(); + js.setMaxSeq(maxSequence); + js.setJobTaskNode(maxNodeHash); + js.setWebScriptsCtx(getWebScriptsCtx()); + js.setMaxItemsPerCycle(getMaxItemsPerCycle()); + + LocalFeedTaskSplitter splitter = new LocalFeedTaskSplitter(); + splitter.setFeedTaskProcessor(feedTaskProcessor); + + Collection jobs = splitter.split(getEstimatedGridSize(), js); + + for (FeedGridJob job : jobs) + { + job.execute(); + } + + long endTime = new Date().getTime(); + + if (logger.isDebugEnabled()) + { + logger.debug(">>> Finish job cycle: " + gridName + " (time taken (secs) = " + ((endTime - startTime) / 1000) + ")"); + } + return true; + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug(">>> No work to be done for this job cycle: " + gridName); + } + return false; + } + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedGridJob.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedGridJob.java new file mode 100644 index 0000000000..fec390d459 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedGridJob.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2008 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.feed.local; + +import java.io.Serializable; + +import org.alfresco.repo.activities.feed.FeedGridJob; +import org.alfresco.repo.activities.feed.FeedTaskProcessor; +import org.alfresco.repo.activities.feed.JobSettings; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Implementation to execute local (ie. not grid) feed job + */ +public class LocalFeedGridJob implements FeedGridJob +{ + private static final Log logger = LogFactory.getLog(LocalFeedGridJob.class); + + private JobSettings arg; + + private FeedTaskProcessor feedTaskProcessor; + + public void setFeedTaskProcessor(FeedTaskProcessor feedTaskProcessor) + { + this.feedTaskProcessor = feedTaskProcessor; + } + + public Serializable execute() throws Exception + { + JobSettings js = getArgument(); + + if (logger.isDebugEnabled()) { logger.debug(">>> Execute: nodehash '" + js.getJobTaskNode() + "' from seq '" + js.getMinSeq() + "' to seq '" + js.getMaxSeq() + "' on this node"); } + + try + { + feedTaskProcessor.process(js.getJobTaskNode(), js.getMinSeq(), js.getMaxSeq(), js.getWebScriptsCtx()); + } + catch (Exception e) + { + logger.error(e); + throw new Exception(e.getMessage(), e.getCause()); + } + + // This job does not return any result. + return null; + } + + public void setArgument(JobSettings arg) + { + this.arg = arg; + } + + public JobSettings getArgument() + { + return this.arg; + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java new file mode 100644 index 0000000000..cd4c0288ab --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskProcessor.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2005-2008 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.feed.local; + +import java.sql.SQLException; +import java.util.List; + +import org.alfresco.repo.activities.feed.ActivityFeedDAO; +import org.alfresco.repo.activities.feed.ActivityFeedDaoService; +import org.alfresco.repo.activities.feed.FeedTaskProcessor; +import org.alfresco.repo.activities.feed.control.FeedControlDAO; +import org.alfresco.repo.activities.feed.control.FeedControlDaoService; +import org.alfresco.repo.activities.post.ActivityPostDAO; +import org.alfresco.repo.activities.post.ActivityPostDaoService; + +import com.ibatis.sqlmap.client.SqlMapClient; + +/** + * The local (ie. not grid) feed task processor is responsible for processing the individual feed job + */ +public class LocalFeedTaskProcessor extends FeedTaskProcessor +{ + private ActivityPostDaoService postDaoService; + private ActivityFeedDaoService feedDaoService; + private FeedControlDaoService feedControlDaoService; + + // used to start/end/commit transaction + // note: currently assumes that all dao services are configured with this mapper / data source + private SqlMapClient sqlMapper; + + public void setPostDaoService(ActivityPostDaoService postDaoService) + { + this.postDaoService = postDaoService; + } + + public void setFeedDaoService(ActivityFeedDaoService feedDaoService) + { + this.feedDaoService = feedDaoService; + } + + public void setFeedControlDaoService(FeedControlDaoService feedControlDaoService) + { + this.feedControlDaoService = feedControlDaoService; + } + + public void setSqlMapClient(SqlMapClient sqlMapper) + { + this.sqlMapper = sqlMapper; + } + + public void startTransaction() throws SQLException + { + sqlMapper.startTransaction(); + } + + public void commitTransaction() throws SQLException + { + sqlMapper.commitTransaction(); + } + + public void endTransaction() throws SQLException + { + sqlMapper.endTransaction(); + } + + public List selectPosts(ActivityPostDAO selector) throws SQLException + { + return postDaoService.selectPosts(selector); + } + + public long insertFeedEntry(ActivityFeedDAO feed) throws SQLException + { + return feedDaoService.insertFeedEntry(feed); + } + + public int updatePostStatus(long id, ActivityPostDAO.STATUS status) throws SQLException + { + return postDaoService.updatePostStatus(id, status); + } + + public List selectUserFeedControls(String userId) throws SQLException + { + return feedControlDaoService.selectFeedControls(userId); + } +} diff --git a/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskSplitter.java b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskSplitter.java new file mode 100644 index 0000000000..1318c67c7f --- /dev/null +++ b/source/java/org/alfresco/repo/activities/feed/local/LocalFeedTaskSplitter.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2008 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.feed.local; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.alfresco.repo.activities.feed.FeedGridJob; +import org.alfresco.repo.activities.feed.FeedTaskProcessor; +import org.alfresco.repo.activities.feed.FeedTaskSplit; +import org.alfresco.repo.activities.feed.JobSettings; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The local feed task splitter is responsible for splitting the feed task into feed jobs + */ +public class LocalFeedTaskSplitter +{ + private static final Log logger = LogFactory.getLog(LocalFeedTaskSplitter.class); + + private FeedTaskProcessor feedTaskProcessor; + + public void setFeedTaskProcessor(FeedTaskProcessor feedTaskProcessor) + { + this.feedTaskProcessor = feedTaskProcessor; + } + + public Collection split(int gridSize, Object o) throws Exception + { + try + { + FeedTaskSplit feedSplitter = new FeedTaskSplit(); + Collection jobs = feedSplitter.split(gridSize, (JobSettings)o); + + List gridJobs = new ArrayList(jobs.size()); + for (JobSettings job : jobs) + { + LocalFeedGridJob gridJob = new LocalFeedGridJob(); + gridJob.setFeedTaskProcessor(feedTaskProcessor); + gridJob.setArgument(job); + gridJobs.add(gridJob); + } + return gridJobs; + //return (Collection)feedSplitter.split(gridSize, (JobSettings)o, new LocalFeedGridJob()); + } + catch (Exception e) + { + logger.equals(e); + throw new Exception(e.getMessage()); + } + } +} diff --git a/source/java/org/alfresco/repo/activities/ibatis/ActivityDaoService.java b/source/java/org/alfresco/repo/activities/ibatis/ActivityDaoService.java new file mode 100644 index 0000000000..9d383583aa --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ibatis/ActivityDaoService.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2008 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.ibatis; + +import java.sql.SQLException; + +/** + * Common interface for activity DAO service + */ +public interface ActivityDaoService +{ + public void startTransaction() throws SQLException; + + public void commitTransaction() throws SQLException; + + public void endTransaction() throws SQLException; +} diff --git a/source/java/org/alfresco/repo/activities/ibatis/IBatisActivityFeedDaoServiceImpl.java b/source/java/org/alfresco/repo/activities/ibatis/IBatisActivityFeedDaoServiceImpl.java new file mode 100644 index 0000000000..8147fd7b69 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ibatis/IBatisActivityFeedDaoServiceImpl.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2008 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.ibatis; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.alfresco.repo.activities.feed.ActivityFeedDAO; +import org.alfresco.repo.activities.feed.ActivityFeedDaoService; + +public class IBatisActivityFeedDaoServiceImpl extends IBatisSqlMapper implements ActivityFeedDaoService +{ + public long insertFeedEntry(ActivityFeedDAO activityFeed) throws SQLException + { + Long id = (Long)getSqlMapClient().insert("insert.activity.feed", activityFeed); + return (id != null ? id : -1); + } + + public int deleteFeedEntries(Date keepDate) throws SQLException + { + return getSqlMapClient().delete("delete.activity.feed.entries.older.than.date", keepDate); + } + + @SuppressWarnings("unchecked") + public List selectUserFeedEntries(String feedUserId, String format) throws SQLException + { + ActivityFeedDAO params = new ActivityFeedDAO(); + params.setFeedUserId(feedUserId); + params.setActivitySummaryFormat(format); + + // where feed user is me and post user is not me + return (List)getSqlMapClient().queryForList("select.activity.feed.for.feeduser", params); + } + + @SuppressWarnings("unchecked") + public List selectUserFeedEntries(String feedUserId, String format, String siteId) throws SQLException + { + ActivityFeedDAO params = new ActivityFeedDAO(); + params.setFeedUserId(feedUserId); + params.setPostUserId(feedUserId); + params.setActivitySummaryFormat(format); + params.setSiteNetwork(siteId); + + // where feed user is me and post user is not me + return (List)getSqlMapClient().queryForList("select.activity.feed.for.feeduser.and.site", params); + } + + @SuppressWarnings("unchecked") + public List selectSiteFeedEntries(String siteId, String format) throws SQLException + { + ActivityFeedDAO params = new ActivityFeedDAO(); + params.setSiteNetwork(siteId); + params.setActivitySummaryFormat(format); + + // where feed user is me and post user is not me + return (List)getSqlMapClient().queryForList("select.activity.feed.for.site", params); + } +} diff --git a/source/java/org/alfresco/repo/activities/ibatis/IBatisActivityPostDaoServiceImpl.java b/source/java/org/alfresco/repo/activities/ibatis/IBatisActivityPostDaoServiceImpl.java new file mode 100644 index 0000000000..dae02b58d1 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ibatis/IBatisActivityPostDaoServiceImpl.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2005-2008 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.ibatis; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.alfresco.repo.activities.post.ActivityPostDAO; +import org.alfresco.repo.activities.post.ActivityPostDaoService; + +public class IBatisActivityPostDaoServiceImpl extends IBatisSqlMapper implements ActivityPostDaoService +{ + @SuppressWarnings("unchecked") + public List selectPosts(ActivityPostDAO activityPost) throws SQLException + { + if ((activityPost.getJobTaskNode() != -1) && + (activityPost.getMinId() != -1) && + (activityPost.getMaxId() != -1) && + (activityPost.getStatus() != null)) + { + return (List)getSqlMapClient().queryForList("select.activity.posts", activityPost); + } + else if (activityPost.getStatus() != null) + { + return (List)getSqlMapClient().queryForList("select.activity.posts.by.status.only", activityPost); + } + else + { + return new ArrayList(0); + } + } + + public Long getMaxActivitySeq() throws SQLException + { + return (Long)getSqlMapClient().queryForObject("select.activity.post.max.seq"); + } + + public Long getMinActivitySeq() throws SQLException + { + return (Long)getSqlMapClient().queryForObject("select.activity.post.min.seq"); + } + + public Integer getMaxNodeHash() throws SQLException + { + return (Integer)getSqlMapClient().queryForObject("select.activity.post.max.jobtasknode"); + } + + public int updatePost(long id, String siteNetwork, String activityData, ActivityPostDAO.STATUS status) throws SQLException + { + ActivityPostDAO post = new ActivityPostDAO(); + post.setId(id); + post.setSiteNetwork(siteNetwork); + post.setActivityData(activityData); + post.setStatus(status.toString()); + post.setLastModified(new Date()); + + return getSqlMapClient().update("update.activity.post.data", post); + } + + public int updatePostStatus(long id, ActivityPostDAO.STATUS status) throws SQLException + { + ActivityPostDAO post = new ActivityPostDAO(); + post.setId(id); + post.setStatus(status.toString()); + post.setLastModified(new Date()); + + return getSqlMapClient().update("update.activity.post.status", post); + } + + public int deletePosts(Date keepDate, ActivityPostDAO.STATUS status) throws SQLException + { + ActivityPostDAO params = new ActivityPostDAO(); + params.setPostDate(keepDate); + params.setStatus(status.toString()); + + return getSqlMapClient().delete("delete.activity.posts.older.than.date", params); + } + + public long insertPost(ActivityPostDAO activityPost) throws SQLException + { + Long id = (Long)getSqlMapClient().insert("insert.activity.post", activityPost); + return (id != null ? id : -1); + } +} diff --git a/source/java/org/alfresco/repo/activities/ibatis/IBatisFeedControlDaoServiceImpl.java b/source/java/org/alfresco/repo/activities/ibatis/IBatisFeedControlDaoServiceImpl.java new file mode 100644 index 0000000000..c337751d4c --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ibatis/IBatisFeedControlDaoServiceImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2008 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.ibatis; + +import java.sql.SQLException; +import java.util.List; + +import org.alfresco.repo.activities.feed.control.FeedControlDAO; +import org.alfresco.repo.activities.feed.control.FeedControlDaoService; + +public class IBatisFeedControlDaoServiceImpl extends IBatisSqlMapper implements FeedControlDaoService +{ + public long insertFeedControl(FeedControlDAO activityFeedControl) throws SQLException + { + Long id = (Long)getSqlMapClient().insert("insert.activity.feedcontrol", activityFeedControl); + return (id != null ? id : -1); + } + + public int deleteFeedControl(FeedControlDAO activityFeedControl) throws SQLException + { + return getSqlMapClient().delete("delete.activity.feedcontrol", activityFeedControl); + } + + @SuppressWarnings("unchecked") + public List selectFeedControls(String feedUserId) throws SQLException + { + FeedControlDAO params = new FeedControlDAO(feedUserId); + + return (List)getSqlMapClient().queryForList("select.activity.feedcontrols.for.user", params); + } + + public long selectFeedControl(FeedControlDAO activityFeedControl) throws SQLException + { + Long id = (Long)getSqlMapClient().queryForObject("select.activity.feedcontrol", activityFeedControl); + return (id != null ? id : -1); + } +} diff --git a/source/java/org/alfresco/repo/activities/ibatis/IBatisSqlMapper.java b/source/java/org/alfresco/repo/activities/ibatis/IBatisSqlMapper.java new file mode 100644 index 0000000000..42a981ff81 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/ibatis/IBatisSqlMapper.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2008 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.ibatis; + +import java.sql.SQLException; + +import com.ibatis.sqlmap.client.SqlMapClient; + +public class IBatisSqlMapper implements ActivityDaoService +{ + private SqlMapClient sqlMapper; + + public void setSqlMapClient(SqlMapClient sqlMapper) + { + this.sqlMapper = sqlMapper; + } + + public SqlMapClient getSqlMapClient() + { + return this.sqlMapper; + } + + public void startTransaction() throws SQLException + { + sqlMapper.startTransaction(); + } + + public void commitTransaction() throws SQLException + { + sqlMapper.commitTransaction(); + } + + public void endTransaction() throws SQLException + { + sqlMapper.endTransaction(); + } +} diff --git a/source/java/org/alfresco/repo/activities/post/ActivityPostDAO.java b/source/java/org/alfresco/repo/activities/post/ActivityPostDAO.java new file mode 100644 index 0000000000..14c805eedf --- /dev/null +++ b/source/java/org/alfresco/repo/activities/post/ActivityPostDAO.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2005-2008 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.post; + +import java.util.Date; + +/** + * Activity Post DAO + */ +public class ActivityPostDAO +{ + public enum STATUS { POSTED, PENDING, PROCESSED, ERROR }; + + private long id; // internal DB-generated sequence id + private String activityData; + private String activityType; + private String userId; + private int jobTaskNode = -1; + private String siteNetwork; + private String appTool; + private String status; + private Date postDate; + private Date lastModified; // for debug + + // for selector + private long minId = -1; + private long maxId = -1; + + public long getId() + { + return id; + } + public void setId(long id) + { + this.id = id; + } + + public String getUserId() + { + return userId; + } + + public void setUserId(String userId) + { + this.userId = userId; + } + + public int getJobTaskNode() + { + return jobTaskNode; + } + + public void setJobTaskNode(int jobTaskNode) + { + this.jobTaskNode = jobTaskNode; + } + + public long getMinId() + { + return minId; + } + + public void setMinId(long minId) + { + this.minId = minId; + } + + public long getMaxId() + { + return maxId; + } + + public void setMaxId(long maxId) + { + this.maxId = maxId; + } + + public String getSiteNetwork() + { + return siteNetwork; + } + + public void setSiteNetwork(String siteNetwork) + { + this.siteNetwork = siteNetwork; + } + + public String getActivityData() + { + return activityData; + } + + public void setActivityData(String activityData) + { + this.activityData = activityData; + } + + public String getActivityType() + { + return activityType; + } + + public void setActivityType(String activityType) + { + this.activityType = activityType; + } + + public Date getPostDate() + { + return postDate; + } + + public void setPostDate(Date postDate) + { + this.postDate = postDate; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public Date getLastModified() + { + return lastModified; + } + + public void setLastModified(Date lastModified) + { + this.lastModified = lastModified; + } + + public String getAppTool() + { + return appTool; + } + + public void setAppTool(String appTool) + { + this.appTool = appTool; + } + + // for debug only + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append("ActivityPost\n["); + sb.append("id=").append(id).append(","); + sb.append("status=").append(status).append(","); + sb.append("postDate=").append(postDate).append(","); + sb.append("userId=").append(userId).append(","); + sb.append("siteNetwork=").append(siteNetwork).append(","); + sb.append("appTool=").append(appTool).append(","); + sb.append("type=").append(activityType).append(","); + sb.append("jobTaskNode=").append(jobTaskNode).append(","); + sb.append("data=\n").append(activityData).append("\n]"); + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/repo/activities/post/ActivityPostDaoService.java b/source/java/org/alfresco/repo/activities/post/ActivityPostDaoService.java new file mode 100644 index 0000000000..5c2608f8e3 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/post/ActivityPostDaoService.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2008 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.post; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.alfresco.repo.activities.ibatis.ActivityDaoService; + +/** + * Interface for activity post DAO service + */ +public interface ActivityPostDaoService extends ActivityDaoService +{ + public List selectPosts(ActivityPostDAO activityPost) throws SQLException; + + public Long getMaxActivitySeq() throws SQLException; + + public Long getMinActivitySeq() throws SQLException; + + public Integer getMaxNodeHash() throws SQLException; + + public int deletePosts(Date keepDate, ActivityPostDAO.STATUS status) throws SQLException; + + public long insertPost(ActivityPostDAO activityPost) throws SQLException; + + public int updatePost(long id, String network, String activityData, ActivityPostDAO.STATUS status) throws SQLException; + + public int updatePostStatus(long id, ActivityPostDAO.STATUS status) throws SQLException; +} diff --git a/source/java/org/alfresco/repo/activities/post/cleanup/PostCleaner.java b/source/java/org/alfresco/repo/activities/post/cleanup/PostCleaner.java new file mode 100644 index 0000000000..82362ed8a1 --- /dev/null +++ b/source/java/org/alfresco/repo/activities/post/cleanup/PostCleaner.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2008 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.post.cleanup; + +import java.sql.SQLException; +import java.util.Date; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.activities.post.ActivityPostDAO; +import org.alfresco.repo.activities.post.ActivityPostDaoService; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.VmShutdownListener; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.quartz.JobExecutionException; + +/** + * Thr post cleaner component is responsible for purging 'obsolete' activity posts + */ +public class PostCleaner +{ + private static Log logger = LogFactory.getLog(PostCleaner.class); + + private static VmShutdownListener vmShutdownListener = new VmShutdownListener(PostCleaner.class.getName()); + + private int maxAgeMins = 0; + + private ActivityPostDaoService postDaoService; + + public void setPostDaoService(ActivityPostDaoService postDaoService) + { + this.postDaoService = postDaoService; + } + + public void setMaxAgeMins(int mins) + { + this.maxAgeMins = mins; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + private void checkProperties() + { + PropertyCheck.mandatory(this, "postDaoService", postDaoService); + + // check the max age + if (maxAgeMins <= 0) + { + throw new AlfrescoRuntimeException("Property 'maxAgeMins' must be greater than 0"); + } + } + + public void execute() throws JobExecutionException + { + checkProperties(); + try + { + long nowTimeOffset = new Date().getTime(); + long keepTimeOffset = nowTimeOffset - (maxAgeMins*60*1000); // millsecs = mins * 60 secs * 1000 msecs + Date keepDate = new Date(keepTimeOffset); + + // clean old entries - PROCESSED - does not clean POSTED or PENDING, which will need to be done manually, if stuck + int deletedCount = postDaoService.deletePosts(keepDate, ActivityPostDAO.STATUS.PROCESSED); + + if (logger.isDebugEnabled()) + { + logger.debug("Cleaned " + deletedCount + " entries (upto " + keepDate + ", max age " + maxAgeMins + " mins)"); + } + } + catch (SQLException e) + { + logger.error("Exception during cleanup of posts", e); + throw new JobExecutionException(e); + } + catch (Throwable e) + { + // If the VM is shutting down, then ignore + if (vmShutdownListener.isVmShuttingDown()) + { + // Ignore + } + else + { + logger.error("Exception during cleanup of posts", e); + } + } + } +} diff --git a/source/java/org/alfresco/repo/activities/post/cleanup/PostCleanupJob.java b/source/java/org/alfresco/repo/activities/post/cleanup/PostCleanupJob.java new file mode 100644 index 0000000000..5b65ff948f --- /dev/null +++ b/source/java/org/alfresco/repo/activities/post/cleanup/PostCleanupJob.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2008 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.post.cleanup; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Executes scheduled post cleaner quartz-job - refer to scheduled-jobs-context.xml + */ +public class PostCleanupJob implements Job +{ + public PostCleanupJob() + { + } + + /** + * Calls the post cleaner to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the post cleaner to use + Object postCleanerObj = jobData.get("postCleaner"); + if (postCleanerObj == null || !(postCleanerObj instanceof PostCleaner)) + { + throw new AlfrescoRuntimeException( + "FeedCleanupJob data must contain valid 'postCleaner' reference"); + } + PostCleaner postCleaner = (PostCleaner)postCleanerObj; + postCleaner.execute(); + } +} diff --git a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java new file mode 100644 index 0000000000..4e4a1033ae --- /dev/null +++ b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2005-2008 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.post.lookup; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.activities.post.ActivityPostDAO; +import org.alfresco.repo.activities.post.ActivityPostDaoService; +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.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.JSONtoFmModel; +import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.VmShutdownListener; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONStringer; +import org.quartz.JobExecutionException; + +/** + * The post lookup component is responsible for updating posts that require a secondary lookup (to get additional activity data) + */ +public class PostLookup +{ + private static Log logger = LogFactory.getLog(PostLookup.class); + + private static VmShutdownListener vmShutdownListener = new VmShutdownListener(PostLookup.class.getName()); + + private ActivityPostDaoService postDaoService; + private NodeService nodeService; + private PermissionService permissionService; + private TransactionService transactionService; + private PersonService personService; + + public void setPostDaoService(ActivityPostDaoService postDaoService) + { + this.postDaoService = postDaoService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + private void checkProperties() + { + PropertyCheck.mandatory(this, "postDaoService", postDaoService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "permissionService", permissionService); + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "personService", personService); + } + + public void execute() throws JobExecutionException + { + checkProperties(); + try + { + ActivityPostDAO params = new ActivityPostDAO(); + params.setStatus(ActivityPostDAO.STATUS.PENDING.toString()); + + List activityPosts = postDaoService.selectPosts(params); + + if (activityPosts.size() > 0) + { + logger.info("Update: " + activityPosts.size() + " activity posts"); + } + + for (ActivityPostDAO activityPost : activityPosts) + { + Map model = JSONtoFmModel.convertJSONObjectToMap(activityPost.getActivityData()); + + String postUserId = activityPost.getUserId(); + + String name = (String)model.get("name"); // can be null + + String nodeRefStr = (String)model.get("nodeRef"); // required + NodeRef nodeRef = new NodeRef(nodeRefStr); + + String parentNodeRefStr = (String)model.get("parentNodeRef"); // optional + NodeRef parentNodeRef = null; + if (parentNodeRefStr != null) + { + parentNodeRef = new NodeRef(parentNodeRefStr); + } + + String typeQName = (String)model.get("typeQName"); + + try + { + postDaoService.startTransaction(); + + Pair siteNetworkActivityData = lookup(activityPost.getSiteNetwork(), nodeRef, name, typeQName, parentNodeRef, postUserId); + + activityPost.setSiteNetwork(siteNetworkActivityData.getFirst()); + activityPost.setActivityData(siteNetworkActivityData.getSecond()); + activityPost.setLastModified(new Date()); + + postDaoService.updatePost(activityPost.getId(), activityPost.getSiteNetwork(), activityPost.getActivityData(), ActivityPostDAO.STATUS.POSTED); + if (logger.isDebugEnabled()) + { + activityPost.setStatus(ActivityPostDAO.STATUS.POSTED.toString()); // for debug output + logger.debug("Updated: " + activityPost); + } + + postDaoService.commitTransaction(); + } + catch (JSONException e) + { + // log error, but consume exception (skip this post) + logger.error(e); + } + catch (SQLException e) + { + logger.error("Exception during update of post", e); + throw new JobExecutionException(e); + } + finally + { + postDaoService.endTransaction(); + } + } + } + catch (SQLException e) + { + logger.error("Exception during select of posts", e); + throw new JobExecutionException(e); + } + catch (Throwable e) + { + // If the VM is shutting down, then ignore + if (vmShutdownListener.isVmShuttingDown()) + { + // Ignore + } + else + { + logger.error("Exception during update of posts", e); + } + } + } + + private Pair lookup(final String networkIn, final NodeRef nodeRef, final String nameIn, final String typeQNameIn, final NodeRef parentNodeRef, final String postUserId) throws JSONException + { + return AuthenticationUtil.runAs(new RunAsWork>() + { + public Pair doWork() throws Exception + { + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + + // wrap to make the request in a transaction + RetryingTransactionCallback> lookup = new RetryingTransactionCallback>() + { + public Pair execute() throws Throwable + { + String jsonString = null; + String displayPath = ""; + String name = nameIn; + String network = networkIn; + String typeQName = typeQNameIn; + Path path = null; + String firstName = ""; + String lastName = ""; + + if (personService.personExists(postUserId)) + { + NodeRef personRef = personService.getPerson(postUserId); + + firstName = (String)nodeService.getProperty(personRef, ContentModel.PROP_FIRSTNAME); + lastName = (String)nodeService.getProperty(personRef, ContentModel.PROP_LASTNAME); + } + + if (((name == null) || (name.length() == 0)) && (nodeRef != null) && (nodeService.exists(nodeRef))) + { + // node exists, lookup node name + if ((name == null) || (name.length() == 0)) + { + name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + } + + path = nodeService.getPath(nodeRef); + + // TODO: missing the prefix ? + typeQName = nodeService.getType(nodeRef).toPrefixString(); + } + + if (((path == null) || (path.size() == 0)) && (parentNodeRef != null) && (nodeService.exists(parentNodeRef))) + { + // parent node exists, lookup parent node path + path = nodeService.getPath(parentNodeRef); + } + + + if (path != null) + { + // lookup display path + displayPath = path.toDisplayPath(nodeService, permissionService); + + // note: for now, also tack on the node name + displayPath += "/" + name; + } + + if (name == null) + { + name = ""; + } + + if (typeQName == null) + { + typeQName = ""; + } + + // activity data + jsonString = new JSONStringer() + .object() + .key("name") + .value(name) + .key("nodeRef") + .value(nodeRef) + .key("typeQName") + .value(typeQName) + .key("displayPath") + .value(displayPath) + .key("firstName") + .value(firstName) + .key("lastName") + .value(lastName) + .endObject().toString(); + + return new Pair(network, jsonString); + } + }; + + // execute in txn + return txnHelper.doInTransaction(lookup, true); + } + }, AuthenticationUtil.getSystemUserName()); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/activities/post/lookup/PostLookupJob.java b/source/java/org/alfresco/repo/activities/post/lookup/PostLookupJob.java new file mode 100644 index 0000000000..3f4b85022f --- /dev/null +++ b/source/java/org/alfresco/repo/activities/post/lookup/PostLookupJob.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2008 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.post.lookup; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Executes scheduled post lookup quartz-job - refer to scheduled-jobs-context.xml + */ +public class PostLookupJob implements Job +{ + public PostLookupJob() + { + } + + /** + * Calls the post lookup to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the post cleaner to use + Object postLookupObj = jobData.get("postLookup"); + if (postLookupObj == null || !(postLookupObj instanceof PostLookup)) + { + throw new AlfrescoRuntimeException( + "FeedCleanupJob data must contain valid 'postLookup' reference"); + } + PostLookup postLookup = (PostLookup)postLookupObj; + postLookup.execute(); + } +} diff --git a/source/java/org/alfresco/repo/activities/script/Activity.java b/source/java/org/alfresco/repo/activities/script/Activity.java new file mode 100644 index 0000000000..f58339be1b --- /dev/null +++ b/source/java/org/alfresco/repo/activities/script/Activity.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2007 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.script; + +import org.alfresco.repo.jscript.BaseScopableProcessorExtension; +import org.alfresco.service.cmr.activities.ActivityService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * Scripted Activity Service for posting activities. + */ + +public final class Activity extends BaseScopableProcessorExtension +{ + private ActivityService activityService; + + /** + * Set the activity service + * + * @param activityService the activity service + */ + public void setActivityService(ActivityService activityService) + { + this.activityService = activityService; + } + + /** + * Post a custom activity type + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param jsonActivityData - required + */ + public void postActivity(String activityType, String siteId, String appTool, String jsonActivityData) + { + activityService.postActivity(activityType, siteId, appTool, jsonActivityData); + } + + /** + * Post a pre-defined activity type - activity data will be looked-up asynchronously, including: + * + * name + * displayPath + * typeQName + * firstName (of posting user) + * lastName (of posting user) + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param nodeRef - required - do not use for deleted (or about to be deleted) nodeRef + */ + public void postActivity(String activityType, String siteId, String appTool, NodeRef nodeRef) + { + activityService.postActivity(activityType, siteId, appTool, nodeRef); + } + + /** + * Post a pre-defined activity type - eg. for checked-out nodeRef or renamed nodeRef + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param nodeRef - required - do not use deleted (or about to be deleted) nodeRef + * @param beforeName - optional - name of node (eg. prior to name change) + */ + public void postActivity(String activityType, String siteId, String appTool, NodeRef nodeRef, String beforeName) + { + activityService.postActivity(activityType, siteId, appTool, nodeRef, beforeName); + } + + /** + * Post a pre-defined activity type - eg. for deleted nodeRef + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param nodeRef - required - can be a deleted (or about to be deleted) nodeRef + * @param name - optional - name of name + * @param typeQName - optional - type of node + * @param parentNodeRef - required - used to lookup path/displayPath + */ + public void postActivity(String activityType, String siteId, String appTool, NodeRef nodeRef, String name, QName typeQName, NodeRef parentNodeRef) + { + activityService.postActivity(activityType, siteId, appTool, nodeRef, name, typeQName, parentNodeRef); + } +} diff --git a/source/java/org/alfresco/repo/activities/script/test_activityService.js b/source/java/org/alfresco/repo/activities/script/test_activityService.js new file mode 100644 index 0000000000..a1b31182cc --- /dev/null +++ b/source/java/org/alfresco/repo/activities/script/test_activityService.js @@ -0,0 +1,19 @@ +// called by ActivityServiceImplTest.java (test_JSAPI) + +var failure = "did not complete script"; + +// invalid +// activities.postActivity("my activity type", null, null, null); +// activities.postActivity(null, "my site", "my app tool", '{ 000 }'); + +// valid +activities.postActivity("test activity type 4", null, null, '{ "item1" : 123 }'); +activities.postActivity("test activity type 5", "my site", null, '{ "item2" : 456 }'); +activities.postActivity("test activity type 6", "my site", "my app tool", '{ "item3" : 789 }'); +activities.postActivity("test activity type 7", "my site", "my app tool", '{ invalidJSON }'); + + +failure = ""; + +// Return the failure message +failure; diff --git a/source/java/org/alfresco/service/cmr/activities/ActivityService.java b/source/java/org/alfresco/service/cmr/activities/ActivityService.java new file mode 100644 index 0000000000..b3379f78d1 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/activities/ActivityService.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2005-2008 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.service.cmr.activities; + +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +public interface ActivityService +{ + + /* + * Post Activity + */ + + /** + * Post a custom activity type + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param jsonActivityData - required + */ + public void postActivity(String activityType, String siteId, String appTool, String jsonActivityData); + + /** + * Post a pre-defined activity type - certain activity data will be looked-up asynchronously, including: + * + * name (of nodeRef) + * displayPath + * typeQName + * firstName (of posting user) + * lastName (of posting user) + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param nodeRef - required - do not use for deleted (or about to be deleted) nodeRef + */ + public void postActivity(String activityType, String siteId, String appTool, NodeRef nodeRef); + + /** + * Post a pre-defined activity type - eg. for checked-out nodeRef or renamed nodeRef + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param nodeRef - required - do not use deleted (or about to be deleted) nodeRef + * @param beforeName - optional - name of node (eg. prior to name change) + */ + public void postActivity(String activityType, String siteId, String appTool, NodeRef nodeRef, String beforeName); + + /** + * Post a pre-defined activity type - eg. for deleted nodeRef + * + * @param activityType - required + * @param siteId - optional, if null will be stored as empty string + * @param appTool - optional, if null will be stored as empty string + * @param nodeRef - required - can be a deleted (or about to be deleted) nodeRef + * @param name - optional - name of name + * @param typeQName - optional - type of node + * @param parentNodeRef - required - used to lookup path/displayPath + */ + public void postActivity(String activityType, String siteId, String appTool, NodeRef nodeRef, String name, QName typeQName, NodeRef parentNodeRef); + + + /* + * Retrieve Feed Entries + */ + + /** + * Retrieve user feed + * + * @param userId - required + * @param format - required + * @param siteId - optional, if set then will filter by given siteId else return all sites + */ + public List> getUserFeedEntries(String userId, String format, String siteId); + + /** + * Retrieve site feed + * + * @param activityType - required + * @param format - required + */ + public List> getSiteFeedEntries(String siteId, String format); + + + /* + * Manage User Feed Controls + */ + + /** + * For current user, set feed control (opt-out) for a site or an appTool or a site/appTool + * + * @param feedControl - required + */ + public void setFeedControl(FeedControl feedControl); + + /** + * For given user, get feed controls + * + * @param userId - required (must match + * @return list of user feed controls + */ + public List getFeedControls(String userId); + + /** + * For current user, get feed controls + * + * @return list of user feed controls + */ + public List getFeedControls(); + + /** + * For current user, unset feed control + * + * @param feedControl - required + */ + public void unsetFeedControl(FeedControl feedControl); + + /** + * For current user, does the feed control exist ? + * + * @param feedControl - required + * @return true, if user feed control exists + */ + public boolean existsFeedControl(FeedControl feedControl); +} diff --git a/source/java/org/alfresco/service/cmr/activities/FeedControl.java b/source/java/org/alfresco/service/cmr/activities/FeedControl.java new file mode 100644 index 0000000000..2a3fc6b21f --- /dev/null +++ b/source/java/org/alfresco/service/cmr/activities/FeedControl.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2008 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.service.cmr.activities; + +public class FeedControl +{ + private String siteId; + private String appToolId; + + public FeedControl(String siteId, String appToolId) + { + if (siteId == null) siteId = ""; + this.siteId = siteId; + + if (appToolId == null) appToolId = ""; + this.appToolId = appToolId; + } + + public String getSiteId() + { + return this.siteId; + } + + public String getAppToolId() + { + return this.appToolId; + } +} diff --git a/source/java/org/alfresco/util/BaseSpringTest.java b/source/java/org/alfresco/util/BaseSpringTest.java index c11e7b7fbe..8943daa857 100644 --- a/source/java/org/alfresco/util/BaseSpringTest.java +++ b/source/java/org/alfresco/util/BaseSpringTest.java @@ -95,6 +95,10 @@ public abstract class BaseSpringTest extends AbstractTransactionalDataSourceSpri { // The derived class is using the default context defaultContext = true; + + this.setAutowireMode(AUTOWIRE_BY_NAME); + //this.setDependencyCheck(false); + if (logger.isDebugEnabled()) { logger.debug("Getting config locations");