/*
* 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.actions.strategies;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.PersistenceException;
import org.apache.commons.logging.Log;
import org.eurekastreams.commons.actions.TaskHandlerExecutionStrategy;
import org.eurekastreams.commons.actions.context.DefaultPrincipal;
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.ValidationException;
import org.eurekastreams.commons.logging.LogFactory;
import org.eurekastreams.commons.server.UserActionRequest;
import org.eurekastreams.server.action.request.profile.SetFollowingStatusByGroupCreatorRequest;
import org.eurekastreams.server.action.request.stream.SyncGroupActivityRecipientParentOrganizationRequest;
import org.eurekastreams.server.domain.DomainFormatUtility;
import org.eurekastreams.server.domain.DomainGroup;
import org.eurekastreams.server.domain.Follower;
import org.eurekastreams.server.domain.Organization;
import org.eurekastreams.server.domain.Person;
import org.eurekastreams.server.domain.strategies.OrganizationHierarchyTraverser;
import org.eurekastreams.server.domain.strategies.OrganizationHierarchyTraverserBuilder;
import org.eurekastreams.server.persistence.DomainGroupMapper;
import org.eurekastreams.server.persistence.OrganizationMapper;
import org.eurekastreams.server.persistence.mappers.GetAllPersonIdsWhoHaveGroupCoordinatorAccess;
import org.eurekastreams.server.persistence.mappers.cache.ClearPrivateGroupIdsViewableByCoordinatorCacheOnGroupUpdate;
import org.eurekastreams.server.search.modelview.DomainGroupModelView;
import org.hibernate.validator.InvalidStateException;
import org.hibernate.validator.InvalidValue;
/**
* Class to update a Group.
*
*/
public class GroupUpdater extends GroupPersister
{
/**
* Logger.
*/
private Log log = LogFactory.make();
/**
* The key to use to store the original domain group name into the fields map between Get and Persist.
*/
private static final String ORIGINAL_GROUP_NAME_KEY = "__KEY_ORIGINAL_GROUP_NAME_KEY";
/**
* The key to use to store the original domain group coordinators into the fields map between Get and Persist.
*/
private static final String ORIGINAL_GROUP_COORDINATORS_KEY = "__KEY_ORIGINAL_GROUP_COORDINATORS_KEY";
/**
* Key used to store original parent org between get and Persist.
*/
private static final String ORIGINAL_PARENT_ORG_KEY = "__KEY_ORIGINAL_PARENT_ORG_KEY";
/**
* Strategy for adding group followers (coordinators are automatically added as followers/members).
*/
private TaskHandlerExecutionStrategy followStrategy;
/**
* Mapper to clear the existing coordinators and followers search text.
*/
private ClearPrivateGroupIdsViewableByCoordinatorCacheOnGroupUpdate clearActivityStreamSearchStringForUsersMapper;
/**
* The organization hierarchy traverser builder - needed because this class is reused by all threads, we can't share
* OrganizationHierarchyTraversers.
*/
private final OrganizationHierarchyTraverserBuilder orgTraverserBuilder;
/**
* Constructor.
*
* @param inGroupMapper
* The group mapper.
* @param inOrgMapper
* The org mapper.
* @param inAccessCheckerMapper
* mapper to determine permissions
* @param inClearActivityStreamSearchStringForUsersMapper
* the mapper to clear out activity search strings for users
* @param inFollowStrategy
* used to automatically add coordinators as group followers/members.
* @param inOrgTraverserBuilder
* {@link OrganizationHierarchyTraverserBuilder}.
*/
public GroupUpdater(final DomainGroupMapper inGroupMapper,
final OrganizationMapper inOrgMapper,
final GetAllPersonIdsWhoHaveGroupCoordinatorAccess inAccessCheckerMapper,
final ClearPrivateGroupIdsViewableByCoordinatorCacheOnGroupUpdate // \n
inClearActivityStreamSearchStringForUsersMapper, final TaskHandlerExecutionStrategy inFollowStrategy,
final OrganizationHierarchyTraverserBuilder inOrgTraverserBuilder)
{
super(inGroupMapper, inOrgMapper);
clearActivityStreamSearchStringForUsersMapper = inClearActivityStreamSearchStringForUsersMapper;
followStrategy = inFollowStrategy;
orgTraverserBuilder = inOrgTraverserBuilder;
}
/**
* Returns Group base on id passed in inFields.
*
* @param inActionContext
* action context
* @param inFields
* the property map.
* @return Organization base on id passed in inFields.
*/
@Override
public DomainGroup get(final TaskHandlerActionContext<PrincipalActionContext> inActionContext,
final Map<String, Serializable> inFields)
{
long id = Long.parseLong(inFields.get("id").toString());
DomainGroup entity = getGroupMapper().findById(id);
// store the original domain group name between get and persist
inFields.put(ORIGINAL_GROUP_NAME_KEY, entity.getName());
inFields.put(ORIGINAL_GROUP_COORDINATORS_KEY, (Serializable) entity.getCoordinators());
// store original parent org name.
inFields.put(ORIGINAL_PARENT_ORG_KEY, entity.getParentOrganization().getShortName());
// clear out the search text for the group coordinators now, before we
// commit to the db
if (!entity.isPublicGroup())
{
clearActivityStreamSearchStringForUsersMapper.execute(entity.getId());
}
return entity;
}
/**
* Updates the Group Data.
*
* @param inActionContext
* The action context.
* @param inFields
* The property map.
* @param inGroup
* The group.
* @throws Exception
* If error occurs.
*/
@Override
public void persist(final TaskHandlerActionContext<PrincipalActionContext> inActionContext,
final Map<String, Serializable> inFields, final DomainGroup inGroup) throws Exception
{
try
{
inGroup.setCapabilities(DomainFormatUtility.splitCapabilitiesString((String) inFields
.get(DomainGroupModelView.KEYWORDS_KEY)));
String creatorUserName = inActionContext.getActionContext().getPrincipal().getAccountId();
String newParentOrgName = (String) inFields.get(DomainGroupModelView.ORG_PARENT_KEY);
String origParentOrgName = (String) inFields.get(ORIGINAL_PARENT_ORG_KEY);
// if parent org has changed, update group and update stats for both original and new orgs.
if (origParentOrgName.compareToIgnoreCase(newParentOrgName) != 0)
{
Organization newParentOrg = getOrgMapper().findByShortName(newParentOrgName);
Organization origParentOrg = getOrgMapper().findByShortName(origParentOrgName);
if (null == newParentOrg)
{
// Parent org cannot be null.
throw new ValidationException();
}
inGroup.setParentOrganization(newParentOrg);
updateOrgStatistics(newParentOrg);
updateOrgStatistics(origParentOrg);
// queue action to update parent org for group activities, reindex activities, and sync activity cache
// lists for new/old orgs up the tree.
inActionContext.getUserActionRequests().add(
new UserActionRequest("syncGroupActivityRecipientParentOrganization", null,
new SyncGroupActivityRecipientParentOrganizationRequest(inGroup.getShortName(),
newParentOrgName, origParentOrgName)));
}
Set<Person> oldCoordinators = (Set<Person>) inFields.get(ORIGINAL_GROUP_COORDINATORS_KEY);
Set<Person> newCoordinators = new HashSet<Person>();
newCoordinators.addAll(inGroup.getCoordinators());
newCoordinators.removeAll(oldCoordinators);
// Make all coordinators follow/join the new group
for (Person coordinator : newCoordinators)
{
SetFollowingStatusByGroupCreatorRequest currentRequest = new SetFollowingStatusByGroupCreatorRequest(
coordinator.getId(), inGroup.getId(), Follower.FollowerStatus.FOLLOWING, inGroup.getName(),
false);
ServiceActionContext currentContext = new ServiceActionContext(currentRequest, new DefaultPrincipal(
creatorUserName, inActionContext.getActionContext().getPrincipal().getOpenSocialId(),
inActionContext.getActionContext().getPrincipal().getId()));
TaskHandlerActionContext<PrincipalActionContext> currentTaskHandlerActionContext =
// line break
new TaskHandlerActionContext<PrincipalActionContext>(currentContext, inActionContext
.getUserActionRequests());
followStrategy.execute(currentTaskHandlerActionContext);
}
getGroupMapper().flush();
queueAsyncAction(inActionContext, inGroup, true);
// see if the group name changed - if so, kick off a task to update all activities that were posted to the
// group
String previousGroupName = (String) inFields.get(ORIGINAL_GROUP_NAME_KEY);
String newGroupName = inGroup.getName();
if (previousGroupName == null || !previousGroupName.equals(newGroupName))
{
log.info("The name for domain group with short name " + inGroup.getShortName()
+ " has been changed from '" + previousGroupName + "' to '" + newGroupName
+ "'. Queuing up activityRecipientDomainGroupNameUpdaterAsyncAction async task to update "
+ "all activities posted to this group.");
// group name updated - kick off a task to update all activities posted to the group
inActionContext.getUserActionRequests().add(
new UserActionRequest("activityRecipientDomainGroupNameUpdaterAsyncAction", null, inGroup
.getShortName()));
// group name updated - kick off a task to update all notifications related to the group
inActionContext.getUserActionRequests().add(
new UserActionRequest("updateNotificationsOnGroupNameChange", null, inGroup.getId()));
}
}
catch (InvalidStateException e)
{
log.error("Failed to persist Group", e);
InvalidValue[] invalidValues = e.getInvalidValues();
ValidationException validationException = new ValidationException();
for (InvalidValue invalidValue : invalidValues)
{
validationException.addError(invalidValue.getPropertyName(), invalidValue.getMessage());
}
throw validationException;
}
catch (PersistenceException e)
{
log.error("Failed to persist Group", e);
}
}
/**
* Update an organization's stats.
*
* @param inOrganizaiton
* the org to update.
*/
private void updateOrgStatistics(final Organization inOrganizaiton)
{
OrganizationHierarchyTraverser orgTraverser = orgTraverserBuilder.getOrganizationHierarchyTraverser();
orgTraverser.traverseHierarchy(inOrganizaiton);
getOrgMapper().updateOrganizationStatistics(orgTraverser);
}
}