001/**
002 * Copyright 2005-2016 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.core.framework.persistence.dao.GenericDao;
019import org.kuali.rice.ken.bo.NotificationBo;
020import org.kuali.rice.ken.bo.NotificationMessageDelivery;
021import org.kuali.rice.ken.bo.NotificationRecipientBo;
022import org.kuali.rice.ken.bo.NotificationRecipientListBo;
023import org.kuali.rice.ken.bo.UserChannelSubscriptionBo;
024import org.kuali.rice.ken.deliverer.impl.KEWActionListMessageDeliverer;
025import org.kuali.rice.ken.exception.NotificationMessageDeliveryException;
026import org.kuali.rice.ken.service.NotificationMessageDeliveryResolverService;
027import org.kuali.rice.ken.service.NotificationRecipientService;
028import org.kuali.rice.ken.service.NotificationService;
029import org.kuali.rice.ken.service.ProcessingResult;
030import org.kuali.rice.ken.service.UserPreferenceService;
031import org.kuali.rice.ken.util.NotificationConstants;
032import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
033import org.kuali.rice.kim.api.identity.principal.Principal;
034import org.kuali.rice.kim.api.services.KimApiServiceLocator;
035import org.springframework.transaction.PlatformTransactionManager;
036
037import java.sql.Timestamp;
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.HashSet;
041import java.util.Iterator;
042import java.util.List;
043import 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 */
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 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<NotificationBo> takeAvailableWorkItems() {
084        Collection<NotificationBo> 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(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}