/* * Copyright (c) 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.notifier; import java.io.StringWriter; import java.util.Collection; import java.util.Date; import java.util.Map; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; import org.eurekastreams.commons.exceptions.OutOfDateObjectException; import org.eurekastreams.commons.server.UserActionRequest; import org.eurekastreams.server.action.execution.notification.NotificationPropertyKeys; import org.eurekastreams.server.domain.Identifiable; import org.eurekastreams.server.domain.InAppNotificationEntity; import org.eurekastreams.server.domain.NotificationType; import org.eurekastreams.server.domain.Person; import org.eurekastreams.server.domain.UnreadInAppNotificationCountDTO; import org.eurekastreams.server.persistence.mappers.DomainMapper; import org.eurekastreams.server.persistence.mappers.requests.PersistenceRequest; import org.eurekastreams.server.search.modelview.PersonModelView; /** * Notifier for in-app notifications. Builds the messages and stores them in the database. */ public class InAppNotificationNotifier implements Notifier { /** Apache Velocity templating engine. */ private final VelocityEngine velocityEngine; /** * Global context for Apache Velocity templating engine. (Holds system-wide properties.) */ private final Context velocityGlobalContext; /** Message templates by notification type. */ private final Map<NotificationType, String> templates; /** Aggregate message templates by notification type. */ private final Map<NotificationType, String> aggregateTemplates; /** Mapper to persist the notification. */ private final DomainMapper<PersistenceRequest<InAppNotificationEntity>, Boolean> insertMapper; /** Mapper to update aggregate notifications. */ private final DomainMapper<PersistenceRequest<InAppNotificationEntity>, Boolean> updateMapper; /** Mapper to sync unread alert count in cache. */ private final DomainMapper<Long, UnreadInAppNotificationCountDTO> syncMapper; /** Provides a dummy person object for persisting the in-app entity. */ private final DomainMapper<Long, Person> placeholderPersonMapper; /** Looks up existing notifications for aggregation. */ private final DomainMapper<InAppNotificationEntity, InAppNotificationEntity> existingNotificationMapper; /** * Constructor. * * @param inVelocityEngine * Apache Velocity templating engine. * @param inVelocityGlobalContext * Global context for Apache Velocity templating engine. * @param inTemplates * Message templates by notification type. * @param inAggregateTemplates * Aggregate message templates by notification type. * @param inInsertMapper * Mapper to persist the notification. * @param inUpdateMapper * Mapper to update existing notifications. * @param inSyncMapper * Mapper to sync unread alert count in cache. * @param inPlaceholderPersonMapper * Provides a dummy person object for persisting the in-app entity. * @param inExistingNotificationMapper * Mapper to search for existing notifications that can be aggregated with the current notification */ public InAppNotificationNotifier(final VelocityEngine inVelocityEngine, final Context inVelocityGlobalContext, final Map<NotificationType, String> inTemplates, final Map<NotificationType, String> inAggregateTemplates, final DomainMapper<PersistenceRequest<InAppNotificationEntity>, Boolean> inInsertMapper, final DomainMapper<PersistenceRequest<InAppNotificationEntity>, Boolean> inUpdateMapper, final DomainMapper<Long, UnreadInAppNotificationCountDTO> inSyncMapper, final DomainMapper<Long, Person> inPlaceholderPersonMapper, final DomainMapper<InAppNotificationEntity, InAppNotificationEntity> inExistingNotificationMapper) { velocityEngine = inVelocityEngine; velocityGlobalContext = inVelocityGlobalContext; templates = inTemplates; aggregateTemplates = inAggregateTemplates; insertMapper = inInsertMapper; updateMapper = inUpdateMapper; syncMapper = inSyncMapper; placeholderPersonMapper = inPlaceholderPersonMapper; existingNotificationMapper = inExistingNotificationMapper; } /** * {@inheritDoc} */ @Override public Collection<UserActionRequest> notify(final NotificationType inType, final Collection<Long> inRecipients, final Map<String, Object> inProperties, final Map<Long, PersonModelView> inRecipientIndex) throws Exception { Context velocityContext = new VelocityContext(new VelocityContext(inProperties, velocityGlobalContext)); velocityContext.put("context", velocityContext); velocityContext.put("type", inType); for (long recipientId : inRecipients) { Person recipient = placeholderPersonMapper.execute(recipientId); if (recipient == null) { continue; } if (aggregateTemplates.containsKey(inType)) { updateAggregateNotification(recipient, inType, inProperties, velocityContext); } else { createNewNotification(recipient, inType, inProperties, velocityContext); } } return null; } /** * Handles aggregated notification types. Checks for an existing notification of the same type to the same * recipient. If it finds an existing notification, it increments its count. Otherwise, it creates a new * notification. * * @param recipient * The person to notify * @param inType * The type of the notification * @param inProperties * Additional info about the notification * @param velocityContext * Velocity context used to generate notification message */ private void updateAggregateNotification(final Person recipient, final NotificationType inType, final Map<String, Object> inProperties, final Context velocityContext) { InAppNotificationEntity searchCriteria = new InAppNotificationEntity(); searchCriteria.setRecipient(recipient); searchCriteria.setNotificationType(inType); searchCriteria.setUrl((String) inProperties.get(NotificationPropertyKeys.URL)); InAppNotificationEntity existingAggregateNotification = existingNotificationMapper.execute(searchCriteria); if (existingAggregateNotification == null) { createNewNotification(recipient, inType, inProperties, velocityContext); return; } int newAggregationCount = existingAggregateNotification.getAggregationCount() + 1; existingAggregateNotification.setAggregationCount(newAggregationCount); existingAggregateNotification.setNotificationDate(new Date()); String template = aggregateTemplates.get(inType); if (template == null) { return; } velocityContext.put("recipient", recipient); velocityContext.put("aggregationCount", newAggregationCount); StringWriter writer = new StringWriter(); velocityEngine.evaluate(velocityContext, writer, "InAppNotification-" + inType, template); String message = writer.toString(); existingAggregateNotification.setMessage(message); try { updateMapper.execute(new PersistenceRequest<InAppNotificationEntity>(existingAggregateNotification)); } catch (OutOfDateObjectException e) { updateAggregateNotification(recipient, inType, inProperties, velocityContext); } syncMapper.execute(recipient.getId()); } /** * Creates a new notification. * * @param recipient * The person to notify * @param inType * The type of the notification * @param inProperties * Additional info about the notification * @param velocityContext * Velocity context used to generate notification message */ private void createNewNotification(final Person recipient, final NotificationType inType, final Map<String, Object> inProperties, final Context velocityContext) { InAppNotificationEntity dbNotif = new InAppNotificationEntity(); dbNotif.setNotificationType(inType); dbNotif.setRecipient(recipient); dbNotif.setUrl((String) inProperties.get(NotificationPropertyKeys.URL)); dbNotif.setHighPriority(Boolean.TRUE.equals(inProperties.get(NotificationPropertyKeys.HIGH_PRIORITY))); Object obj = inProperties.get(NotificationPropertyKeys.SOURCE); if (obj instanceof Identifiable) { Identifiable source = (Identifiable) obj; dbNotif.setSourceType(source.getEntityType()); dbNotif.setSourceUniqueId(source.getUniqueId()); dbNotif.setSourceName(source.getDisplayName()); } obj = inProperties.get(NotificationPropertyKeys.ACTOR); if (obj instanceof Identifiable) { Identifiable actor = (Identifiable) obj; dbNotif.setAvatarOwnerType(actor.getEntityType()); dbNotif.setAvatarOwnerUniqueId(actor.getUniqueId()); } String template = templates.get(inType); if (template == null) { return; } velocityContext.put("recipient", recipient); StringWriter writer = new StringWriter(); velocityEngine.evaluate(velocityContext, writer, "InAppNotification-" + inType, template); String message = writer.toString(); dbNotif.setMessage(message); insertMapper.execute(new PersistenceRequest<InAppNotificationEntity>(dbNotif)); syncMapper.execute(recipient.getId()); } }