001 /**
002 * Copyright 2005-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.ken.service.impl;
017
018 import org.kuali.rice.core.framework.persistence.dao.GenericDao;
019 import org.kuali.rice.ken.bo.NotificationBo;
020 import org.kuali.rice.ken.bo.NotificationMessageDelivery;
021 import org.kuali.rice.ken.bo.NotificationRecipientBo;
022 import org.kuali.rice.ken.bo.NotificationRecipientListBo;
023 import org.kuali.rice.ken.bo.UserChannelSubscriptionBo;
024 import org.kuali.rice.ken.deliverer.impl.KEWActionListMessageDeliverer;
025 import org.kuali.rice.ken.exception.NotificationMessageDeliveryException;
026 import org.kuali.rice.ken.service.NotificationMessageDeliveryResolverService;
027 import org.kuali.rice.ken.service.NotificationRecipientService;
028 import org.kuali.rice.ken.service.NotificationService;
029 import org.kuali.rice.ken.service.ProcessingResult;
030 import org.kuali.rice.ken.service.UserPreferenceService;
031 import org.kuali.rice.ken.util.NotificationConstants;
032 import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
033 import org.kuali.rice.kim.api.identity.principal.Principal;
034 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
035 import org.springframework.transaction.PlatformTransactionManager;
036
037 import java.sql.Timestamp;
038 import java.util.ArrayList;
039 import java.util.Collection;
040 import java.util.HashSet;
041 import java.util.Iterator;
042 import java.util.List;
043 import java.util.concurrent.ExecutorService;
044
045 /**
046 * This is the default out-of-the-box implementation that leverages the status flag on a notification (RESOLVED versus UNRESOLVED) to determine whether
047 * the notification's message deliveries need to be resolved or not. This also looks at the start and auto remove
048 * dates and times.
049 * @author Kuali Rice Team (rice.collab@kuali.org)
050 */
051 public class NotificationMessageDeliveryResolverServiceImpl extends ConcurrentJob<NotificationBo> implements NotificationMessageDeliveryResolverService {
052 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
053 .getLogger(NotificationMessageDeliveryResolverServiceImpl.class);
054
055 private NotificationRecipientService notificationRecipientService;
056 private GenericDao businessObjectDao;
057 private UserPreferenceService userPreferenceService;
058 private NotificationService notificationService;
059
060 /**
061 * Constructs a NotificationMessageDeliveryDispatchServiceImpl instance.
062 * @param notificationRecipientService
063 * @param businessObjectDao
064 * @param txManager
065 * @param executor
066 * @param userPreferenceService
067 */
068 public NotificationMessageDeliveryResolverServiceImpl(NotificationService notificationService, NotificationRecipientService notificationRecipientService,
069 GenericDao businessObjectDao, PlatformTransactionManager txManager, ExecutorService executor,
070 UserPreferenceService userPreferenceService) {
071 super(txManager, executor);
072 this.notificationService = notificationService;
073 this.notificationRecipientService = notificationRecipientService;
074 this.businessObjectDao = businessObjectDao;
075 this.userPreferenceService = userPreferenceService;
076 }
077
078 /**
079 * Obtains and marks as taken all unresolved (and untaken) notifications
080 * @return a collection of available Notifications to process
081 */
082 @Override
083 protected Collection<NotificationBo> takeAvailableWorkItems() {
084 Collection<NotificationBo> nots = notificationService.takeNotificationsForResolution();
085 //LOG.debug("Took " + nots.size() + " notifications");
086
087 //for (Notification not: nots) {
088 // LOG.debug("Took notification: " + not.getId() + " " + not.getTitle());
089 //}
090 return nots;
091 }
092
093
094 /**
095 * This method is responsible for building out the complete recipient list, which will resolve all members for groups, and add
096 * them to the official list only if they are not already in the list.
097 * @param notification
098 * @return HashSet<String>
099 */
100 private HashSet<String> buildCompleteRecipientList(NotificationBo notification) {
101 HashSet<String> completeRecipientList = new HashSet<String>(notification.getRecipients().size());
102
103 // process the list that came in with the notification request
104 for (int i = 0; i < notification.getRecipients().size(); i++) {
105 NotificationRecipientBo recipient = notification.getRecipient(i);
106 if (KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode().equals(recipient.getRecipientType())) {
107 // resolve group's users
108 String[] groupMembers = notificationRecipientService.getGroupMembers(recipient.getRecipientId());
109 for(int j = 0; j < groupMembers.length; j++) {
110 completeRecipientList.add(groupMembers[j]);
111 }
112 } else { // just a user, so add to the list
113 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(recipient.getRecipientId());
114 completeRecipientList.add(principal.getPrincipalId());
115 }
116 }
117
118 // now process the default recipient lists that are associated with the channel
119 Iterator<NotificationRecipientListBo> i = notification.getChannel().getRecipientLists().iterator();
120 while (i.hasNext()) {
121 NotificationRecipientListBo listRecipient = i.next();
122 if (KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode().equals(listRecipient.getRecipientType())) {
123 // resolve group's users
124 String[] groupMembers = notificationRecipientService.getGroupMembers(listRecipient.getRecipientId());
125 for (int j = 0; j < groupMembers.length; j++) {
126 completeRecipientList.add(groupMembers[j]);
127 }
128 } else { // just a user, so add to the list
129 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(listRecipient.getRecipientId());
130 completeRecipientList.add(principal.getPrincipalId());
131 }
132 }
133
134 // now process the subscribers that are associated with the channel
135 List<UserChannelSubscriptionBo> subscriptions = notification.getChannel().getSubscriptions();
136 for (UserChannelSubscriptionBo subscription: subscriptions) {
137 // NOTE: at this time channel subscriptions are USER-only - GROUP is not supported
138 // this could be implemented by adding a recipientType/userType column as we do in
139 // other recipient/user-related tables/BOs
140 completeRecipientList.add(subscription.getUserId());
141 }
142
143 return completeRecipientList;
144 }
145
146 /**
147 * Generates all message deliveries for a given notification and save thems to the database.
148 * Updates each Notification record to indicate it has been resolved.
149 * Should be performed within a separate transaction
150 * @param notification the Notification for which to generate message deliveries
151 * @return a count of the number of message deliveries generated
152 */
153 /* Perform within transaction */
154 @Override
155 protected Collection<Object> processWorkItems(Collection<NotificationBo> notifications) {
156 List<Object> successes = new ArrayList<Object>();
157
158 // because this concurrent job does not performed grouping of work items, there should only
159 // ever be one notification object per work unit anyway...
160 for (NotificationBo notification: notifications) {
161 // now figure out each unique recipient for this notification
162 HashSet<String> uniqueRecipients = buildCompleteRecipientList(notification);
163
164 // now for each unique recipient, figure out each delivery end point and create a NotificationMessageDelivery record
165 Iterator<String> j = uniqueRecipients.iterator();
166 while(j.hasNext()) {
167 String userRecipientId = j.next();
168
169 NotificationMessageDelivery defaultMessageDelivery = new NotificationMessageDelivery();
170 defaultMessageDelivery.setMessageDeliveryStatus(NotificationConstants.MESSAGE_DELIVERY_STATUS.UNDELIVERED);
171 defaultMessageDelivery.setNotification(notification);
172 defaultMessageDelivery.setUserRecipientId(userRecipientId);
173
174 //now save that delivery end point; this record will be later processed by the dispatch service which will actually deliver it
175 businessObjectDao.save(defaultMessageDelivery);
176
177 try {
178 new KEWActionListMessageDeliverer().deliverMessage(defaultMessageDelivery);
179 } catch (NotificationMessageDeliveryException e) {
180 throw new RuntimeException(e);
181 }
182
183 // we have no delivery stage any more, anything we send to KCB needs to be considered "delivered" from
184 // the perspective of KEN
185 defaultMessageDelivery.setMessageDeliveryStatus(NotificationConstants.MESSAGE_DELIVERY_STATUS.DELIVERED);
186 businessObjectDao.save(defaultMessageDelivery);
187
188 successes.add(defaultMessageDelivery);
189
190 // also, update the status of the notification so that it's message deliveries are not resolved again
191 notification.setProcessingFlag(NotificationConstants.PROCESSING_FLAGS.RESOLVED);
192 // unlock the record now
193 notification.setLockedDateValue(null);
194 businessObjectDao.save(notification);
195 }
196
197 }
198
199 return successes;
200 }
201
202 /**
203 * @see org.kuali.rice.ken.service.impl.ConcurrentJob#unlockWorkItem(java.lang.Object)
204 */
205 @Override
206 protected void unlockWorkItem(NotificationBo notification) {
207 LOG.debug("Unlocking notification: " + notification.getId() + " " + notification.getTitle());
208 notificationService.unlockNotification(notification);
209 }
210
211 /**
212 * This method is responsible for resolving the list of NotificationMessageDelivery records for a given notification. This service will look
213 * at all notifications that are ready to be delivered and will "explode" out specific message delivery records for given delivery end points.
214 * @see org.kuali.rice.ken.service.NotificationMessageDeliveryResolverService#resolveNotificationMessageDeliveries()
215 */
216 public ProcessingResult resolveNotificationMessageDeliveries() {
217 LOG.debug("[" + new Timestamp(System.currentTimeMillis()).toString() + "] STARTING RESOLUTION OF NOTIFICATION MESSAGE DELIVERIES");
218
219 ProcessingResult result = run();
220
221 LOG.debug("[" + new Timestamp(System.currentTimeMillis()).toString() + "] FINISHED RESOLUTION OF NOTIFICATION MESSAGE DELIVERIES - " +
222 "Message Delivery End Points Resolved = " + result.getSuccesses().size());
223
224 return result;
225 }
226 }