/* * Copyright (c) 2010-2011 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.notification; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; 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.exceptions.ExecutionException; import org.eurekastreams.commons.logging.LogFactory; import org.eurekastreams.commons.server.UserActionRequest; import org.eurekastreams.server.action.execution.notification.translator.NotificationTranslator; import org.eurekastreams.server.action.request.notification.CreateNotificationsRequest; import org.eurekastreams.server.action.request.notification.CreateNotificationsRequest.RequestType; import org.eurekastreams.server.domain.NotificationDTO; import org.eurekastreams.server.domain.NotificationFilterPreference.Category; import org.eurekastreams.server.domain.NotificationFilterPreferenceDTO; import org.eurekastreams.server.domain.NotificationType; import org.eurekastreams.server.persistence.mappers.DomainMapper; import org.eurekastreams.server.persistence.mappers.db.GetNotificationFilterPreferencesByPeopleIds; import org.eurekastreams.server.search.modelview.PersonModelView; /** * Async action to generate notifications. * */ public class CreateNotificationsExecution implements TaskHandlerExecutionStrategy<ActionContext> { /** Local logger instance. */ private final Log logger = LogFactory.make(); /** Map of valid translators. */ private final Map<RequestType, NotificationTranslator> translators; /** * Populates a notification with any details not provided by the translator. Right now there is one, but eventually * there may be a list or map of them. */ private final NotificationPopulator populator; /** List of notifiers that should be executed. */ private final Map<String, Notifier> notifiers; /** Mapper to filter out unwanted notifications per recipient. */ private final GetNotificationFilterPreferencesByPeopleIds preferencesMapper; /** Mapper to get people for filtering (determining locked users, etc.). */ private final DomainMapper<Long, PersonModelView> personMapper; /** Provides the category for each notification type. */ private final Map<NotificationType, Category> notificationTypeToCategory; /** Recipient filter strategies per notifier type. */ private final Map<String, Iterable<RecipientFilter>> recipientFilters; /** * Constructor. * * @param inTranslators * map of translators to set. * @param inPopulator * notification populator. * @param inNotifiers * list of notifiers to set. * @param inPreferencesMapper * preferences mapper to set. * @param inPersonMapper * Mapper to get people for filtering. * @param inNotificationTypeCategories * Map providing the category for each notification type. * @param inRecipientFilters * Recipient filter strategies per notifier type. */ public CreateNotificationsExecution(final Map<RequestType, NotificationTranslator> inTranslators, final NotificationPopulator inPopulator, final Map<String, Notifier> inNotifiers, final GetNotificationFilterPreferencesByPeopleIds inPreferencesMapper, final DomainMapper<Long, PersonModelView> inPersonMapper, final Map<NotificationType, Category> inNotificationTypeCategories, final Map<String, Iterable<RecipientFilter>> inRecipientFilters) { translators = inTranslators; populator = inPopulator; notifiers = inNotifiers; preferencesMapper = inPreferencesMapper; personMapper = inPersonMapper; notificationTypeToCategory = inNotificationTypeCategories; recipientFilters = inRecipientFilters; } @Override public Serializable execute(final TaskHandlerActionContext<ActionContext> inActionContext) throws ExecutionException { CreateNotificationsRequest currentRequest = (CreateNotificationsRequest) inActionContext.getActionContext() .getParams(); if (logger.isInfoEnabled()) { logger.info("Generating notifications for " + currentRequest.getType()); } NotificationTranslator translator = translators.get(currentRequest.getType()); if (translator == null) { // exit if notification request type is disabled return Boolean.FALSE; } Collection<NotificationDTO> notifications = translator.translate(currentRequest.getActorId(), currentRequest.getDestinationId(), currentRequest.getActivityId()); if (notifications == null || notifications.isEmpty()) { return Boolean.TRUE; } // Gets all notification recipients so their preferences can be retrieved using the mapper List<Long> allRecipients = new ArrayList<Long>(); for (NotificationDTO dto : notifications) { allRecipients.addAll(dto.getRecipientIds()); } List<NotificationFilterPreferenceDTO> recipientFilterPreferences = preferencesMapper.execute(allRecipients); List<UserActionRequest> asyncRequests = inActionContext.getUserActionRequests(); for (NotificationDTO notification : notifications) { boolean populated = false; for (String notifierKey : notifiers.keySet()) { if (logger.isInfoEnabled()) { logger.info("Filtering " + notification.getType() + " recipients for notifier " + notifierKey + "from this list: " + notification.getRecipientIds()); } List<Long> filteredRecipients = filterRecipients(notification.getRecipientIds(), notification, recipientFilterPreferences, notifierKey); if (!filteredRecipients.isEmpty()) { // "populate" the notification with any additional data not set by the translator // (Do it here so that if the notification gets completely filtered out, we don't do the work if (!populated) { populator.populate(notification); populated = true; } try { if (logger.isInfoEnabled()) { logger.info("Sending notification " + notification.getType() + " via " + notifierKey + " to " + filteredRecipients + " destinationType of " + notification.getDestinationType()); } // clone notification and set recipients NotificationDTO clonedNotification = new NotificationDTO(notification); clonedNotification.setRecipientIds(filteredRecipients); UserActionRequest actionRequest = notifiers.get(notifierKey).notify(clonedNotification); if (actionRequest != null) { asyncRequests.add(actionRequest); } } catch (Exception ex) { logger.error( "Failed to send notifications from " + notifierKey + " for " + notification.getType(), ex); } } } } return Boolean.TRUE; } /** * Filters out notification recipients based on per-recipient settings. * * @param notificationRecipients * the list of all recipient ids for the notification, unfiltered. * @param notification * the notification. * @param preferences * the list of all notification preferences for users in the the allRecipient list. * @param notifierType * the key string for the notifier itself. * @return the filtered list of recipient ids. */ private List<Long> filterRecipients(final List<Long> notificationRecipients, final NotificationDTO notification, final List<NotificationFilterPreferenceDTO> preferences, final String notifierType) { Category category = notificationTypeToCategory.get(notification.getType()); List<Long> recipients = new ArrayList<Long>(notificationRecipients); // remove any users who opted out of the notification (for the given transport) for (NotificationFilterPreferenceDTO preference : preferences) { if (preference.getNotifierType().equals(notifierType) && preference.getNotificationCategory().equals(category)) { recipients.remove(preference.getPersonId()); } } // filter list further by configurable criteria Iterable<RecipientFilter> filters = recipientFilters.get(notifierType); if (filters == null) { return recipients; } else { List<Long> finalRecipients = new ArrayList<Long>(); eachRecipient: for (Long recipientId : recipients) { // unfortunately, users (who have not opted out of a given notifier) will be retrieved once PER notifier // (vs. once for the whole action) PersonModelView recipient = personMapper.execute(recipientId); for (RecipientFilter filter : filters) { if (filter.shouldFilter(recipient, notification, notifierType)) { continue eachRecipient; } } finalRecipients.add(recipientId); } return finalRecipients; } } }