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.core.framework.persistence.dao.GenericDao;
19  import org.kuali.rice.ken.bo.NotificationBo;
20  import org.kuali.rice.ken.bo.NotificationMessageDelivery;
21  import org.kuali.rice.ken.bo.NotificationRecipientBo;
22  import org.kuali.rice.ken.bo.NotificationRecipientListBo;
23  import org.kuali.rice.ken.bo.UserChannelSubscriptionBo;
24  import org.kuali.rice.ken.deliverer.impl.KEWActionListMessageDeliverer;
25  import org.kuali.rice.ken.exception.NotificationMessageDeliveryException;
26  import org.kuali.rice.ken.service.NotificationMessageDeliveryResolverService;
27  import org.kuali.rice.ken.service.NotificationRecipientService;
28  import org.kuali.rice.ken.service.NotificationService;
29  import org.kuali.rice.ken.service.ProcessingResult;
30  import org.kuali.rice.ken.service.UserPreferenceService;
31  import org.kuali.rice.ken.util.NotificationConstants;
32  import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
33  import org.kuali.rice.kim.api.identity.principal.Principal;
34  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
35  import org.springframework.transaction.PlatformTransactionManager;
36  
37  import java.sql.Timestamp;
38  import java.util.ArrayList;
39  import java.util.Collection;
40  import java.util.HashSet;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.concurrent.ExecutorService;
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 GenericDao businessObjectDao;
57      private UserPreferenceService userPreferenceService;
58      private NotificationService notificationService;
59  
60      /**
61       * Constructs a NotificationMessageDeliveryDispatchServiceImpl instance.
62       * @param notificationRecipientService
63       * @param businessObjectDao
64       * @param txManager
65       * @param executor
66       * @param userPreferenceService
67       */
68      public NotificationMessageDeliveryResolverServiceImpl(NotificationService notificationService, NotificationRecipientService notificationRecipientService,
69              GenericDao businessObjectDao, PlatformTransactionManager txManager, ExecutorService executor,
70  	    UserPreferenceService userPreferenceService) {
71          super(txManager, executor);
72          this.notificationService = notificationService;
73          this.notificationRecipientService = notificationRecipientService;
74          this.businessObjectDao = businessObjectDao;
75          this.userPreferenceService = userPreferenceService;
76      }
77  
78      /**
79       * Obtains and marks as taken all unresolved (and untaken) notifications
80       * @return a collection of available Notifications to process
81       */
82      @Override
83      protected Collection<NotificationBo> takeAvailableWorkItems() {
84          Collection<NotificationBo> nots = notificationService.takeNotificationsForResolution();
85          //LOG.debug("Took " + nots.size() + " notifications");
86          
87          //for (Notification not: nots) {
88          //   LOG.debug("Took notification: " + not.getId() + " " + not.getTitle());
89          //}
90          return nots;
91      }
92  
93  
94      /**
95       * This method is responsible for building out the complete recipient list, which will resolve all members for groups, and add
96       * them to the official list only if they are not already in the list.
97       * @param notification
98       * @return HashSet<String>
99       */
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 }