001 /** 002 * Copyright 2005-2012 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.Notification; 020 import org.kuali.rice.ken.bo.NotificationMessageDelivery; 021 import org.kuali.rice.ken.bo.NotificationRecipient; 022 import org.kuali.rice.ken.bo.NotificationRecipientList; 023 import org.kuali.rice.ken.bo.UserChannelSubscription; 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<Notification> 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<Notification> takeAvailableWorkItems() { 084 Collection<Notification> 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(Notification 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 NotificationRecipient 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<NotificationRecipientList> i = notification.getChannel().getRecipientLists().iterator(); 120 while (i.hasNext()) { 121 NotificationRecipientList 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<UserChannelSubscription> subscriptions = notification.getChannel().getSubscriptions(); 136 for (UserChannelSubscription 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<Notification> 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 (Notification 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.setLockedDate(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(Notification 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 }