/*
* Copyright (c) 2009-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.service.opensocial.spi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.Future;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.common.util.ImmediateFuture;
import org.apache.shindig.protocol.ProtocolException;
import org.apache.shindig.protocol.RestfulCollection;
import org.apache.shindig.social.core.model.ActivityImpl;
import org.apache.shindig.social.opensocial.model.Activity;
import org.apache.shindig.social.opensocial.spi.ActivityService;
import org.apache.shindig.social.opensocial.spi.CollectionOptions;
import org.apache.shindig.social.opensocial.spi.GroupId;
import org.apache.shindig.social.opensocial.spi.UserId;
import org.eurekastreams.commons.actions.TaskHandlerAction;
import org.eurekastreams.commons.actions.context.Principal;
import org.eurekastreams.commons.actions.context.service.ServiceActionContext;
import org.eurekastreams.commons.actions.service.ServiceAction;
import org.eurekastreams.commons.actions.service.TaskHandlerServiceAction;
import org.eurekastreams.commons.logging.LogFactory;
import org.eurekastreams.commons.server.service.ActionController;
import org.eurekastreams.server.action.principal.OpenSocialPrincipalPopulator;
import org.eurekastreams.server.action.request.opensocial.GetUserActivitiesRequest;
import org.eurekastreams.server.action.request.stream.PostActivityRequest;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.GadgetDefinition;
import org.eurekastreams.server.domain.GeneralGadgetDefinition;
import org.eurekastreams.server.domain.gadgetspec.GadgetMetaDataDTO;
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.DomainEntityMapper;
import org.eurekastreams.server.persistence.GadgetDefinitionMapper;
import org.eurekastreams.server.service.opensocial.gadgets.spec.GadgetMetaDataFetcher;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* This class provides the implementation of the ActivityService interface for Shindig.
*
*/
public class ActivityServiceImpl implements ActivityService
{
/**
* Logger.
*/
private final Log log = LogFactory.make();
/**
* Action that gets a specified set of activities for a specified user.
*/
private final ServiceAction getUserActivitiesAction;
/**
* Action that deletes a specified set of activities for a specified user.
*/
private final TaskHandlerAction deleteUserActivities;
/**
* Instance of the {@link ActionController} for this class.
*/
private final ActionController serviceActionController;
/**
* Instance of the {@link OpenSocialPrincipalPopulator} for this class.
*/
private final OpenSocialPrincipalPopulator openSocialPrincipalPopulator;
/**
* Instance of the {@link TaskHandlerServiceAction} for this class.
*/
private final TaskHandlerServiceAction postActivityAction;
/** For getting gadget definition (in order to get name). */
private final DomainEntityMapper<GadgetDefinition> gadgetDefinitionMapper;
/** For getting gadget name. */
private final GadgetMetaDataFetcher gadgetMetaDataFetcher;
/**
* Basic constructor for the PersonService implementation.
*
* @param inGetUserActivitiesAction
* the action to get a specified set of activities for a specified user.
* @param inDeleteActivitiesAction
* the action to deleted a specified set of activities for a specified user.
* @param inServiceActionController
* - instance of the {@link ActionController} used to execute the actions.
* @param inOpenSocialPrincipalPopulator
* - instance of the {@link OpenSocialPrincipalPopulator} used to retrieve a Principal object based on
* the opensocial id.
* @param inPostActivityAction
* the action to create an activity.
* @param inGadgetDefinitionMapper
* Mapper to fetch gadget definitions (for looking up title).
* @param inGadgetMetaDataFetcher
* Used to fetch gadget metadata (for looking up title).
*/
@Inject
public ActivityServiceImpl(@Named("getUserActivities") final ServiceAction inGetUserActivitiesAction,
@Named("deleteUserActivities") final TaskHandlerAction inDeleteActivitiesAction,
final ActionController inServiceActionController,
final OpenSocialPrincipalPopulator inOpenSocialPrincipalPopulator,
@Named("postPersonActivityServiceActionTaskHandler") final TaskHandlerServiceAction inPostActivityAction,
@Named("jpaGadgetDefinitionMapper") final GadgetDefinitionMapper inGadgetDefinitionMapper,
final GadgetMetaDataFetcher inGadgetMetaDataFetcher)
{
getUserActivitiesAction = inGetUserActivitiesAction;
deleteUserActivities = inDeleteActivitiesAction;
serviceActionController = inServiceActionController;
openSocialPrincipalPopulator = inOpenSocialPrincipalPopulator;
postActivityAction = inPostActivityAction;
gadgetDefinitionMapper = inGadgetDefinitionMapper;
gadgetMetaDataFetcher = inGadgetMetaDataFetcher;
}
/**
* Create Activity Implementation for Shindig.
*
* @param userId
* - id of the user to create the activity for.
* @param groupId
* - id of the group that the user belongs to.
* @param appId
* - id of the application creating the activity.
* @param fields
* - //TODO not sure about this one yet.
* @param activity
* - the activity to be added.
* @param token
* - the security token for the request.
*
* @return void
*/
public Future<Void> createActivity(final UserId userId, final GroupId groupId, final String appId,
final Set<String> fields, final Activity activity, final SecurityToken token)
{
log.debug("Entering createActivity data with userId " + userId.getUserId(token) + ", appId " + appId + ", "
+ fields.size() + ", AcivityId " + activity.getId() + ", token appId " + token.getAppId());
try
{
Principal currentUserPrincipal = openSocialPrincipalPopulator.getPrincipal(userId.getUserId(token));
// Create the actor.
StreamEntityDTO actorEntity = new StreamEntityDTO();
actorEntity.setUniqueIdentifier(currentUserPrincipal.getOpenSocialId());
actorEntity.setType(EntityType.PERSON);
// Create the destination stream.
StreamEntityDTO destStream = new StreamEntityDTO();
destStream.setUniqueIdentifier(currentUserPrincipal.getAccountId());
destStream.setType(EntityType.PERSON);
// Create the activitydto object.
ActivityDTO currentActivity = new ActivityDTO();
currentActivity.setActor(actorEntity);
currentActivity.setBaseObjectProperties(new HashMap<String, String>(
convertActivityFromOSToEureka(activity)));
Long appIdNumeric = Long.valueOf(appId);
currentActivity.setAppId(appIdNumeric);
currentActivity.setAppType(EntityType.APPLICATION);
currentActivity.setAppName(lookupGadgetTitle(appIdNumeric));
// Sets default activity type
if (!currentActivity.getBaseObjectProperties().containsKey("baseObjectType"))
{
currentActivity.setBaseObjectType(BaseObjectType.NOTE);
}
else
{
String objectType = currentActivity.getBaseObjectProperties().get("baseObjectType");
currentActivity.setBaseObjectType(BaseObjectType.valueOf(objectType));
if (currentActivity.getBaseObjectProperties().containsKey("source"))
{
currentActivity.setAppSource(currentActivity.getBaseObjectProperties().get("source"));
}
}
currentActivity.setVerb(ActivityVerb.POST);
currentActivity.setDestinationStream(destStream);
PostActivityRequest params = new PostActivityRequest(currentActivity);
ServiceActionContext currentContext = new ServiceActionContext(params, currentUserPrincipal);
// Make the call to the action to perform the create.
serviceActionController.execute(currentContext, postActivityAction);
}
catch (Exception e)
{
log.error("Error occurred creating Activity ", e);
throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
}
return ImmediateFuture.newInstance(null);
}
/**
* Retrieves the gadget's title given it's ID.
*
* @param appId
* Gadget ID.
* @return Title.
* @throws Exception
* On error.
*/
private String lookupGadgetTitle(final Long appId) throws Exception
{
// TODO: This is the brute-force approach. It gets the gadget definition from the database, then uses the gadget
// metadata fetcher which does a whole bunch of stuff that we don't need (e.g. user prefs, etc.) -- all just so
// we can get the title. Note that we should not use the title from the gadget definition even though it is
// there - it is deprecated.
GadgetDefinition gadgetDef = gadgetDefinitionMapper.findById(appId);
List<GadgetMetaDataDTO> gadgetsMetadata =
gadgetMetaDataFetcher.getGadgetsMetaData(Collections.singletonMap(gadgetDef.getUrl(),
(GeneralGadgetDefinition) gadgetDef));
return gadgetsMetadata.get(0).getTitle();
}
/**
* Delete activities implementation for Shindig.
*
* @param userId
* - id of the user to delete the activities for.
* @param groupId
* - id of the group the user is in.
* @param appId
* - id of the application deleting the activities.
* @param activityIds
* - set of ids to be deleted.
* @param token
* - the security token for the request.
*
* @return void
*/
public Future<Void> deleteActivities(final UserId userId, final GroupId groupId, final String appId,
final Set<String> activityIds, final SecurityToken token)
{
log.debug("Entering deleteActivities data with userId" + userId.getUserId(token) + ", appId " + appId + ", "
+ activityIds.size() + ", token appId " + token.getAppId());
Principal currentUserPrincipal = openSocialPrincipalPopulator.getPrincipal(userId.getUserId(token));
try
{
// convert set of string to list of longs to call action with.
ArrayList<Long> params = new ArrayList<Long>(activityIds.size());
for (String id : activityIds)
{
params.add(Long.parseLong(id));
}
// create the action context.
ServiceActionContext currentContext = new ServiceActionContext(params, currentUserPrincipal);
// execute the action.
serviceActionController.execute(currentContext, deleteUserActivities);
}
catch (Exception e)
{
log.error("Error occurred deleting OpenSocial Application Data " + e.toString());
throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString());
}
return ImmediateFuture.newInstance(null);
}
/**
* Shindig implementation for retrieving activities from a set of users.
*
* @param userIds
* - set of userIds to retrieve activities for.
* @param groupId
* - //TODO not sure about this one yet.
* @param appId
* - id of the application requesting the activities.
* @param fields
* - set of fields to retrieve.
* @param options
* - collection of options for retrieving activities.
* @param token
* - the security token for the request.
*
* @return collection of activities.
*/
@SuppressWarnings("unchecked")
public Future<RestfulCollection<Activity>> getActivities(final Set<UserId> userIds, final GroupId groupId,
final String appId, final Set<String> fields, final CollectionOptions options, final SecurityToken token)
{
log.trace("Entering getActivities");
List<Activity> osActivities = new ArrayList<Activity>();
try
{
Set<String> userIdList = new HashSet<String>();
for (UserId currentUserId : userIds)
{
if (!currentUserId.getUserId(token).equals("null"))
{
userIdList.add(currentUserId.getUserId(token));
}
}
log.debug("Sending getActivities userIdList to action: " + userIdList.toString());
GetUserActivitiesRequest currentRequest = new GetUserActivitiesRequest(new ArrayList<Long>(), userIdList);
ServiceActionContext currentContext =
new ServiceActionContext(currentRequest, openSocialPrincipalPopulator.getPrincipal(token
.getViewerId()));
LinkedList<ActivityDTO> activities =
(LinkedList<ActivityDTO>) serviceActionController.execute(currentContext, getUserActivitiesAction);
log.debug("Retrieved " + activities.size() + " activities from action");
for (ActivityDTO currentActivity : activities)
{
osActivities.add(convertActivityFromEurekaActivityDTOToOS(currentActivity));
}
}
catch (Exception ex)
{
log.error("Error occurred retrieving activities ", ex);
throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
}
return ImmediateFuture.newInstance(new RestfulCollection<Activity>(osActivities));
}
/**
* Shindig implementation for retrieving activities from a single user.
*
* @param userId
* - id of the user to retrieve the activities for.
* @param groupId
* - id of the group that the user is a member of.
* @param appId
* - id of the application requesting the activities.
* @param fields
* - set of fields to retrieve for the activity.
* @param options
* - collection of options for retrieving the activities.
* @param activityIds
* - set of ids of the activities to retrieve.
* @param token
* - the security token for the request.
*
* @return collection of activities.
*/
@SuppressWarnings("unchecked")
public Future<RestfulCollection<Activity>> getActivities(final UserId userId, final GroupId groupId,
final String appId, final Set<String> fields, final CollectionOptions options,
final Set<String> activityIds, final SecurityToken token)
{
log.trace("Entering getActivities");
List<Activity> osActivities = new ArrayList<Activity>();
try
{
log.debug("Sending getActivities activityIdList to action: " + activityIds.toString());
LinkedList<Long> activityIdsForRequest = new LinkedList<Long>();
for (String currentActivityId : activityIds)
{
activityIdsForRequest.add(new Long(currentActivityId));
}
Set<String> openSocialIdsForRequest = new HashSet<String>();
openSocialIdsForRequest.add(userId.getUserId(token));
GetUserActivitiesRequest currentRequest =
new GetUserActivitiesRequest(activityIdsForRequest, openSocialIdsForRequest);
ServiceActionContext currentContext =
new ServiceActionContext(currentRequest, openSocialPrincipalPopulator.getPrincipal(userId
.getUserId(token)));
LinkedList<ActivityDTO> activities =
(LinkedList<ActivityDTO>) serviceActionController.execute(currentContext, getUserActivitiesAction);
log.debug("Retrieved " + activities.size() + " activities from action");
for (ActivityDTO currentActivity : activities)
{
osActivities.add(convertActivityFromEurekaActivityDTOToOS(currentActivity));
}
}
catch (Exception ex)
{
log.error("Error occurred retrieving activities ", ex);
throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
}
return ImmediateFuture.newInstance(new RestfulCollection<Activity>(osActivities));
}
/**
* Shindig implementation for retrieving a single activity.
*
* @param userId
* - id of the user to retrieve the activity for.
* @param groupId
* - id of the group that the user belongs to.
* @param appId
* - id of the application requesting the activity.
* @param fields
* - //TODO not sure about this yet.
* @param activityId
* - id of the activity to retrieve.
* @param token
* - the security token for the request.
*
* @return single Activity
*/
public Future<Activity> getActivity(final UserId userId, final GroupId groupId, final String appId,
final Set<String> fields, final String activityId, final SecurityToken token)
{
// put the activity id in a list and call the getActivities method
Set<String> activityIds = new HashSet<String>();
activityIds.add(activityId);
Future<RestfulCollection<Activity>> activities =
getActivities(userId, groupId, appId, fields, null, activityIds, token);
// pull the returned activitity out of the list and return it to the client
Future<Activity> outActivity = null;
try
{
outActivity = ImmediateFuture.newInstance(activities.get().getEntry().get(0));
}
catch (Exception e)
{
log.error(e);
}
return outActivity;
}
/**
* Helper method that converts a passed in eurekastreams ActivityDTO object into a Shindig Activity object.
*
* @param inEurekaActivityDTO
* - eurekastreams ActivityDTO to be converted.
* @return converted OpenSocial Activity object.
*/
private Activity convertActivityFromEurekaActivityDTOToOS(final ActivityDTO inEurekaActivityDTO)
{
Activity osActivity = new ActivityImpl();
// TODO: this code needs to be refactor when new message object rearch is done.
// Populate the OpenSocial Activity properties.
// osActivity.setAppId(inEurekaActivity.getAppId());
// osActivity.setBody(inEurekaActivity.getBody());
// osActivity.setBodyId(inEurekaActivity.getBodyId());
osActivity.setExternalId(String.valueOf(inEurekaActivityDTO.getId()));
// osActivity.setId(String.valueOf(inEurekaActivityDTO.getEntityId()));
osActivity.setUpdated(inEurekaActivityDTO.getPostedTime());
// osActivity.setPriority(inEurekaActivity.getPriority());
// osActivity.setStreamFaviconUrl(inEurekaActivity.getStreamFaviconUrl());
// osActivity.setStreamSourceUrl(inEurekaActivity.getStreamSourceUrl());
osActivity.setStreamTitle(inEurekaActivityDTO.getDestinationStream().getDisplayName());
// osActivity.setStreamUrl(inEurekaActivity.getStreamUrl());
// osActivity.setTemplateParams(inEurekaActivity.getTemplateParams());
osActivity.setTitle(inEurekaActivityDTO.getBaseObjectProperties().get("Content"));
// osActivity.setTitleId(inEurekaActivity.getTitleId());
// osActivity.setUrl(inEurekaActivity.getUrl());
osActivity.setUserId(inEurekaActivityDTO.getActor().getUniqueIdentifier());
return osActivity;
}
/**
* Helper method that converts a passed in eurekastreams Activity object into a Shindig Activity object.
*
* @param inOSActivity
* - eurekastreams Activity to be converted.
* @return converted Activity object.
*/
private HashMap<String, String> convertActivityFromOSToEureka(final Activity inOSActivity)
{
HashMap<String, String> outMap = new HashMap<String, String>();
outMap.put("appId", inOSActivity.getAppId());
if (inOSActivity.getBody() == null)
{
outMap.put("body", "body");
}
else
{
outMap.put("body", inOSActivity.getBody());
}
outMap.put("bodyId", inOSActivity.getBodyId());
outMap.put("id", inOSActivity.getExternalId());
outMap.put("openSocialId", inOSActivity.getId());
if (inOSActivity.getUpdated() != null)
{
outMap.put("updated", inOSActivity.getUpdated().toString());
}
if (inOSActivity.getPostedTime() != null)
{
outMap.put("postedTime", inOSActivity.getPostedTime().toString());
}
if (inOSActivity.getPriority() == null)
{
outMap.put("priority", Float.valueOf(0).toString());
}
else
{
outMap.put("priority", inOSActivity.getPriority().toString());
}
outMap.put("streamFaviconUrl", inOSActivity.getStreamFaviconUrl());
outMap.put("streamSourceUrl", inOSActivity.getStreamSourceUrl());
outMap.put("streamTitle", inOSActivity.getStreamTitle());
outMap.put("streamUrl", inOSActivity.getStreamUrl());
if (inOSActivity.getTemplateParams() != null)
{
for (Entry<String, String> currentEntry : inOSActivity.getTemplateParams().entrySet())
{
outMap.put(currentEntry.getKey(), currentEntry.getValue());
}
}
if (inOSActivity.getTitle() != null && inOSActivity.getTitle().length() > 0)
{
outMap.put("content", inOSActivity.getTitle());
}
else
{
outMap.put("content", inOSActivity.getTitleId());
}
outMap.put("url", inOSActivity.getUrl());
outMap.put("userId", inOSActivity.getUserId());
return outMap;
}
}