View Javadoc

1   /**
2    * Copyright 2005-2012 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.framework.persistence.dao.GenericDao;
21  import org.kuali.rice.ken.bo.NotificationBo;
22  import org.kuali.rice.ken.bo.NotificationChannelBo;
23  import org.kuali.rice.ken.bo.NotificationChannelReviewerBo;
24  import org.kuali.rice.ken.bo.NotificationContentTypeBo;
25  import org.kuali.rice.ken.bo.NotificationPriorityBo;
26  import org.kuali.rice.ken.bo.NotificationProducerBo;
27  import org.kuali.rice.ken.bo.NotificationRecipientBo;
28  import org.kuali.rice.ken.bo.NotificationSenderBo;
29  import org.kuali.rice.ken.document.kew.NotificationWorkflowDocument;
30  import org.kuali.rice.ken.exception.ErrorList;
31  import org.kuali.rice.ken.service.NotificationChannelService;
32  import org.kuali.rice.ken.service.NotificationMessageContentService;
33  import org.kuali.rice.ken.service.NotificationRecipientService;
34  import org.kuali.rice.ken.service.NotificationService;
35  import org.kuali.rice.ken.service.NotificationWorkflowDocumentService;
36  import org.kuali.rice.ken.util.NotificationConstants;
37  import org.kuali.rice.ken.util.Util;
38  import org.kuali.rice.kew.api.WorkflowDocument;
39  import org.kuali.rice.kew.rule.GenericAttributeContent;
40  import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
41  import org.springframework.web.servlet.ModelAndView;
42  
43  import javax.servlet.ServletException;
44  import javax.servlet.http.HttpServletRequest;
45  import javax.servlet.http.HttpServletResponse;
46  import java.io.IOException;
47  import java.sql.Timestamp;
48  import java.text.ParseException;
49  import java.util.Date;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Map;
53  
54  /**
55   * This class is the controller for sending Simple notification messages via an end user interface.
56   * @author Kuali Rice Team (rice.collab@kuali.org)
57   */
58  public class SendNotificationMessageController extends BaseSendNotificationController {
59      /** Logger for this class and subclasses */
60      private static final Logger LOG = Logger
61              .getLogger(SendNotificationMessageController.class);
62  
63      private static final String NONE_CHANNEL = "___NONE___";
64      private static final long REASONABLE_IMMEDIATE_TIME_THRESHOLD = 1000 * 60 * 5; // <= 5 minutes is "immediate"
65  
66      /**
67       * Returns whether the specified time is considered "in the future", based on some reasonable
68       * threshold
69       * @param time the time to test
70       * @return whether the specified time is considered "in the future", based on some reasonable
71       *         threshold
72       */
73      private boolean timeIsInTheFuture(long time) {
74          boolean future = (time - System.currentTimeMillis()) > REASONABLE_IMMEDIATE_TIME_THRESHOLD;
75          LOG.info("Time: " + new Date(time) + " is in the future? " + future);
76          return future;
77      }
78  
79      /**
80       * Returns whether the specified Notification can be reasonably expected to have recipients.
81       * This is determined on whether the channel has default recipients, is subscribably, and
82       * whether the send date time is far enough in the future to expect that if there are no
83       * subscribers, there may actually be some by the time the notification is sent.
84       * @param notification the notification to test
85       * @return whether the specified Notification can be reasonably expected to have recipients
86       */
87      private boolean hasPotentialRecipients(NotificationBo notification) {
88          LOG.info("notification channel " + notification.getChannel() + " is subscribable: "
89                  + notification.getChannel().isSubscribable());
90          return notification.getChannel().getRecipientLists().size() > 0
91                  ||
92                  notification.getChannel().getSubscriptions().size() > 0
93                  ||
94                  (notification.getChannel().isSubscribable() && timeIsInTheFuture(notification.getSendDateTimeValue()
95                          .getTime()));
96      }
97  
98      protected NotificationService notificationService;
99  
100     protected NotificationWorkflowDocumentService notificationWorkflowDocService;
101 
102     protected NotificationChannelService notificationChannelService;
103 
104     protected NotificationRecipientService notificationRecipientService;
105 
106     protected NotificationMessageContentService messageContentService;
107 
108     protected GenericDao businessObjectDao;
109 
110     /**
111      * Set the NotificationService
112      * @param notificationService
113      */
114     public void setNotificationService(NotificationService notificationService) {
115         this.notificationService = notificationService;
116     }
117 
118     /**
119      * This method sets the NotificationWorkflowDocumentService
120      * @param s
121      */
122     public void setNotificationWorkflowDocumentService(
123             NotificationWorkflowDocumentService s) {
124         this.notificationWorkflowDocService = s;
125     }
126 
127     /**
128      * Sets the notificationChannelService attribute value.
129      * @param notificationChannelService The notificationChannelService to set.
130      */
131     public void setNotificationChannelService(
132             NotificationChannelService notificationChannelService) {
133         this.notificationChannelService = notificationChannelService;
134     }
135 
136     /**
137      * Sets the notificationRecipientService attribute value.
138      * @param notificationRecipientService
139      */
140     public void setNotificationRecipientService(
141             NotificationRecipientService notificationRecipientService) {
142         this.notificationRecipientService = notificationRecipientService;
143     }
144 
145     /**
146      * Sets the messageContentService attribute value.
147      * @param messageContentService
148      */
149     public void setMessageContentService(
150             NotificationMessageContentService notificationMessageContentService) {
151         this.messageContentService = notificationMessageContentService;
152     }
153 
154     /**
155      * Sets the businessObjectDao attribute value.
156      * @param businessObjectDao The businessObjectDao to set.
157      */
158     public void setBusinessObjectDao(GenericDao businessObjectDao) {
159         this.businessObjectDao = businessObjectDao;
160     }
161 
162     /**
163      * Handles the display of the form for sending a simple notification message
164      * @param request : a servlet request
165      * @param response : a servlet response
166      * @throws ServletException : an exception
167      * @throws IOException : an exception
168      * @return a ModelAndView object
169      */
170     public ModelAndView sendSimpleNotificationMessage(
171             HttpServletRequest request, HttpServletResponse response)
172             throws ServletException, IOException {
173         String view = "SendSimpleNotificationMessage";
174 
175         LOG.debug("remoteUser: " + request.getRemoteUser());
176 
177         Map<String, Object> model = setupModelForSendSimpleNotification(request);
178         model.put("errors", new ErrorList()); // need an empty one so we don't have an NPE
179 
180         return new ModelAndView(view, model);
181     }
182 
183     /**
184      * This method prepares the model used for the send simple notification message form.
185      * @param request
186      * @return Map<String, Object>
187      */
188     private Map<String, Object> setupModelForSendSimpleNotification(
189             HttpServletRequest request) {
190         Map<String, Object> model = new HashMap<String, Object>();
191         model.put("defaultSender", request.getRemoteUser());
192         model.put("channels", notificationChannelService
193                 .getAllNotificationChannels());
194         model.put("priorities", businessObjectDao
195                 .findAll(NotificationPriorityBo.class));
196         // set sendDateTime to current datetime if not provided
197         String sendDateTime = request.getParameter("sendDateTime");
198         String currentDateTime = Util.getCurrentDateTime();
199         if (StringUtils.isEmpty(sendDateTime)) {
200             sendDateTime = currentDateTime;
201         }
202         model.put("sendDateTime", sendDateTime);
203 
204         // retain the original date time or set to current if
205         // it was not in the request
206         if (request.getParameter("originalDateTime") == null) {
207             model.put("originalDateTime", currentDateTime);
208         } else {
209             model.put("originalDateTime", request.getParameter("originalDateTime"));
210         }
211 
212         model.put("userRecipients", request.getParameter("userRecipients"));
213         model.put("workgroupRecipients", request.getParameter("workgroupRecipients"));
214         model.put("workgroupNamespaceCodes", request.getParameter("workgroupNamespaceCodes"));
215 
216         return model;
217     }
218 
219     /**
220      * This method handles submitting the actual simple notification message.
221      * @param request
222      * @param response
223      * @return ModelAndView
224      * @throws ServletException
225      * @throws IOException
226      */
227     public ModelAndView submitSimpleNotificationMessage(
228             HttpServletRequest request, HttpServletResponse response)
229             throws ServletException, IOException {
230         LOG.debug("remoteUser: " + request.getRemoteUser());
231 
232         // obtain a workflow user object first
233         //WorkflowIdDTO initiator = new WorkflowIdDTO(request.getRemoteUser());
234         String initiatorId = request.getRemoteUser();
235 
236         // now construct the workflow document, which will interact with workflow
237         WorkflowDocument document;
238         Map<String, Object> model = new HashMap<String, Object>();
239         String view;
240         try {
241             document = NotificationWorkflowDocument.createNotificationDocument(
242                     initiatorId,
243                     NotificationConstants.KEW_CONSTANTS.SEND_NOTIFICATION_REQ_DOC_TYPE);
244 
245             //parse out the application content into a Notification BO
246             NotificationBo notification = populateNotificationInstance(request,
247                     model);
248 
249             // now get that content in an understandable XML format and pass into document
250             String notificationAsXml = messageContentService
251                     .generateNotificationMessage(notification);
252 
253             Map<String, String> attrFields = new HashMap<String, String>();
254             List<NotificationChannelReviewerBo> reviewers = notification.getChannel().getReviewers();
255             int ui = 0;
256             int gi = 0;
257             for (NotificationChannelReviewerBo reviewer : reviewers) {
258                 String prefix;
259                 int index;
260                 if (KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.equals(reviewer.getReviewerType())) {
261                     prefix = "user";
262                     index = ui;
263                     ui++;
264                 } else if (KimGroupMemberTypes.GROUP_MEMBER_TYPE.equals(reviewer.getReviewerType())) {
265                     prefix = "group";
266                     index = gi;
267                     gi++;
268                 } else {
269                     LOG.error("Invalid type for reviewer " + reviewer.getReviewerId() + ": "
270                             + reviewer.getReviewerType());
271                     continue;
272                 }
273                 attrFields.put(prefix + index, reviewer.getReviewerId());
274             }
275             GenericAttributeContent gac = new GenericAttributeContent("channelReviewers");
276             document.setApplicationContent(notificationAsXml);
277             document.setAttributeContent("<attributeContent>" + gac.generateContent(attrFields) + "</attributeContent>");
278 
279             document.setTitle(notification.getTitle());
280 
281             document.route("This message was submitted via the simple notification message submission form by user "
282                     + initiatorId);
283 
284             view = "SendSimpleNotificationMessage";
285 
286             // This ain't pretty, but it gets the job done for now.
287             ErrorList el = new ErrorList();
288             el.addError("Notification(s) sent.");
289             model.put("errors", el);
290 
291         } catch (ErrorList el) {
292             // route back to the send form again
293             Map<String, Object> model2 = setupModelForSendSimpleNotification(request);
294             model.putAll(model2);
295             model.put("errors", el);
296 
297             view = "SendSimpleNotificationMessage";
298         } catch (Exception e) {
299             throw new RuntimeException(e);
300         }
301 
302         return new ModelAndView(view, model);
303     }
304 
305     /**
306      * This method creates a new Notification instance from the form values.
307      * @param request
308      * @param model
309      * @return Notification
310      * @throws IllegalArgumentException
311      */
312     private NotificationBo populateNotificationInstance(
313             HttpServletRequest request, Map<String, Object> model)
314             throws IllegalArgumentException, ErrorList {
315         ErrorList errors = new ErrorList();
316 
317         NotificationBo notification = new NotificationBo();
318 
319         // grab data from form
320         // channel name
321         String channelName = request.getParameter("channelName");
322         if (StringUtils.isEmpty(channelName) || StringUtils.equals(channelName, NONE_CHANNEL)) {
323             errors.addError("You must choose a channel.");
324         } else {
325             model.put("channelName", channelName);
326         }
327 
328         // priority name
329         String priorityName = request.getParameter("priorityName");
330         if (StringUtils.isEmpty(priorityName)) {
331             errors.addError("You must choose a priority.");
332         } else {
333             model.put("priorityName", priorityName);
334         }
335 
336         // sender names
337         String senderNames = request.getParameter("senderNames");
338         String[] senders = null;
339         if (StringUtils.isEmpty(senderNames)) {
340             errors.addError("You must enter at least one sender.");
341         } else {
342             senders = StringUtils.split(senderNames, ",");
343 
344             model.put("senderNames", senderNames);
345         }
346 
347         // delivery type
348         String deliveryType = request.getParameter("deliveryType");
349         if (StringUtils.isEmpty(deliveryType)) {
350             errors.addError("You must choose a type.");
351         } else {
352             if (deliveryType
353                     .equalsIgnoreCase(NotificationConstants.DELIVERY_TYPES.FYI)) {
354                 deliveryType = NotificationConstants.DELIVERY_TYPES.FYI;
355             } else {
356                 deliveryType = NotificationConstants.DELIVERY_TYPES.ACK;
357             }
358             model.put("deliveryType", deliveryType);
359         }
360 
361         // get datetime when form was initially rendered
362         String originalDateTime = request.getParameter("originalDateTime");
363         Date origdate = null;
364         Date senddate = null;
365         Date removedate = null;
366         try {
367             origdate = Util.parseUIDateTime(originalDateTime);
368         } catch (ParseException pe) {
369             errors.addError("Original date is invalid.");
370         }
371         // send date time
372         String sendDateTime = request.getParameter("sendDateTime");
373         if (StringUtils.isBlank(sendDateTime)) {
374             sendDateTime = Util.getCurrentDateTime();
375         }
376 
377         try {
378             senddate = Util.parseUIDateTime(sendDateTime);
379         } catch (ParseException pe) {
380             errors.addError("You specified an invalid Send Date/Time.  Please use the calendar picker.");
381         }
382 
383         if (senddate != null && senddate.before(origdate)) {
384             errors.addError("Send Date/Time cannot be in the past.");
385         }
386 
387         model.put("sendDateTime", sendDateTime);
388 
389         // auto remove date time
390         String autoRemoveDateTime = request.getParameter("autoRemoveDateTime");
391         if (StringUtils.isNotBlank(autoRemoveDateTime)) {
392             try {
393                 removedate = Util.parseUIDateTime(autoRemoveDateTime);
394             } catch (ParseException pe) {
395                 errors.addError("You specified an invalid Auto-Remove Date/Time.  Please use the calendar picker.");
396             }
397 
398             if (removedate != null) {
399                 if (removedate.before(origdate)) {
400                     errors.addError("Auto-Remove Date/Time cannot be in the past.");
401                 } else if (senddate != null && removedate.before(senddate)) {
402                     errors.addError("Auto-Remove Date/Time cannot be before the Send Date/Time.");
403                 }
404             }
405         }
406 
407         model.put("autoRemoveDateTime", autoRemoveDateTime);
408 
409         // user recipient names
410         String[] userRecipients = parseUserRecipients(request);
411 
412         // workgroup recipient names
413         String[] workgroupRecipients = parseWorkgroupRecipients(request);
414 
415         // workgroup namespace codes
416         String[] workgroupNamespaceCodes = parseWorkgroupNamespaceCodes(request);
417 
418         // title
419         String title = request.getParameter("title");
420         if (!StringUtils.isEmpty(title)) {
421             model.put("title", title);
422         } else {
423             errors.addError("You must fill in a title");
424         }
425 
426         // message
427         String message = request.getParameter("message");
428         if (StringUtils.isEmpty(message)) {
429             errors.addError("You must fill in a message.");
430         } else {
431             model.put("message", message);
432         }
433 
434         // stop processing if there are errors
435         if (errors.getErrors().size() > 0) {
436             throw errors;
437         }
438 
439         // now populate the notification BO instance
440         NotificationChannelBo channel = Util.retrieveFieldReference("channel",
441                 "name", channelName, NotificationChannelBo.class,
442                 businessObjectDao);
443         notification.setChannel(channel);
444 
445         NotificationPriorityBo priority = Util.retrieveFieldReference("priority",
446                 "name", priorityName, NotificationPriorityBo.class,
447                 businessObjectDao);
448         notification.setPriority(priority);
449 
450         NotificationContentTypeBo contentType = Util.retrieveFieldReference(
451                 "contentType", "name",
452                 NotificationConstants.CONTENT_TYPES.SIMPLE_CONTENT_TYPE,
453                 NotificationContentTypeBo.class, businessObjectDao);
454         notification.setContentType(contentType);
455 
456         NotificationProducerBo producer = Util
457                 .retrieveFieldReference(
458                         "producer",
459                         "name",
460                         NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_NAME,
461                         NotificationProducerBo.class, businessObjectDao);
462         notification.setProducer(producer);
463 
464         for (String senderName : senders) {
465             if (StringUtils.isEmpty(senderName)) {
466                 errors.addError("A sender's name cannot be blank.");
467             } else {
468                 NotificationSenderBo ns = new NotificationSenderBo();
469                 ns.setSenderName(senderName.trim());
470                 notification.addSender(ns);
471             }
472         }
473 
474         boolean recipientsExist = false;
475 
476         if (userRecipients != null && userRecipients.length > 0) {
477             recipientsExist = true;
478             for (String userRecipientId : userRecipients) {
479                 if (isUserRecipientValid(userRecipientId, errors)) {
480                     NotificationRecipientBo recipient = new NotificationRecipientBo();
481                     recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
482                     recipient.setRecipientId(userRecipientId);
483                     notification.addRecipient(recipient);
484                 }
485             }
486         }
487 
488         if (workgroupRecipients != null && workgroupRecipients.length > 0) {
489             recipientsExist = true;
490             if (workgroupNamespaceCodes != null && workgroupNamespaceCodes.length > 0) {
491                 if (workgroupNamespaceCodes.length == workgroupRecipients.length) {
492                     for (int i = 0; i < workgroupRecipients.length; i++) {
493                         if (isWorkgroupRecipientValid(workgroupRecipients[i], workgroupNamespaceCodes[i], errors)) {
494                             NotificationRecipientBo recipient = new NotificationRecipientBo();
495                             recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode());
496                             recipient.setRecipientId(
497                                     getGroupService().getGroupByNamespaceCodeAndName(workgroupNamespaceCodes[i],
498                                             workgroupRecipients[i]).getId());
499                             notification.addRecipient(recipient);
500                         }
501                     }
502                 } else {
503                     errors.addError("The number of groups must match the number of namespace codes");
504                 }
505             } else {
506                 errors.addError("You must specify a namespace code for every group name");
507             }
508         } else if (workgroupNamespaceCodes != null && workgroupNamespaceCodes.length > 0) {
509             errors.addError("You must specify a group name for every namespace code");
510         }
511 
512         // check to see if there were any errors
513         if (errors.getErrors().size() > 0) {
514             throw errors;
515         }
516 
517         notification.setTitle(title);
518 
519         notification.setDeliveryType(deliveryType);
520 
521         // simpledateformat is not threadsafe, have to sync and validate
522         Date d = null;
523         if (StringUtils.isNotBlank(sendDateTime)) {
524             try {
525                 d = Util.parseUIDateTime(sendDateTime);
526             } catch (ParseException pe) {
527                 errors.addError("You specified an invalid send date and time.  Please use the calendar picker.");
528             }
529             notification.setSendDateTimeValue(new Timestamp(d.getTime()));
530         }
531 
532         Date d2 = null;
533         if (StringUtils.isNotBlank(autoRemoveDateTime)) {
534             try {
535                 d2 = Util.parseUIDateTime(autoRemoveDateTime);
536                 if (d2.before(d)) {
537                     errors.addError("Auto Remove Date/Time cannot be before Send Date/Time.");
538                 }
539             } catch (ParseException pe) {
540                 errors.addError("You specified an invalid auto remove date and time.  Please use the calendar picker.");
541             }
542             notification.setAutoRemoveDateTimeValue(new Timestamp(d2.getTime()));
543         }
544 
545         if (!recipientsExist && !hasPotentialRecipients(notification)) {
546             errors.addError("You must specify at least one user or group recipient.");
547         }
548 
549         // check to see if there were any errors
550         if (errors.getErrors().size() > 0) {
551             throw errors;
552         }
553 
554         notification
555                 .setContent(NotificationConstants.XML_MESSAGE_CONSTANTS.CONTENT_SIMPLE_OPEN
556                         + NotificationConstants.XML_MESSAGE_CONSTANTS.MESSAGE_OPEN
557                         + message
558                         + NotificationConstants.XML_MESSAGE_CONSTANTS.MESSAGE_CLOSE
559                         + NotificationConstants.XML_MESSAGE_CONSTANTS.CONTENT_CLOSE);
560 
561         return notification;
562     }
563 }