/*
* Copyright (c) 2010-2012 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.stream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.eurekastreams.commons.actions.TaskHandlerExecutionStrategy;
import org.eurekastreams.commons.actions.context.PrincipalActionContext;
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.SharedResourceRequest;
import org.eurekastreams.server.action.request.notification.ActivityNotificationsRequest;
import org.eurekastreams.server.action.request.notification.CreateNotificationsRequest;
import org.eurekastreams.server.action.request.notification.CreateNotificationsRequest.RequestType;
import org.eurekastreams.server.action.request.stream.PostActivityRequest;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.stream.Activity;
import org.eurekastreams.server.domain.stream.ActivityDTO;
import org.eurekastreams.server.domain.stream.ActivityVerb;
import org.eurekastreams.server.domain.stream.SharedResource;
import org.eurekastreams.server.persistence.mappers.DomainMapper;
import org.eurekastreams.server.persistence.mappers.InsertMapper;
import org.eurekastreams.server.persistence.mappers.cache.Cache;
import org.eurekastreams.server.persistence.mappers.cache.CacheKeys;
import org.eurekastreams.server.persistence.mappers.cache.PostActivityUpdateStreamsByActorMapper;
import org.eurekastreams.server.persistence.mappers.cache.Transformer;
import org.eurekastreams.server.persistence.mappers.requests.InsertActivityCommentRequest;
import org.eurekastreams.server.persistence.mappers.requests.PersistenceRequest;
import org.eurekastreams.server.persistence.mappers.stream.InsertActivityComment;
import org.eurekastreams.server.search.modelview.PersonModelView;
import org.eurekastreams.server.service.actions.strategies.RecipientRetriever;
import org.eurekastreams.server.service.actions.strategies.activity.ActivityFilter;
/**
* This class contains the business logic for posting an Activity to the system.
*
*/
public class PostActivityExecutionStrategy implements TaskHandlerExecutionStrategy<PrincipalActionContext>
{
/**
* Local logger instance.
*/
private final Log logger = LogFactory.make();
/**
* Instance of {@link InsertMapper} for {@link Activity} objects.
*/
private final InsertMapper<Activity> insertMapper;
/**
* Instance of the {@link InsertActivityComment} mapper.
*/
private final InsertActivityComment insertCommentDAO;
/**
* Instance of the {@link BulkActivitiesMapper}.
*/
private final DomainMapper<List<Long>, List<ActivityDTO>> activitiesMapper;
/**
* Instance of the {@link RecipientRetriever} responsible for retrieving the recipient from the {@link ActivityDTO}.
*/
private final RecipientRetriever recipientRetriever;
/**
* Instance of the {@link PostActivityUpdateStreamsByActorMapper} responsible for updating the cached lists directly
* related to the actor of the {@link Activity}.
*/
private final PostActivityUpdateStreamsByActorMapper updateStreamsByActorMapper;
/**
* Mapper to get or insert shared resources.
*/
private final DomainMapper<SharedResourceRequest, SharedResource> findOrInsertSharedResourceMapper;
/**
* The cache to use to clean up shared resources.
*/
private final Cache cache;
/**
* List of filters to apply to action.
*/
private final List<ActivityFilter> filters;
/**
* Mapper to get a person model view by account id.
*/
private final DomainMapper<String, PersonModelView> getPersonModelViewByAccountIdMapper;
/** Transforms a shared resource's unique key to a cache key suffix. */
private final Transformer<String, String> sharedResourceCacheKeySuffixTransformer;
/**
* Constructor for the PostActivityExecutionStrategy.
*
* @param inInsertMapper
* - instance of the {@link InsertMapper} for the {@link Activity} object.
* @param inInsertCommentDAO
* - instance of the {@link InsertActivityComment} mapper.
* @param inActivitiesMapper
* - instance of the {@link BulkActivitiesMapper}.
* @param inRecipientRetriever
* - instance of the {@link RecipientRetriever}.
* @param inUpdateStreamsByActorMapper
* - instance of the {@link PostActivityUpdateStreamsByActorMapper}.
* @param inFindOrInsertSharedResourceMapper
* mapper to find or insert shared resources
* @param inCache
* the cache to use to clean up shared resources immediately
* @param inGetPersonModelViewByAccountIdMapper
* person mapper.
* @param inFilters
* activity filters.
* @param inSharedResourceCacheKeySuffixTransformer
* Transforms a shared resource's unique key to a cache key suffix.
*/
public PostActivityExecutionStrategy(final InsertMapper<Activity> inInsertMapper,
final InsertActivityComment inInsertCommentDAO,
final DomainMapper<List<Long>, List<ActivityDTO>> inActivitiesMapper,
final RecipientRetriever inRecipientRetriever,
final PostActivityUpdateStreamsByActorMapper inUpdateStreamsByActorMapper,
final DomainMapper<SharedResourceRequest, SharedResource> inFindOrInsertSharedResourceMapper,
final Cache inCache, final DomainMapper<String, PersonModelView> inGetPersonModelViewByAccountIdMapper,
final List<ActivityFilter> inFilters,
final Transformer<String, String> inSharedResourceCacheKeySuffixTransformer)
{
insertMapper = inInsertMapper;
insertCommentDAO = inInsertCommentDAO;
activitiesMapper = inActivitiesMapper;
recipientRetriever = inRecipientRetriever;
updateStreamsByActorMapper = inUpdateStreamsByActorMapper;
findOrInsertSharedResourceMapper = inFindOrInsertSharedResourceMapper;
cache = inCache;
getPersonModelViewByAccountIdMapper = inGetPersonModelViewByAccountIdMapper;
filters = inFilters;
sharedResourceCacheKeySuffixTransformer = inSharedResourceCacheKeySuffixTransformer;
}
/**
* {@inheritDoc}.
*
* Perform the business logic for posting an {@link Activity} to the system.
*
* Create the {@link Activity} object from the provided {@link ActivityDTO}, assign appropriate values, update the
* cached streams related to the actor (surgical strike) and submit assemble async requests to be submitted to the
* queue.
*
* @return Populated instance of the {@link ActivityDTO} after being persisted.
*/
@Override
public Serializable execute(final TaskHandlerActionContext<PrincipalActionContext> inActionContext)
{
ActivityDTO inActivityDTO = ((PostActivityRequest) inActionContext.getActionContext().getParams())
.getActivityDTO();
ActivityDTO persistedActivityDTO;
Activity newActivity = convertDTOToActivity(inActivityDTO, inActionContext.getUserActionRequests());
List<UserActionRequest> queueRequests = new ArrayList<UserActionRequest>();
String actorAccountName = inActionContext.getActionContext().getPrincipal().getAccountId();
newActivity.setPostedTime(new Date());
newActivity.setActorId(actorAccountName);
newActivity.setActorType(EntityType.PERSON);
long actorId = 0;
long destinationId = 0;
EntityType destinationType;
// Persist to long term storage.
insertMapper.execute(new PersistenceRequest<Activity>(newActivity));
insertMapper.flush();
// Force the cache to load the activityDTO in from the db.
List<ActivityDTO> activityResults = activitiesMapper.execute(Arrays.asList(newActivity.getId()));
persistedActivityDTO = activityResults.get(0);
actorId = persistedActivityDTO.getActor().getId();
destinationId = persistedActivityDTO.getDestinationStream().getDestinationEntityId();
destinationType = persistedActivityDTO.getDestinationStream().getType();
// add activity to destination entity streams
updateStreamsByActorMapper.execute(persistedActivityDTO);
// Insert the comment that was posted with a shared post.
if (inActivityDTO.getFirstComment() != null && inActivityDTO.getVerb().equals(ActivityVerb.SHARE))
{
insertCommentDAO.execute(new InsertActivityCommentRequest(actorId, persistedActivityDTO.getId(),
inActivityDTO.getFirstComment().getBody()));
}
RequestType requestType = null;
// Sends notifications for new personal stream posts.
if (destinationType == EntityType.PERSON)
{
requestType = RequestType.POST_PERSON_STREAM;
}
// Sends notifications for new group stream posts.
else if (destinationType == EntityType.GROUP)
{
requestType = RequestType.POST_GROUP_STREAM;
}
// Setup the queued requests.
if (requestType != null)
{
CreateNotificationsRequest notificationRequest = new ActivityNotificationsRequest(requestType, actorId,
destinationId, persistedActivityDTO.getEntityId());
queueRequests
.add(new UserActionRequest(CreateNotificationsRequest.ACTION_NAME, null, notificationRequest));
}
// TODO: fix this so activityDTO fields related to specific user
// are not saved in cache.
queueRequests.add(new UserActionRequest("postActivityAsyncAction", null, new PostActivityRequest(
persistedActivityDTO)));
inActionContext.getUserActionRequests().addAll(queueRequests);
PersonModelView person = getPersonModelViewByAccountIdMapper.execute(actorAccountName);
// execute filter strategies.
for (ActivityFilter filter : filters)
{
filter.filter(Collections.singletonList(persistedActivityDTO), person);
}
return persistedActivityDTO;
}
/**
* Method to convert ActivityDTO to an Activity object.
*
* @param inActivityDTO
* - ActivityDTO instance to be converted.
* @param inUserActionRequestList
* the user action request list - add any post-transaction requests to this list
* @return - Activity object populated with the values from the ActivityDTO passed in.
*/
private Activity convertDTOToActivity(final ActivityDTO inActivityDTO,
final List<UserActionRequest> inUserActionRequestList)
{
Activity currentActivity = new Activity();
currentActivity.setAnnotation(inActivityDTO.getAnnotation());
// This will only occur in the Share verb scenario.
if (inActivityDTO.getBaseObjectProperties().containsKey("originalActivityId")
&& (inActivityDTO.getBaseObjectProperties().get("originalActivityId") != null))
{
try
{
currentActivity.setOriginalActivityId(new Long(inActivityDTO.getBaseObjectProperties().get(
"originalActivityId")));
}
catch (NumberFormatException nex)
{
logger.error("Error occurred parsing original activity id: "
+ inActivityDTO.getBaseObjectProperties().get("originalActivityId"), nex);
}
}
if (inActivityDTO.getBaseObjectProperties().containsKey("targetUrl")
&& inActivityDTO.getBaseObjectProperties().get("targetUrl") != null)
{
String url = inActivityDTO.getBaseObjectProperties().get("targetUrl");
if (url != null)
{
// has a link to share
logger.info("New activity shares link with url: " + url);
SharedResource sr = findOrInsertSharedResourceMapper.execute(new SharedResourceRequest(url, null));
if (sr != null)
{
logger.info("Found shared resource - id: " + sr.getId());
currentActivity.setSharedLink(sr);
String cacheKey = CacheKeys.SHARED_RESOURCE_BY_UNIQUE_KEY
+ sharedResourceCacheKeySuffixTransformer.transform(url);
// delete the cache immediately
logger.debug("Immediately deleting cache key while in transaction '" + cacheKey
+ "', then queuing it up for post-transaction cleanup to avoid race.");
cache.delete(cacheKey);
// queue up a cache delete for after this transaction is
// closed - to prevent race condition
inUserActionRequestList.add(new UserActionRequest("deleteCacheKeysAction", null,
(Serializable) Collections.singleton(cacheKey)));
}
}
}
currentActivity.setBaseObject(inActivityDTO.getBaseObjectProperties());
currentActivity.setBaseObjectType(inActivityDTO.getBaseObjectType());
currentActivity.setLocation(inActivityDTO.getLocation());
currentActivity.setMood(inActivityDTO.getMood());
if (inActivityDTO.getOriginalActor() != null)
{
currentActivity.setOriginalActorId(inActivityDTO.getOriginalActor().getUniqueIdentifier());
currentActivity.setOriginalActorType(inActivityDTO.getOriginalActor().getType());
}
currentActivity.setRecipientStreamScope(recipientRetriever.getStreamScope(inActivityDTO));
currentActivity.setVerb(inActivityDTO.getVerb());
currentActivity.setIsDestinationStreamPublic(recipientRetriever.isDestinationStreamPublic(inActivityDTO));
currentActivity.setAppId(inActivityDTO.getAppId());
currentActivity.setAppName(inActivityDTO.getAppName());
currentActivity.setAppSource(inActivityDTO.getAppSource());
currentActivity.setAppType(inActivityDTO.getAppType());
currentActivity.setShowInStream(inActivityDTO.getShowInStream());
return currentActivity;
}
}