1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
67
68
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;
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
116
117
118
119 public void setNotificationService(NotificationService notificationService) {
120 this.notificationService = notificationService;
121 }
122
123
124
125
126
127
128 public void setNotificationWorkflowDocumentService(NotificationWorkflowDocumentService notificationWorkflowDocService) {
129 this.notificationWorkflowDocService = notificationWorkflowDocService;
130 }
131
132
133
134
135
136
137 public void setNotificationChannelService(NotificationChannelService notificationChannelService) {
138 this.notificationChannelService = notificationChannelService;
139 }
140
141
142
143
144
145
146 public void setNotificationRecipientService(NotificationRecipientService notificationRecipientService) {
147 this.notificationRecipientService = notificationRecipientService;
148 }
149
150
151
152
153
154
155 public void setNotificationMessageContentService(NotificationMessageContentService notificationMessageContentService) {
156 this.notificationMessageContentService = notificationMessageContentService;
157 }
158
159
160
161
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
286
287
288
289
290
291
292
293
294
295 protected ModelAndView submitNotificationMessage(HttpServletRequest request, String routeMessage, String viewName)
296 throws ServletException, IOException {
297 LOG.debug("remoteUser: " + request.getRemoteUser());
298
299
300
301 String initiatorId = getPrincipalIdFromIdOrName( request.getRemoteUser());
302 LOG.debug("initiatorId: " + initiatorId);
303
304
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
313 ErrorList el = new ErrorList();
314 el.addError("Notification(s) sent.");
315 model.put("errors", el);
316 } catch (ErrorList el) {
317
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
330
331
332
333
334
335
336
337
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
345 NotificationBo notification = populateNotificationInstance(request, model);
346
347
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
382
383
384
385
386
387
388
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
397
398
399
400
401
402
403
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
433 String[] userRecipients = parseUserRecipients(request);
434
435
436 String[] workgroupRecipients = parseWorkgroupRecipients(request);
437
438
439 String[] workgroupNamespaceCodes = parseWorkgroupNamespaceCodes(request);
440
441 String title = getParameter(request, "title", model, errors, "You must fill in a title.");
442
443
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
561
562
563
564
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
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
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
598
599
600
601
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
611
612
613
614
615
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
624
625
626
627
628
629
630
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 }