View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.ken.service.impl;
17  
18  import org.kuali.rice.ken.bo.NotificationBo;
19  import org.kuali.rice.ken.bo.NotificationMessageDelivery;
20  import org.kuali.rice.ken.bo.NotificationRecipientBo;
21  import org.kuali.rice.ken.bo.NotificationRecipientListBo;
22  import org.kuali.rice.ken.bo.UserChannelSubscriptionBo;
23  import org.kuali.rice.ken.deliverer.impl.KEWActionListMessageDeliverer;
24  import org.kuali.rice.ken.exception.NotificationMessageDeliveryException;
25  import org.kuali.rice.ken.service.NotificationMessageDeliveryResolverService;
26  import org.kuali.rice.ken.service.NotificationRecipientService;
27  import org.kuali.rice.ken.service.NotificationService;
28  import org.kuali.rice.ken.service.ProcessingResult;
29  import org.kuali.rice.ken.util.NotificationConstants;
30  import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
31  import org.kuali.rice.kim.api.identity.principal.Principal;
32  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
33  import org.kuali.rice.krad.data.DataObjectService;
34  import org.springframework.transaction.PlatformTransactionManager;
35  
36  import java.sql.Timestamp;
37  import java.util.ArrayList;
38  import java.util.Collection;
39  import java.util.HashSet;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.concurrent.ExecutorService;
43  
44  
45  /**
46   * This is the default out-of-the-box implementation that leverages the status flag on a notification (RESOLVED versus UNRESOLVED) to determine whether
47   * the notification's message deliveries need to be resolved or not.  This also looks at the start and auto remove
48   * dates and times.
49   * @author Kuali Rice Team (rice.collab@kuali.org)
50   */
51  public class NotificationMessageDeliveryResolverServiceImpl extends ConcurrentJob<NotificationBo> implements NotificationMessageDeliveryResolverService {
52      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
53  	.getLogger(NotificationMessageDeliveryResolverServiceImpl.class);
54  
55      private NotificationRecipientService notificationRecipientService;
56      private DataObjectService dataObjectService;
57      private NotificationService notificationService;
58  
59      /**
60       * Constructs a NotificationMessageDeliveryDispatchServiceImpl instance.
61       * @param notificationRecipientService
62       * @param dataObjectService
63       * @param txManager
64       * @param executor
65       */
66      public NotificationMessageDeliveryResolverServiceImpl(NotificationService notificationService, NotificationRecipientService notificationRecipientService,
67              DataObjectService dataObjectService, PlatformTransactionManager txManager, ExecutorService executor) {
68          super(txManager, executor);
69          this.notificationService = notificationService;
70          this.notificationRecipientService = notificationRecipientService;
71          this.dataObjectService = dataObjectService;
72      }
73  
74      /**
75       * Obtains and marks as taken all unresolved (and untaken) notifications
76       * @return a collection of available Notifications to process
77       */
78      @Override
79      protected Collection<NotificationBo> takeAvailableWorkItems() {
80          Collection<NotificationBo> nots = notificationService.takeNotificationsForResolution();
81          //LOG.debug("Took " + nots.size() + " notifications");
82          
83          //for (Notification not: nots) {
84          //   LOG.debug("Took notification: " + not.getId() + " " + not.getTitle());
85          //}
86          return nots;
87      }
88  
89  
90      /**
91       * This method is responsible for building out the complete recipient list, which will resolve all members for groups, and add
92       * them to the official list only if they are not already in the list.
93       * @param notification
94       * @return HashSet<String>
95       */
96      private HashSet<String> buildCompleteRecipientList(NotificationBo notification) {
97          HashSet<String> completeRecipientList = new HashSet<String>(notification.getRecipients().size());
98  
99          // 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 }