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    }