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.web.spring;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.criteria.QueryByCriteria;
21  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
22  import org.kuali.rice.core.framework.persistence.dao.GenericDao;
23  import org.kuali.rice.coreservice.api.namespace.Namespace;
24  import org.kuali.rice.coreservice.api.namespace.NamespaceService;
25  import org.kuali.rice.ken.bo.NotificationBo;
26  import org.kuali.rice.ken.bo.NotificationChannelBo;
27  import org.kuali.rice.ken.bo.NotificationChannelReviewerBo;
28  import org.kuali.rice.ken.bo.NotificationPriorityBo;
29  import org.kuali.rice.ken.bo.NotificationProducerBo;
30  import org.kuali.rice.ken.bo.NotificationRecipientBo;
31  import org.kuali.rice.ken.bo.NotificationSenderBo;
32  import org.kuali.rice.ken.document.kew.NotificationWorkflowDocument;
33  import org.kuali.rice.ken.exception.ErrorList;
34  import org.kuali.rice.ken.service.NotificationChannelService;
35  import org.kuali.rice.ken.service.NotificationMessageContentService;
36  import org.kuali.rice.ken.service.NotificationRecipientService;
37  import org.kuali.rice.ken.service.NotificationService;
38  import org.kuali.rice.ken.service.NotificationWorkflowDocumentService;
39  import org.kuali.rice.ken.util.NotificationConstants;
40  import org.kuali.rice.ken.util.Util;
41  import org.kuali.rice.kew.api.WorkflowDocument;
42  import org.kuali.rice.kew.rule.GenericAttributeContent;
43  import org.kuali.rice.kim.api.KimConstants;
44  import org.kuali.rice.kim.api.group.Group;
45  import org.kuali.rice.kim.api.group.GroupService;
46  import org.kuali.rice.kim.api.identity.IdentityService;
47  import org.kuali.rice.kim.api.identity.principal.Principal;
48  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
49  import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
50  import org.kuali.rice.krad.data.DataObjectService;
51  import org.springframework.web.servlet.ModelAndView;
52  import org.springframework.web.servlet.mvc.multiaction.MultiActionController;
53  
54  import javax.servlet.ServletException;
55  import javax.servlet.http.HttpServletRequest;
56  import java.io.IOException;
57  import java.sql.Timestamp;
58  import java.text.ParseException;
59  import java.util.ArrayList;
60  import java.util.Date;
61  import java.util.HashMap;
62  import java.util.List;
63  import java.util.Map;
64  
65  /**
66   * Base class for KEN controllers for sending notifications
67   *
68   * @author Kuali Rice Team (rice.collab@kuali.org)
69   *
70   */
71  public class BaseSendNotificationController extends MultiActionController {
72      private static final Logger LOG = Logger.getLogger(BaseSendNotificationController.class);
73  
74      private static final String USER_RECIPS_PARAM = "userRecipients";
75      private static final String WORKGROUP_RECIPS_PARAM = "workgroupRecipients";
76      private static final String WORKGROUP_NAMESPACE_CODES_PARAM = "workgroupNamespaceCodes";
77      private static final String SPLIT_REGEX = "(%2C|,)";
78  
79      private static final String NONE_CHANNEL = "___NONE___";
80      private static final long REASONABLE_IMMEDIATE_TIME_THRESHOLD = 1000 * 60 * 5; // <= 5 minutes is "immediate"
81  
82      private static IdentityService identityService;
83      private static GroupService groupService;
84      private static NamespaceService namespaceService;
85  
86      protected NotificationService notificationService;
87      protected NotificationWorkflowDocumentService notificationWorkflowDocService;
88      protected NotificationChannelService notificationChannelService;
89      protected NotificationRecipientService notificationRecipientService;
90      protected NotificationMessageContentService notificationMessageContentService;
91      protected DataObjectService dataObjectService;
92  
93      protected static IdentityService getIdentityService() {
94          if ( identityService == null ) {
95              identityService = KimApiServiceLocator.getIdentityService();
96          }
97          return identityService;
98      }
99  
100     protected static GroupService getGroupService() {
101         if ( groupService == null ) {
102             groupService = KimApiServiceLocator.getGroupService();
103         }
104         return groupService;
105     }
106 
107     protected static NamespaceService getNamespaceService() {
108         if ( namespaceService == null ) {
109             namespaceService = CoreServiceApiServiceLocator.getNamespaceService();
110         }
111         return namespaceService;
112     }
113 
114     /**
115      * Sets the {@link NotificationService}.
116      *
117      * @param notificationService the service to set
118      */
119     public void setNotificationService(NotificationService notificationService) {
120         this.notificationService = notificationService;
121     }
122 
123     /**
124      * Sets the {@link NotificationWorkflowDocumentService}.
125      *
126      * @param notificationWorkflowDocService the service to set
127      */
128     public void setNotificationWorkflowDocumentService(NotificationWorkflowDocumentService notificationWorkflowDocService) {
129         this.notificationWorkflowDocService = notificationWorkflowDocService;
130     }
131 
132     /**
133      * Sets the {@link NotificationChannelService}.
134      *
135      * @param notificationChannelService the service to set
136      */
137     public void setNotificationChannelService(NotificationChannelService notificationChannelService) {
138         this.notificationChannelService = notificationChannelService;
139     }
140 
141     /**
142      * Sets the {@link NotificationRecipientService}.
143      *
144      * @param notificationRecipientService the service to set
145      */
146     public void setNotificationRecipientService(NotificationRecipientService notificationRecipientService) {
147         this.notificationRecipientService = notificationRecipientService;
148     }
149 
150     /**
151      * Sets the {@link NotificationMessageContentService}.
152      *
153      * @param notificationMessageContentService the service to set
154      */
155     public void setNotificationMessageContentService(NotificationMessageContentService notificationMessageContentService) {
156         this.notificationMessageContentService = notificationMessageContentService;
157     }
158 
159     /**
160      * Sets the businessObjectDao attribute value.
161      * @param dataObjectService the service to set
162      */
163     public void setDataObjectService(DataObjectService dataObjectService) {
164         this.dataObjectService = dataObjectService;
165     }
166 
167 
168     protected String getParameter(HttpServletRequest request, String parameterName, Map<String, Object> model, ErrorList errors, String errorMessage) {
169         String parameter = request.getParameter(parameterName);
170 
171         if (StringUtils.isNotEmpty(parameter)) {
172             model.put(parameterName, parameter);
173         } else {
174             errors.addError(errorMessage);
175         }
176 
177         return parameter;
178     }
179 
180     protected String getParameter(HttpServletRequest request, String parameterName, Map<String, Object> model, ErrorList errors, String errorMessage, String defaultValue) {
181         String parameter = StringUtils.defaultIfBlank(request.getParameter(parameterName), defaultValue);
182 
183         if (StringUtils.isNotEmpty(parameter)) {
184             model.put(parameterName, parameter);
185         } else {
186             errors.addError(errorMessage);
187         }
188 
189         return parameter;
190     }
191 
192     protected String[] getParameterList(HttpServletRequest request, String parameterName, Map<String, Object> model, ErrorList errors, String errorMessage) {
193         String parameter = request.getParameter(parameterName);
194         String[] senders = null;
195 
196         if (StringUtils.isNotEmpty(parameter)) {
197             senders = StringUtils.split(parameter, ",");
198             model.put(parameterName, parameter);
199         } else {
200             errors.addError(errorMessage);
201         }
202 
203         return senders;
204     }
205 
206     protected Date getDate(String parameter, ErrorList errors, String errorMessage) {
207         Date date = null;
208 
209         try {
210             date = Util.parseUIDateTime(parameter);
211         } catch (ParseException pe) {
212             errors.addError(errorMessage);
213         }
214 
215         return date;
216     }
217     
218     protected String[] parseUserRecipients(HttpServletRequest request) {
219         return parseCommaSeparatedValues(request, USER_RECIPS_PARAM);
220     }
221 
222     protected String[] parseWorkgroupRecipients(HttpServletRequest request) {
223         return parseCommaSeparatedValues(request, WORKGROUP_RECIPS_PARAM);
224     }
225 
226     protected String[] parseWorkgroupNamespaceCodes(HttpServletRequest request) {
227     	return parseCommaSeparatedValues(request, WORKGROUP_NAMESPACE_CODES_PARAM);
228     }
229     
230     protected String[] parseCommaSeparatedValues(HttpServletRequest request, String param) {
231         String vals = request.getParameter(param);
232         if (vals != null) {
233             String[] split = vals.split(SPLIT_REGEX);
234             List<String> strs = new ArrayList<String>();
235             for (String component: split) {
236                 if (StringUtils.isNotBlank(component)) {
237                     strs.add(component.trim());
238                 }
239             }
240             return strs.toArray(new String[strs.size()]);
241         } else {
242             return new String[0];
243         }
244     }
245 
246     protected boolean isUserRecipientValid(String user, ErrorList errors) {
247         boolean valid = true;
248         Principal principal = getIdentityService().getPrincipalByPrincipalName(user);
249         if (principal == null) {
250         	valid = false;
251         	errors.addError("'" + user + "' is not a valid principal name");
252         }
253 
254         return valid;
255     }
256 
257     protected boolean isWorkgroupRecipientValid(String groupName, String namespaceCode, ErrorList errors) {
258     	Namespace nSpace = getNamespaceService().getNamespace(namespaceCode);
259     	if (nSpace == null) {
260     		errors.addError((new StringBuilder()).append('\'').append(namespaceCode).append("' is not a valid namespace code").toString());
261     		return false;
262     	} else {
263     		Group i = getGroupService().getGroupByNamespaceCodeAndName(namespaceCode, groupName);
264        		if (i == null) {
265        			errors.addError((new StringBuilder()).append('\'').append(groupName).append(
266        					"' is not a valid group name for namespace code '").append(namespaceCode).append('\'').toString());
267        			return false;
268        		} else {
269        			return true;
270        		}
271     	}
272     }
273     protected String getPrincipalIdFromIdOrName(String principalIdOrName) {
274         Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(principalIdOrName);
275         if (principal == null) {
276             principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalIdOrName);
277         }
278         if (principal == null) {
279             throw new RiceIllegalArgumentException("Could not locate a principal as initiator with the given remoteUser of " + principalIdOrName);
280         }
281         return principal.getPrincipalId();
282     }
283 
284     /**
285      * Submits the actual event notification message.
286      *
287      * @param request the servlet request
288      * @param routeMessage the message to attach to the route action
289      * @param viewName the name of the view to forward to after completion
290      *
291      * @return the next view to show
292      * @throws javax.servlet.ServletException
293      * @throws java.io.IOException
294      */
295     protected ModelAndView submitNotificationMessage(HttpServletRequest request, String routeMessage, String viewName)
296             throws ServletException, IOException {
297         LOG.debug("remoteUser: " + request.getRemoteUser());
298 
299         // obtain a workflow user object first
300         //WorkflowIdDTO initiator = new WorkflowIdDTO(request.getRemoteUser());
301         String initiatorId = getPrincipalIdFromIdOrName( request.getRemoteUser());
302         LOG.debug("initiatorId: " + initiatorId);
303 
304         // now construct the workflow document, which will interact with workflow
305         Map<String, Object> model = new HashMap<String, Object>();
306 
307         try {
308             WorkflowDocument document = createNotificationWorkflowDocument(request, initiatorId, model);
309 
310             document.route(routeMessage + initiatorId);
311 
312             // This ain't pretty, but it gets the job done for now.
313             ErrorList el = new ErrorList();
314             el.addError("Notification(s) sent.");
315             model.put("errors", el);
316         } catch (ErrorList el) {
317             // route back to the send form again
318             Map<String, Object> model2 = setupModelForSendNotification(request);
319             model.putAll(model2);
320             model.put("errors", el);
321         } catch (Exception e) {
322             throw new RuntimeException(e);
323         }
324 
325         return new ModelAndView(viewName, model);
326     }
327 
328     /**
329      * Creates a notification {@link WorkflowDocument}.
330      *
331      * @param request the servlet request
332      * @param initiatorId the user sending the notification
333      * @param model the Spring MVC model
334      *
335      * @return a {@link WorkflowDocument} for the notification
336      * @throws java.lang.IllegalArgumentException
337      * @throws org.kuali.rice.ken.exception.ErrorList
338      */
339     protected WorkflowDocument createNotificationWorkflowDocument(HttpServletRequest request, String initiatorId,
340             Map<String, Object> model) throws IllegalArgumentException, ErrorList {
341         WorkflowDocument document = NotificationWorkflowDocument.createNotificationDocument(initiatorId,
342                 NotificationConstants.KEW_CONSTANTS.SEND_NOTIFICATION_REQ_DOC_TYPE);
343 
344         //parse out the application content into a Notification BO
345         NotificationBo notification = populateNotificationInstance(request, model);
346 
347         // now get that content in an understandable XML format and pass into document
348         String notificationAsXml = notificationMessageContentService.generateNotificationMessage(notification);
349 
350         Map<String, String> attrFields = new HashMap<String,String>();
351         List<NotificationChannelReviewerBo> reviewers = notification.getChannel().getReviewers();
352         int ui = 0;
353         int gi = 0;
354         for (NotificationChannelReviewerBo reviewer: reviewers) {
355             String prefix;
356             int index;
357             if (KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode().equals(reviewer.getReviewerType())) {
358                 prefix = "user";
359                 index = ui;
360                 ui++;
361             } else if (KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode().equals(reviewer.getReviewerType())) {
362                 prefix = "group";
363                 index = gi;
364                 gi++;
365             } else {
366                 LOG.error("Invalid type for reviewer " + reviewer.getReviewerId() + ": " + reviewer.getReviewerType());
367                 continue;
368             }
369             attrFields.put(prefix + index, reviewer.getReviewerId());
370         }
371         GenericAttributeContent gac = new GenericAttributeContent("channelReviewers");
372         document.setApplicationContent(notificationAsXml);
373         document.setAttributeContent("<attributeContent>" + gac.generateContent(attrFields) + "</attributeContent>");
374 
375         document.setTitle(notification.getTitle());
376 
377         return document;
378     }
379 
380     /**
381      * Creates a new {@link NotificationBo} instance.
382      *
383      * @param request the servlet request
384      * @param model the Spring MVC model
385      *
386      * @return a new notification
387      * @throws java.lang.IllegalArgumentException
388      * @throws org.kuali.rice.ken.exception.ErrorList
389      */
390     protected NotificationBo populateNotificationInstance(HttpServletRequest request, Map<String, Object> model)
391             throws IllegalArgumentException, ErrorList {
392         return createNotification(request, model, new ErrorList());
393     }
394 
395     /**
396      * Provides an overridable method in which to customize a created {@link NotificationBo} instance.
397      *
398      * @param request the servlet request
399      * @param model the Spring MVC model
400      * @param errors the error list
401      *
402      * @return a new notification
403      * @throws ErrorList
404      */
405     protected NotificationBo createNotification(HttpServletRequest request, Map<String, Object> model, ErrorList errors)
406             throws ErrorList {
407         String channelName = getChannelName(request, model, errors);
408         String priorityName = getParameter(request, "priorityName", model, errors, "You must choose a priority.");
409         String[] senders = getParameterList(request, "senderNames", model, errors, "You must enter at least one sender.");
410         String deliveryType = getDeliveryType(request, model, errors);
411 
412         Date originalDate = getDate(request.getParameter("originalDateTime"), errors, "Original date is invalid.");
413 
414         String sendDateTime = StringUtils.defaultIfBlank(request.getParameter("sendDateTime"), Util.getCurrentDateTime());
415         Date sendDate = getDate(sendDateTime, errors, "You specified an invalid Send Date/Time.  Please use the calendar picker.");
416         if (sendDate != null && sendDate.before(originalDate)) {
417             errors.addError("Send Date/Time cannot be in the past.");
418         }
419         model.put("sendDateTime", sendDateTime);
420 
421         String autoRemoveDateTime = request.getParameter("autoRemoveDateTime");
422         Date removeDate = getDate(autoRemoveDateTime, errors, "You specified an invalid Auto-Remove Date/Time.  Please use the calendar picker.");
423         if (removeDate != null) {
424             if (removeDate.before(originalDate)) {
425                 errors.addError("Auto-Remove Date/Time cannot be in the past.");
426             } else if (sendDate != null && removeDate.before(sendDate)) {
427                 errors.addError("Auto-Remove Date/Time cannot be before the Send Date/Time.");
428             }
429         }
430         model.put("autoRemoveDateTime", autoRemoveDateTime);
431 
432         // user recipient names
433         String[] userRecipients = parseUserRecipients(request);
434 
435         // workgroup recipient names
436         String[] workgroupRecipients = parseWorkgroupRecipients(request);
437 
438         // workgroup namespace codes
439         String[] workgroupNamespaceCodes = parseWorkgroupNamespaceCodes(request);
440 
441         String title = getParameter(request, "title", model, errors, "You must fill in a title.");
442 
443         // check to see if there were any errors
444         if (!errors.getErrors().isEmpty()) {
445             throw errors;
446         }
447 
448         return createNotification(title, deliveryType, sendDate, removeDate, channelName, priorityName,
449                 senders, userRecipients, workgroupRecipients, workgroupNamespaceCodes, errors);
450     }
451 
452     private NotificationBo createNotification(String title, String deliveryType, Date sendDate, Date removeDate,
453             String channelName, String priorityName, String[] senders, String[] userRecipients,
454             String[] workgroupRecipients, String[] workgroupNamespaceCodes, ErrorList errors) throws ErrorList {
455         NotificationBo notification = new NotificationBo();
456         notification.setTitle(title);
457         notification.setDeliveryType(deliveryType);
458         notification.setSendDateTimeValue(new Timestamp(sendDate.getTime()));
459         notification.setAutoRemoveDateTimeValue(new Timestamp(removeDate.getTime()));
460 
461         NotificationChannelBo channel = Util.retrieveFieldReference("channel", "name", channelName,
462                 NotificationChannelBo.class, dataObjectService);
463         notification.setChannel(channel);
464 
465         NotificationPriorityBo priority = Util.retrieveFieldReference("priority", "name", priorityName,
466                 NotificationPriorityBo.class, dataObjectService);
467         notification.setPriority(priority);
468 
469         NotificationProducerBo producer = Util.retrieveFieldReference("producer", "name",
470                 NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_NAME, NotificationProducerBo.class,
471                 dataObjectService);
472         notification.setProducer(producer);
473 
474         for (String senderName : senders) {
475             if (StringUtils.isEmpty(senderName)) {
476                 errors.addError("A sender's name cannot be blank.");
477             } else {
478                 NotificationSenderBo ns = new NotificationSenderBo();
479                 ns.setSenderName(senderName.trim());
480                 notification.addSender(ns);
481             }
482         }
483 
484         if (userRecipients != null && userRecipients.length > 0) {
485             for (String userRecipientId : userRecipients) {
486                 if (isUserRecipientValid(userRecipientId, errors)) {
487                     NotificationRecipientBo recipient = new NotificationRecipientBo();
488                     recipient.setRecipientType(KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
489                     recipient.setRecipientId(userRecipientId);
490                     notification.addRecipient(recipient);
491                 }
492             }
493         }
494 
495         if (workgroupRecipients != null && workgroupRecipients.length > 0) {
496             if (workgroupNamespaceCodes != null && workgroupNamespaceCodes.length > 0) {
497                 if (workgroupNamespaceCodes.length == workgroupRecipients.length) {
498                     for (int i = 0; i < workgroupRecipients.length; i++) {
499                         if (isWorkgroupRecipientValid(workgroupRecipients[i], workgroupNamespaceCodes[i], errors)) {
500                             NotificationRecipientBo recipient = new NotificationRecipientBo();
501                             recipient.setRecipientType(KimConstants.KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode());
502                             recipient.setRecipientId(
503                                     getGroupService().getGroupByNamespaceCodeAndName(workgroupNamespaceCodes[i],
504                                             workgroupRecipients[i]).getId());
505                             notification.addRecipient(recipient);
506                         }
507                     }
508                 } else {
509                     errors.addError("The number of groups must match the number of namespace codes");
510                 }
511             } else {
512                 errors.addError("You must specify a namespace code for every group name");
513             }
514         } else if (workgroupNamespaceCodes != null && workgroupNamespaceCodes.length > 0) {
515             errors.addError("You must specify a group name for every namespace code");
516         }
517 
518         if (!recipientsExist(userRecipients, workgroupRecipients) && !hasPotentialRecipients(notification)) {
519             errors.addError("You must specify at least one user or group recipient.");
520         }
521 
522         notification.setContent(NotificationConstants.XML_MESSAGE_CONSTANTS.CONTENT_SIMPLE_OPEN
523                 + NotificationConstants.XML_MESSAGE_CONSTANTS.MESSAGE_OPEN
524                 + NotificationConstants.XML_MESSAGE_CONSTANTS.MESSAGE_CLOSE
525                 + NotificationConstants.XML_MESSAGE_CONSTANTS.CONTENT_CLOSE);
526 
527         return notification;
528     }
529 
530     private String getChannelName(HttpServletRequest request, Map<String, Object> model, ErrorList errors) {
531         String channelName = request.getParameter("channelName");
532 
533         if (StringUtils.isEmpty(channelName) || StringUtils.equals(channelName, NONE_CHANNEL)) {
534             errors.addError("You must choose a channel.");
535         } else {
536             model.put("channelName", channelName);
537         }
538 
539         return channelName;
540     }
541 
542     private String getDeliveryType(HttpServletRequest request, Map<String, Object> model, ErrorList errors) {
543         String deliveryType = request.getParameter("deliveryType");
544 
545         if (StringUtils.isNotEmpty(deliveryType)) {
546             if (deliveryType.equalsIgnoreCase(NotificationConstants.DELIVERY_TYPES.FYI)) {
547                 deliveryType = NotificationConstants.DELIVERY_TYPES.FYI;
548             } else {
549                 deliveryType = NotificationConstants.DELIVERY_TYPES.ACK;
550             }
551             model.put("deliveryType", deliveryType);
552         } else {
553             errors.addError("You must choose a delivery type.");
554         }
555 
556         return deliveryType;
557     }
558 
559     /**
560      * Prepares the model used for sending the notification.
561      *
562      * @param request the servlet request
563      *
564      * @return the Spring MVC model
565      */
566     protected Map<String, Object> setupModelForSendNotification(HttpServletRequest request) {
567         Map<String, Object> model = new HashMap<String, Object>();
568 
569         model.put("defaultSender", request.getRemoteUser());
570         model.put("channels", notificationChannelService.getAllNotificationChannels());
571         model.put("priorities", dataObjectService.findMatching(NotificationPriorityBo.class,
572                 QueryByCriteria.Builder.create().build()).getResults());
573 
574         // set sendDateTime to current datetime if not provided
575         String sendDateTime = request.getParameter("sendDateTime");
576         String currentDateTime = Util.getCurrentDateTime();
577         if (StringUtils.isEmpty(sendDateTime)) {
578             sendDateTime = currentDateTime;
579         }
580         model.put("sendDateTime", sendDateTime);
581 
582         // retain the original date time or set to current if it was not in the request
583         if (request.getParameter("originalDateTime") == null) {
584             model.put("originalDateTime", currentDateTime);
585         } else {
586             model.put("originalDateTime", request.getParameter("originalDateTime"));
587         }
588 
589         model.put("userRecipients", request.getParameter("userRecipients"));
590         model.put("workgroupRecipients", request.getParameter("workgroupRecipients"));
591         model.put("workgroupNamespaceCodes", request.getParameter("workgroupNamespaceCodes"));
592 
593         return model;
594     }
595 
596     /**
597      * Returns whether the specified time is considered "in the future", based on some reasonable threshold.
598      *
599      * @param time the time to test
600      *
601      * @return true if the specified time is considered "in the future", false otherwise
602      */
603     private boolean timeIsInTheFuture(long time) {
604         boolean future = (time - System.currentTimeMillis()) > REASONABLE_IMMEDIATE_TIME_THRESHOLD;
605         LOG.info("Time: " + new Date(time) + " is in the future? " + future);
606         return future;
607     }
608 
609     /**
610      * Returns whether recipients exist either, from users or workgroups.
611      *
612      * @param userRecipients the list of user recipients
613      * @param workgroupRecipients the list of workgroup recipients
614      *
615      * @return true if there are any recipients, false otherwise
616      */
617     private boolean recipientsExist(String[] userRecipients, String[] workgroupRecipients) {
618         return (userRecipients != null && userRecipients.length > 0)
619             || (workgroupRecipients != null && workgroupRecipients.length > 0);
620     }
621 
622     /**
623      * Returns whether the specified Notification can be reasonably expected to have recipients.
624      *
625      * This is determined on whether the channel has default recipients, is subscribable, and whether the send date time
626      * is far enough in the future to expect that if there are no subscribers, there may actually be some by the time
627      * the notification is sent.
628      * @param notification the notification to test
629      *
630      * @return whether the specified Notification can be reasonably expected to have recipients
631      */
632     private boolean hasPotentialRecipients(NotificationBo notification) {
633         LOG.info("notification channel " + notification.getChannel() + " is subscribable: " + notification.getChannel().isSubscribable());
634         return !notification.getChannel().getRecipientLists().isEmpty() ||
635                !notification.getChannel().getSubscriptions().isEmpty() ||
636                 (notification.getChannel().isSubscribable() && timeIsInTheFuture(notification.getSendDateTimeValue().getTime()));
637     }
638 }