/* * 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.HashMap; import java.util.List; import java.util.Set; import org.eurekastreams.commons.actions.TaskHandlerExecutionStrategy; import org.eurekastreams.commons.actions.context.Principal; import org.eurekastreams.commons.actions.context.PrincipalActionContext; import org.eurekastreams.commons.actions.context.TaskHandlerActionContext; import org.eurekastreams.commons.actions.context.service.ServiceActionContext; import org.eurekastreams.commons.exceptions.ExecutionException; import org.eurekastreams.commons.server.UserActionRequest; import org.eurekastreams.server.action.request.notification.CreateNotificationsRequest; import org.eurekastreams.server.action.request.notification.CreateNotificationsRequest.RequestType; import org.eurekastreams.server.action.request.profile.RequestForGroupMembershipRequest; import org.eurekastreams.server.action.request.profile.SetFollowingStatusByGroupCreatorRequest; import org.eurekastreams.server.action.request.profile.SetFollowingStatusRequest; import org.eurekastreams.server.action.request.stream.DeleteIdsFromListsRequest; import org.eurekastreams.server.action.request.stream.PostActivityRequest; import org.eurekastreams.server.domain.EntityType; import org.eurekastreams.server.domain.Follower; import org.eurekastreams.server.domain.Follower.FollowerStatus; import org.eurekastreams.server.domain.stream.ActivityDTO; import org.eurekastreams.server.domain.stream.ActivityVerb; import org.eurekastreams.server.domain.stream.BaseObjectType; import org.eurekastreams.server.domain.stream.StreamEntityDTO; import org.eurekastreams.server.persistence.DomainGroupMapper; import org.eurekastreams.server.persistence.mappers.DomainMapper; import org.eurekastreams.server.persistence.mappers.cache.AddCachedGroupFollower; import org.eurekastreams.server.persistence.mappers.cache.CacheKeys; import org.eurekastreams.server.persistence.mappers.db.DeleteRequestForGroupMembership; import org.eurekastreams.server.persistence.mappers.stream.GetDomainGroupsByShortNames; import org.eurekastreams.server.search.modelview.DomainGroupModelView; import org.eurekastreams.server.search.modelview.PersonModelView; /** * Class responsible for providing the strategy that updates the appropriate lists when a group is followed. * */ public class SetFollowingGroupStatusExecution implements TaskHandlerExecutionStrategy<PrincipalActionContext> { /** * Local instance of the GetDomainGroupsByShortNames mapper. */ private final GetDomainGroupsByShortNames groupMapper; /** Mapper to get person by id (for getting account id). */ private final DomainMapper<Long, PersonModelView> getPersonByIdMapper; /** * Mapper to get the person id from an account id. */ private final DomainMapper<String, Long> getPersonIdFromAccountIdMapper; /** * Local instance of the DomainGroupMapper mapper. */ private final DomainGroupMapper domainGroupMapper; /** * Local instance of the AddCachedGroupFollower mapper. */ private final AddCachedGroupFollower addCachedGroupFollowerMapper; /** * Local instance of the GetGroupFollowerIds mapper. */ private final DomainMapper<Long, List<Long>> followerIdsMapper; /** * Mapper to remove group access requests. */ private final DeleteRequestForGroupMembership deleteRequestForGroupMembershipMapper; /** * The post activity executor. */ private final TaskHandlerExecutionStrategy postActivityExecutor; /** * Delete cache key mapper. */ private final DomainMapper<Set<String>, Boolean> deleteCacheKeyMapper; /** * Constructor for the SetFollowingGroupStatusExecution. * * @param inGroupMapper * - instance of the GetDomainGroupsByShortNames mapper. * @param inGetPersonByIdMapper * Mapper to get person by id (for getting account id). * @param inGetPersonIdFromAccountIdMapper * - Mapper to get the person id from an account id * @param inDomainGroupMapper * - instance of the DomainGroupMapper mapper. * @param inAddCachedGroupFollowerMapper * - instance of the AddCachedGroupFollower mapper. * @param inFollowerIdsMapper * - mapper to get the follower ids for a group * @param inDeleteRequestForGroupMembershipMapper * Mapper to remove group access requests. * @param inPostActivityExecutor * post executor. * @param inDeleteCacheKeyMapper * Delete cache key mapper. * */ public SetFollowingGroupStatusExecution(final GetDomainGroupsByShortNames inGroupMapper, final DomainMapper<Long, PersonModelView> inGetPersonByIdMapper, final DomainMapper<String, Long> inGetPersonIdFromAccountIdMapper, final DomainGroupMapper inDomainGroupMapper, final AddCachedGroupFollower inAddCachedGroupFollowerMapper, final DomainMapper<Long, List<Long>> inFollowerIdsMapper, final DeleteRequestForGroupMembership inDeleteRequestForGroupMembershipMapper, final TaskHandlerExecutionStrategy inPostActivityExecutor, final DomainMapper<Set<String>, Boolean> inDeleteCacheKeyMapper) { groupMapper = inGroupMapper; getPersonByIdMapper = inGetPersonByIdMapper; getPersonIdFromAccountIdMapper = inGetPersonIdFromAccountIdMapper; domainGroupMapper = inDomainGroupMapper; addCachedGroupFollowerMapper = inAddCachedGroupFollowerMapper; followerIdsMapper = inFollowerIdsMapper; deleteRequestForGroupMembershipMapper = inDeleteRequestForGroupMembershipMapper; postActivityExecutor = inPostActivityExecutor; deleteCacheKeyMapper = inDeleteCacheKeyMapper; } /** * {@inheritDoc}. * * This method sets the following status based on the passed in request object. There is an extra block of code here * that handles an additional request object type that passes in the follower and target ids by string name instead * of their long id's. This extra support is needed for the GroupCreator object that gets called from the back end * with long ids instead of the string unique keys which require an additional mapper to look up the correct long * values. */ @Override public Serializable execute(final TaskHandlerActionContext<PrincipalActionContext> inActionContext) throws ExecutionException { Long followerId; String followerAccountId = null; Long targetId; FollowerStatus followerStatus; List<UserActionRequest> taskRequests = new ArrayList<UserActionRequest>(); String targetName; boolean isPending = false; // this switching here is a hold over until the GroupCreator can be refactored to call this strategy // and not fail because of additional mapper calls on the DomainGroupModelView and PersonModelView objects. if (inActionContext.getActionContext().getParams() instanceof SetFollowingStatusRequest) { SetFollowingStatusRequest currentRequest = (SetFollowingStatusRequest) inActionContext.getActionContext() .getParams(); followerStatus = currentRequest.getFollowerStatus(); followerAccountId = currentRequest.getFollowerUniqueId(); followerId = getPersonIdFromAccountIdMapper.execute(followerAccountId); DomainGroupModelView targetResult = groupMapper.fetchUniqueResult(currentRequest.getTargetUniqueId()); targetName = targetResult.getName(); targetId = targetResult.getEntityId(); } else if (inActionContext.getActionContext().getParams() instanceof SetFollowingStatusByGroupCreatorRequest) { SetFollowingStatusByGroupCreatorRequest currentRequest = // \n (SetFollowingStatusByGroupCreatorRequest) inActionContext.getActionContext().getParams(); followerId = currentRequest.getFollowerId(); targetId = currentRequest.getTargetId(); targetName = currentRequest.getTargetName(); followerStatus = currentRequest.getFollowerStatus(); isPending = currentRequest.isPending(); if (Follower.FollowerStatus.FOLLOWING.equals(followerStatus) && !isPending) { followerAccountId = getPersonByIdMapper.execute(followerId).getAccountId(); } } else { throw new IllegalArgumentException("Invalid Request type sent to SetFollowingGroupStatusExecution."); } switch (followerStatus) { case FOLLOWING: // Update the db and cache for list of followers and following. domainGroupMapper.addFollower(followerId, targetId); // Update the cache list of followers addCachedGroupFollowerMapper.execute(followerId, targetId); // Queue async action to remove the newly followed group from cache (to sync follower counts) taskRequests.add(new UserActionRequest("deleteCacheKeysAction", null, (Serializable) Collections .singleton(CacheKeys.GROUP_BY_ID + targetId))); // remove any requests from the user for group membership if (deleteRequestForGroupMembershipMapper .execute(new RequestForGroupMembershipRequest(targetId, followerId))) { // if any requests were present, then user was just approved for access taskRequests.add(new UserActionRequest("createNotificationsAction", null, new CreateNotificationsRequest(RequestType.REQUEST_GROUP_ACCESS_APPROVED, inActionContext .getActionContext().getPrincipal().getId(), targetId, followerId))); } // remove person modelview from cache as groupstreamhiddenlineindex will be changed. deleteCacheKeyMapper.execute(Collections.singleton(CacheKeys.PERSON_BY_ID + followerId)); // Sends new follower notifications. CreateNotificationsRequest notificationRequest = new CreateNotificationsRequest(RequestType.GROUP_FOLLOWER, followerId, targetId, 0); taskRequests.add(new UserActionRequest("createNotificationsAction", null, notificationRequest)); // Posts a message to the user's personal stream unless this is a new pending group if (!isPending) { StreamEntityDTO destination = new StreamEntityDTO(); destination.setUniqueIdentifier(followerAccountId); destination.setType(EntityType.PERSON); ActivityDTO activity = new ActivityDTO(); HashMap<String, String> props = new HashMap<String, String>(); activity.setBaseObjectProperties(props); String content = "%EUREKA:ACTORNAME% has joined the " + targetName + " group"; activity.getBaseObjectProperties().put("content", content); activity.setDestinationStream(destination); activity.setBaseObjectType(BaseObjectType.NOTE); activity.setVerb(ActivityVerb.POST); // Note: create a principal for the follower: we want to post on the follower's stream as the follower. // The current principal will be different from the follower in some cases, namely when following a // private group (the current principal / actor is the coordinator who approved access). postActivityExecutor.execute(new TaskHandlerActionContext<PrincipalActionContext>( new ServiceActionContext(new PostActivityRequest(activity), createPrincipal(followerId, followerAccountId)), inActionContext.getUserActionRequests())); } break; case NOTFOLLOWING: // Update the db and cache for list of followers and following. domainGroupMapper.removeFollower(followerId, targetId); // Queue async action to remove the newly followed group from cache (to sync follower counts) taskRequests.add(new UserActionRequest("deleteCacheKeysAction", null, (Serializable) Collections .singleton(CacheKeys.GROUP_BY_ID + targetId))); // Remove the current user that is severing a relationship with the target group // from the list of followers for that target group. taskRequests.add(new UserActionRequest("deleteIdsFromLists", null, new DeleteIdsFromListsRequest( Collections.singletonList(CacheKeys.FOLLOWERS_BY_GROUP + targetId), Collections .singletonList(followerId)))); // Remove the target group the current user is now following from the list of // groups that the current user is already following. taskRequests.add(new UserActionRequest("deleteIdsFromLists", null, new DeleteIdsFromListsRequest( Collections.singletonList(CacheKeys.GROUPS_FOLLOWED_BY_PERSON + followerId), Collections .singletonList(targetId)))); break; default: // nothing to do here. } inActionContext.getUserActionRequests().addAll(taskRequests); return followerIdsMapper.execute(targetId).size(); } /** * Creates a principal for the given user's id and account id. * * @param followerId * Person id. * @param followerAccountId * Person account id. * @return Principal. */ private Principal createPrincipal(final Long followerId, final String followerAccountId) { return new Principal() { @Override public String getOpenSocialId() { return null; } @Override public Long getId() { return followerId; } @Override public String getAccountId() { return followerAccountId; } @Override public String getSessionId() { return ""; } }; } }