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