/*
* Copyright (c) 2010 Lockheed Martin Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eurekastreams.server.action.execution.profile;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.eurekastreams.commons.actions.TaskHandlerExecutionStrategy;
import org.eurekastreams.commons.actions.context.ActionContext;
import org.eurekastreams.commons.actions.context.TaskHandlerActionContext;
import org.eurekastreams.commons.logging.LogFactory;
import org.eurekastreams.commons.server.UserActionRequest;
import org.eurekastreams.server.action.request.DeleteFromSearchIndexRequest;
import org.eurekastreams.server.action.request.stream.DeleteIdsFromListsRequest;
import org.eurekastreams.server.domain.DomainGroup;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.stream.Activity;
import org.eurekastreams.server.persistence.mappers.cache.CacheKeys;
import org.eurekastreams.server.persistence.mappers.db.DeleteAllFeedSubscriberByEntityTypeAndId;
import org.eurekastreams.server.persistence.mappers.db.DeleteGroup;
import org.eurekastreams.server.persistence.mappers.db.DeleteGroupActivity;
import org.eurekastreams.server.persistence.mappers.db.RemoveGroupFollowers;
import org.eurekastreams.server.persistence.mappers.requests.BulkActivityDeleteResponse;
import org.eurekastreams.server.persistence.mappers.requests.DeleteAllFeedSubscriberByEntityTypeAndIdRequest;
import org.eurekastreams.server.persistence.mappers.requests.DeleteGroupResponse;
/**
* Execution strategy for deleting a group and associated objects from database, and creating the UserActionRequests to
* clean up cache and search index.
*
*/
public class DeleteGroupFromDBExecution implements TaskHandlerExecutionStrategy<ActionContext>
{
/**
* Logger.
*/
private Log log = LogFactory.make();
/**
* {@link DeleteGroupActivity}.
*/
private DeleteGroupActivity deleteGroupActivityDAO;
/**
* {@link RemoveGroupFollowers}.
*/
private RemoveGroupFollowers removeGroupFollowersDAO;
/**
* {@link DeleteGroup}.
*/
private DeleteGroup deleteGroupDAO;
/**
* {@link DeleteAllFeedSubscriberByEntityTypeAndId}.
*/
private DeleteAllFeedSubscriberByEntityTypeAndId deleteGroupFeedSubscriptions;
/**
* Max cache list size.
*/
private final int maxCacheListSize;
/**
* Constructor.
*
* @param inDeleteGroupActivityDAO
* {@link DeleteGroupActivity}.
* @param inRemoveGroupFollowersDAO
* {@link RemoveGroupFollowers}.
* @param inDeleteGroupDAO
* {@link DeleteGroup}.
* @param inDeleteGroupFeedSubscriptions
* {@link DeleteAllFeedSubscriberByEntityTypeAndId}.
* @param inMaxCacheListSize
* Max size for cache list.
*/
public DeleteGroupFromDBExecution(final DeleteGroupActivity inDeleteGroupActivityDAO,
final RemoveGroupFollowers inRemoveGroupFollowersDAO, final DeleteGroup inDeleteGroupDAO,
final DeleteAllFeedSubscriberByEntityTypeAndId inDeleteGroupFeedSubscriptions, final int inMaxCacheListSize)
{
deleteGroupActivityDAO = inDeleteGroupActivityDAO;
removeGroupFollowersDAO = inRemoveGroupFollowersDAO;
deleteGroupDAO = inDeleteGroupDAO;
deleteGroupFeedSubscriptions = inDeleteGroupFeedSubscriptions;
maxCacheListSize = inMaxCacheListSize;
}
/**
* Deleting a group and associated objects from database.
*
* @param inActionContext
* {@link TaskHandlerActionContext}.
* @return True if successful.
*/
@Override
public Serializable execute(final TaskHandlerActionContext<ActionContext> inActionContext)
{
// ================= Database deletes =======================
Long groupId = (Long) inActionContext.getActionContext().getParams();
Long startDB = null;
if (log.isInfoEnabled())
{
startDB = System.currentTimeMillis();
}
Long startTime = null;
Long endTime = null;
if (log.isDebugEnabled())
{
log.debug("Deleting activities for groupid:" + groupId);
startTime = System.currentTimeMillis();
}
// Delete comments/activities/starred activity associations
BulkActivityDeleteResponse deleteActivityResponse = deleteGroupActivityDAO.execute(groupId);
if (log.isDebugEnabled())
{
endTime = System.currentTimeMillis();
log.debug("Time to delete activities/comments: " + (endTime - startTime));
log.debug("Removing followers for groupid:" + groupId);
startTime = endTime;
}
// Remove user/group follower associations (reset order indexes) and adjust groupsCount for followers.
List<Long> followerIds = removeGroupFollowersDAO.execute(groupId);
if (log.isDebugEnabled())
{
endTime = System.currentTimeMillis();
log.debug("Time to remove followers from group: " + (endTime - startTime));
log.debug("Removing group subscriptions for groupid:" + groupId);
startTime = endTime;
}
// Remove feed subscriptions for group.
deleteGroupFeedSubscriptions.execute(new DeleteAllFeedSubscriberByEntityTypeAndIdRequest(groupId,
EntityType.GROUP));
if (log.isDebugEnabled())
{
endTime = System.currentTimeMillis();
log.debug("Time to remove group subscriptions: " + (endTime - startTime));
log.debug("Removing group and associated objects for groupid:" + groupId);
startTime = endTime;
}
// Remove the group itself and adjust parent org stats recursively
DeleteGroupResponse deleteGroupResponse = deleteGroupDAO.execute(groupId);
if (log.isDebugEnabled())
{
endTime = System.currentTimeMillis();
log.debug("Time to remove group and associated objects: " + (endTime - startTime));
}
Long endDB = null;
if (log.isInfoEnabled())
{
endDB = System.currentTimeMillis();
}
// ================= Cache update task generation =======================
Long startAsync = null;
if (log.isInfoEnabled())
{
startAsync = System.currentTimeMillis();
}
int startSize = 0;
int endSize = 0;
if (log.isDebugEnabled())
{
startSize = inActionContext.getUserActionRequests().size();
}
// purge fixed set of cache keys.
generateSingleDeleteKeyFromCacheTask(getKeysToPurgeFromCache(deleteGroupResponse), inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for purge fixed set of cache keys: " + (endSize - startSize));
}
startSize = endSize;
// create tasks (1/key) for removing group id from CacheKeys.GROUPS_FOLLOWED_BY_PERSON lists
generateRemoveIdsFromListTasks(createKeys(CacheKeys.GROUPS_FOLLOWED_BY_PERSON, followerIds), Collections
.singletonList(deleteGroupResponse.getGroupId()), inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for remove group id from GROUPS_FOLLOWED_BY_PERSON lists: " + (endSize - startSize));
}
startSize = endSize;
// create task for removing activities in group's stream from users' starred activity lists.
generateRemoveIdsFromStarredActivityListTasks(deleteActivityResponse, inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for remove groups activity ids from starred activity lists: " + (endSize - startSize));
}
// get list of activity ids to remove from cache lists, no need to go beyond maxCacheListSize
List<Long> cachedActivityIds = deleteActivityResponse.getActivityIds().size() > maxCacheListSize // \n
? deleteActivityResponse.getActivityIds().subList(0, maxCacheListSize - 1)
: deleteActivityResponse.getActivityIds();
startSize = endSize;
// create tasks for removing activities from group's stream from all parent orgs.
generateRemoveIdsFromListTasks(createKeys(CacheKeys.ACTIVITY_IDS_FOR_ORG_BY_SHORTNAME_RECURSIVE,
deleteGroupResponse.getParentOrganizationShortNames()), cachedActivityIds, inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for remove activity ids from parent orgs of the deleted group: " + (endSize - startSize));
}
// create task for removing activities in group's stream from everyone stream
generateRemoveIdsFromListTasks(Collections.singletonList(CacheKeys.EVERYONE_ACTIVITY_IDS), cachedActivityIds,
inActionContext);
startSize = endSize;
// purge group from search index.
generateDeleteFromSearchIndexTasks(DomainGroup.class, Collections.singletonList(deleteGroupResponse
.getGroupId()), inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for purge group from search index: " + (endSize - startSize));
}
startSize = endSize;
// purge ALL activities from search index.
generateDeleteFromSearchIndexTasks(Activity.class, deleteActivityResponse.getActivityIds(), inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for purge ALL activities from search index: " + (endSize - startSize));
}
startSize = endSize;
// remove ALL activities from cache (low priority should we even do this?)
generateIndividualDeleteKeyFromCacheTasks(new HashSet<String>(createKeys(CacheKeys.ACTIVITY_BY_ID,
deleteActivityResponse.getActivityIds())), inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for remove ALL activities from cache: " + (endSize - startSize));
}
startSize = endSize;
// remove ALL comments from cache (low priority should we even do this?)
generateIndividualDeleteKeyFromCacheTasks(new HashSet<String>(createKeys(CacheKeys.COMMENT_BY_ID,
deleteActivityResponse.getCommentIds())), inActionContext);
if (log.isDebugEnabled())
{
endSize = inActionContext.getUserActionRequests().size();
log.debug("Tasks for remove ALL comments from cache: " + (endSize - startSize));
log.debug("Total async tasks for delete: " + endSize);
}
if (log.isInfoEnabled())
{
long now = System.currentTimeMillis();
String logMessage = "GroupId: " + groupId + " DB delete: " + (endDB - startDB) + " Async task generation: "
+ (now - startAsync) + " Total time: " + (now - startDB);
log.info(logMessage);
}
return Boolean.TRUE;
}
/**
* Queues UserActionRequest for deleteActivitiesFromLists actions. Queues a task for each key passed in.
*
* @param keys
* Cache keys to remove ids from
* @param values
* The ids to remove from given lists.
* @param inActionContext
* {@link TaskHandlerActionContext}.
*/
private void generateRemoveIdsFromListTasks(final List<String> keys, final List<Long> values,
final TaskHandlerActionContext<ActionContext> inActionContext)
{
for (String key : keys)
{
// Put an action on the queue to delete the activities from the appropriate lists
inActionContext.getUserActionRequests().add(
new UserActionRequest("deleteIdsFromLists", null, new DeleteIdsFromListsRequest(Collections
.singletonList(key), values)));
}
}
/**
* Queues UserActionRequest for deleteFromSearchIndex actions.
*
* @param clazz
* Class of item to remove.
* @param ids
* The ids to remove.
* @param inActionContext
* {@link TaskHandlerActionContext}.
*/
private void generateDeleteFromSearchIndexTasks(final Class< ? > clazz, final List<Long> ids,
final TaskHandlerActionContext<ActionContext> inActionContext)
{
// Put an action on the queue to delete the activities from search index
inActionContext.getUserActionRequests()
.add(
new UserActionRequest("deleteFromSearchIndexAction", null, new DeleteFromSearchIndexRequest(
clazz, ids)));
}
/**
* Queues Single UserActionRequest for deleteKeysFromCache action.
*
* @param keys
* keys to delete from cache.
* @param inActionContext
* {@link TaskHandlerActionContext}.
*/
private void generateSingleDeleteKeyFromCacheTask(final Set<String> keys,
final TaskHandlerActionContext<ActionContext> inActionContext)
{
inActionContext.getUserActionRequests().add(
new UserActionRequest("deleteCacheKeysAction", null, (Serializable) keys));
}
/**
* Queues UserActionRequest for deleteKeysFromCache action, one for each key.
*
* @param keys
* keys to delete from cache.
* @param inActionContext
* {@link TaskHandlerActionContext}.
*/
private void generateIndividualDeleteKeyFromCacheTasks(final Set<String> keys,
final TaskHandlerActionContext<ActionContext> inActionContext)
{
Iterator<String> it = keys.iterator();
while (it.hasNext())
{
inActionContext.getUserActionRequests().add(
new UserActionRequest("deleteCacheKeysAction", null, new HashSet<String>(Collections
.singletonList(it.next()))));
}
}
/**
* Utility method for converting Map of personid/starred activity ids to tasks to update the users'
* CacheKeys.STARRED_BY_PERSON_ID lists in cache.
*
* @param inDeleteActivityResponse
* {@link BulkActivityDeleteResponse} containing Map.
* @param inActionContext
* {@link TaskHandlerActionContext}.
*/
private void generateRemoveIdsFromStarredActivityListTasks(
final BulkActivityDeleteResponse inDeleteActivityResponse,
final TaskHandlerActionContext<ActionContext> inActionContext)
{
Map<Long, Set<Long>> starMap = inDeleteActivityResponse.getPeopleWithStarredActivities();
Set<Long> personIds = starMap.keySet();
for (Long personId : personIds)
{
generateRemoveIdsFromListTasks(Collections.singletonList(CacheKeys.STARRED_BY_PERSON_ID + personId),
new ArrayList<Long>(starMap.get(personId)), inActionContext);
}
}
/**
* Generate keys to delete from cache as a result of deleting a group.
*
* @param inRequest
* {@link DeleteGroupCacheUpdateRequest}.
* @return Keys to delete from cache as a result of deleting a group.
*/
private Set<String> getKeysToPurgeFromCache(final DeleteGroupResponse inRequest)
{
Set<String> keysToPurgeFromCache = new HashSet<String>();
// remove group by id/shortname from cache.
keysToPurgeFromCache.add(CacheKeys.GROUP_BY_SHORT_NAME + inRequest.getGroupShortName());
keysToPurgeFromCache.add(CacheKeys.GROUP_BY_ID + inRequest.getGroupId());
// remove follower person ids list for group
keysToPurgeFromCache.add(CacheKeys.FOLLOWERS_BY_GROUP + inRequest.getGroupId());
// remove coordinator ids list for group.
keysToPurgeFromCache.add(CacheKeys.COORDINATOR_PERSON_IDS_BY_GROUP_ID + inRequest.getGroupId());
// remove the group stream from cache
keysToPurgeFromCache.add(CacheKeys.ENTITY_STREAM_BY_SCOPE_ID + inRequest.getStreamScopeId());
return keysToPurgeFromCache;
}
/**
* Generate cacheKeys.
*
* @param keyRoot
* Root of key.
* @param inIds
* Id for key
* @return List of generated keys.
*/
@SuppressWarnings("unchecked")
private List<String> createKeys(final String keyRoot, final List inIds)
{
List keys = new ArrayList();
for (int i = 0; i < inIds.size(); i++)
{
keys.add(keyRoot + inIds.get(i));
}
return keys;
}
}