1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.ken.service.impl;
17
18 import com.thoughtworks.xstream.XStream;
19 import com.thoughtworks.xstream.io.xml.DomDriver;
20 import org.apache.commons.io.IOUtils;
21 import org.apache.commons.lang.StringUtils;
22 import org.apache.log4j.Logger;
23 import org.kuali.rice.core.api.util.xml.XmlException;
24 import org.kuali.rice.core.api.util.xml.XmlJotter;
25 import org.kuali.rice.ken.bo.NotificationBo;
26 import org.kuali.rice.ken.bo.NotificationChannelBo;
27 import org.kuali.rice.ken.bo.NotificationContentTypeBo;
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.NotificationResponseBo;
32 import org.kuali.rice.ken.bo.NotificationSenderBo;
33 import org.kuali.rice.ken.service.NotificationContentTypeService;
34 import org.kuali.rice.ken.service.NotificationMessageContentService;
35 import org.kuali.rice.ken.util.CompoundNamespaceContext;
36 import org.kuali.rice.ken.util.ConfiguredNamespaceContext;
37 import org.kuali.rice.ken.util.NotificationConstants;
38 import org.kuali.rice.ken.util.Util;
39 import org.kuali.rice.kew.util.Utilities;
40 import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
41 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
42 import org.kuali.rice.krad.data.DataObjectService;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.Element;
45 import org.w3c.dom.Node;
46 import org.w3c.dom.NodeList;
47 import org.xml.sax.InputSource;
48 import org.xml.sax.SAXException;
49
50 import javax.xml.parsers.ParserConfigurationException;
51 import javax.xml.xpath.XPath;
52 import javax.xml.xpath.XPathConstants;
53 import javax.xml.xpath.XPathExpressionException;
54 import javax.xml.xpath.XPathFactory;
55 import java.io.ByteArrayInputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.sql.Timestamp;
59 import java.text.DateFormat;
60 import java.text.ParseException;
61 import java.text.SimpleDateFormat;
62 import java.util.ArrayList;
63 import java.util.Date;
64 import java.util.HashMap;
65 import java.util.List;
66 import java.util.Map;
67
68
69
70
71
72
73
74 public class NotificationMessageContentServiceImpl implements NotificationMessageContentService {
75 private static final Logger LOG = Logger.getLogger(NotificationMessageContentServiceImpl.class);
76
77
78
79
80 static final String CONTENT_TYPE_NAMESPACE_PREFIX = "ns:notification/ContentType";
81
82
83 private static final DateFormat DATEFORMAT_CURR_TZ = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
84
85
86
87
88 private DataObjectService dataObjectService;
89
90
91
92 private NotificationContentTypeService notificationContentTypeService;
93
94
95
96
97
98
99
100 public NotificationMessageContentServiceImpl(DataObjectService dataObjectService, NotificationContentTypeService notificationContentTypeService) {
101 this.dataObjectService = dataObjectService;
102 this.notificationContentTypeService = notificationContentTypeService;
103 }
104
105
106
107
108
109 public NotificationBo parseNotificationRequestMessage(String notificationMessageAsXml) throws IOException, XmlException {
110
111
112
113 byte[] bytes = notificationMessageAsXml.getBytes();
114
115 return parseNotificationRequestMessage(bytes);
116 }
117
118
119
120
121
122 public NotificationBo parseNotificationRequestMessage(InputStream stream) throws IOException, XmlException {
123
124
125
126 byte[] bytes = IOUtils.toByteArray(stream);
127
128 return parseNotificationRequestMessage(bytes);
129 }
130
131
132
133
134
135
136
137
138
139
140 private NotificationBo parseNotificationRequestMessage(byte[] bytes) throws IOException, XmlException {
141
142 Document doc;
143 try {
144 doc = Util.parseWithNotificationEntityResolver(new InputSource(new ByteArrayInputStream(bytes)), true, true, notificationContentTypeService);
145 } catch (ParserConfigurationException pce) {
146 throw new XmlException("Error obtaining XML parser", pce);
147 } catch (SAXException se) {
148 throw new XmlException("Error validating notification request", se);
149 }
150
151 Element root = doc.getDocumentElement();
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 XPath xpath = XPathFactory.newInstance().newXPath();
173 xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc));
174
175
176 LOG.debug("URI: " + xpath.getNamespaceContext().getNamespaceURI("nreq"));
177 try {
178 NotificationBo notification = new NotificationBo();
179
180 String channelName = (String) xpath.evaluate("/nreq:notification/nreq:channel", root);
181 LOG.debug("CHANNELNAME: "+ channelName);
182 String producerName = xpath.evaluate("/nreq:notification/nreq:producer", root);
183
184 List<String> senders = new ArrayList<String>();
185 NodeList nodes = (NodeList) xpath.evaluate("/nreq:notification/nreq:senders/nreq:sender", root, XPathConstants.NODESET);
186 for (int i = 0; i < nodes.getLength(); i++) {
187 LOG.debug("sender node: " + nodes.item(i));
188 LOG.debug("sender node VALUE: " + nodes.item(i).getTextContent());
189 senders.add(nodes.item(i).getTextContent());
190 }
191 nodes = (NodeList) xpath.evaluate("/nreq:notification/nreq:recipients/nreq:group|/nreq:notification/nreq:recipients/nreq:user", root, XPathConstants.NODESET);
192 List<NotificationRecipientBo> recipients = new ArrayList<NotificationRecipientBo>();
193 for (int i = 0; i < nodes.getLength(); i++) {
194 Node node = nodes.item(i);
195 NotificationRecipientBo recipient = new NotificationRecipientBo();
196
197 if (NotificationConstants.RECIPIENT_TYPES.GROUP.equalsIgnoreCase(node.getLocalName())) {
198
199 recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode());
200 recipient.setRecipientId(KimApiServiceLocator.getGroupService().getGroupByNamespaceCodeAndName(
201 Utilities.parseGroupNamespaceCode(node.getTextContent()), Utilities.parseGroupName(
202 node.getTextContent())).getId());
203 } else if (NotificationConstants.RECIPIENT_TYPES.USER.equalsIgnoreCase(node.getLocalName())){
204
205 recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
206 recipient.setRecipientId(node.getTextContent());
207 } else {
208 throw new XmlException("Invalid 'recipientType' value: '" + node.getLocalName() +
209 "'. Needs to either be 'user' or 'group'");
210 }
211 recipient.setNotification(notification);
212 recipients.add(recipient);
213 }
214
215 String deliveryType = xpath.evaluate("/nreq:notification/nreq:deliveryType", root);
216 String sendDateTime = xpath.evaluate("/nreq:notification/nreq:sendDateTime", root);
217 String autoRemoveDateTime = xpath.evaluate("/nreq:notification/nreq:autoRemoveDateTime", root);
218
219 String priorityName = xpath.evaluate("/nreq:notification/nreq:priority", root);
220 String title = xpath.evaluate("/nreq:notification/nreq:title", root);
221 String contentTypeName = xpath.evaluate("/nreq:notification/nreq:contentType", root);
222
223
224
225
226
227 if (!StringUtils.isBlank(title)) {
228 notification.setTitle(title);
229 }
230
231
232
233
234 NotificationChannelBo channel = Util.retrieveFieldReference("channel", "name", channelName, NotificationChannelBo.class, dataObjectService);
235 notification.setChannel(channel);
236
237 NotificationProducerBo producer = Util.retrieveFieldReference("producer", "name", producerName, NotificationProducerBo.class, dataObjectService);
238 notification.setProducer(producer);
239
240 for (String sender: senders) {
241 NotificationSenderBo ns = new NotificationSenderBo();
242 LOG.debug("Setting sender: " + sender);
243 ns.setSenderName(sender);
244 ns.setNotification(notification);
245 notification.addSender(ns);
246 }
247
248 for (NotificationRecipientBo recipient: recipients) {
249 LOG.debug("Setting recipient id: "+ recipient.getRecipientId());
250
251 notification.addRecipient(recipient);
252 }
253
254
255 if(!NotificationConstants.DELIVERY_TYPES.ACK.equalsIgnoreCase(deliveryType) &&
256 !NotificationConstants.DELIVERY_TYPES.FYI.equalsIgnoreCase(deliveryType)) {
257 throw new XmlException("Invalid 'deliveryType' value: '" + deliveryType +
258 "'. Must be either 'ACK' or 'FYI'.");
259 }
260 notification.setDeliveryType(deliveryType);
261
262
263
264
265 Date d;
266 if(StringUtils.isNotBlank(sendDateTime)) {
267 try {
268 d = Util.parseXSDDateTime(sendDateTime);
269 } catch (ParseException pe) {
270 throw new XmlException("Invalid 'sendDateTime' value: " + sendDateTime, pe);
271 }
272 notification.setSendDateTimeValue(new Timestamp(d.getTime()));
273 }
274 if(StringUtils.isNotBlank(autoRemoveDateTime)) {
275 try {
276 d = Util.parseXSDDateTime(autoRemoveDateTime);
277 } catch (ParseException pe) {
278 throw new XmlException("Invalid 'autoRemoveDateTime' value: " + autoRemoveDateTime, pe);
279 }
280 notification.setAutoRemoveDateTimeValue(new Timestamp(d.getTime()));
281 }
282
283
284
285 NotificationPriorityBo priority = Util.retrieveFieldReference("priority", "name", priorityName, NotificationPriorityBo.class, dataObjectService);
286 notification.setPriority(priority);
287
288 NotificationContentTypeBo contentType =
289 Util.retrieveFieldReference("contentType", "name", contentTypeName, NotificationContentTypeBo.class, dataObjectService, Boolean.TRUE);
290 notification.setContentType(contentType);
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315 Map<String, String> contentTypeNamespace = new HashMap<String, String>();
316 String ephemeralNamespace = "contentNS_" + System.currentTimeMillis();
317 contentTypeNamespace.put(ephemeralNamespace, CONTENT_TYPE_NAMESPACE_PREFIX + contentType.getName());
318 xpath.setNamespaceContext(new CompoundNamespaceContext(new ConfiguredNamespaceContext(contentTypeNamespace), xpath.getNamespaceContext()));
319 Node contentNode = (Node) xpath.evaluate("/nreq:notification/" + ephemeralNamespace + ":content", root, XPathConstants.NODE);
320 Element contentElement = null;
321 String content = "";
322
323
324
325
326
327 if (contentNode == null) {
328 throw new XmlException("The 'content' element is mandatory.");
329 }
330 if (contentNode != null) {
331 if (!(contentNode instanceof Element)) {
332
333 throw new XmlException("The 'content' node is not an Element! (???).");
334 }
335 contentElement = (Element) contentNode;
336
337
338 content = XmlJotter.jotNode(contentNode, true);
339 }
340
341 notification.setContent(content);
342
343 LOG.debug("Content type: " + contentType.getName());
344 LOG.debug("Content: " + content);
345
346
347
348
349 validateContent(notification, contentType.getName(), contentElement, content);
350
351 return notification;
352 } catch (XPathExpressionException xpee) {
353 throw new XmlException("Error parsing request", xpee);
354 }
355 }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372 private void validateContent(NotificationBo notification, String contentType, Element contentElement, String content) throws IOException, XmlException {
373
374
375
376
377
378
379
380
381
382 String contentTypeTitleCase = Character.toTitleCase(contentType.charAt(0)) + contentType.substring(1);
383 String expectedNamespaceURI = CONTENT_TYPE_NAMESPACE_PREFIX + contentTypeTitleCase;
384 String actualNamespaceURI = contentElement.getNamespaceURI();
385 if (!actualNamespaceURI.equals(expectedNamespaceURI)) {
386 throw new XmlException("Namespace URI of 'content' node, '" + actualNamespaceURI + "', does not match expected namespace URI, '" + expectedNamespaceURI + "', for content type '" + contentType + "'");
387 }
388 }
389
390
391
392
393
394 public String generateNotificationResponseMessage(NotificationResponseBo response) {
395 XStream xstream = new XStream(new DomDriver());
396 xstream.alias("response", NotificationResponseBo.class);
397 xstream.alias("status", String.class);
398 xstream.alias("message", String.class);
399 xstream.alias("notificationId", Long.class);
400 String xml = xstream.toXML(response);
401 return xml;
402 }
403
404
405
406
407
408
409 public String generateNotificationMessage(NotificationBo notification, String userRecipientId) {
410
411 NotificationBo clone = Util.cloneNotificationWithoutObjectReferences(notification);
412
413
414
415
416
417
418
419
420
421
422
423
424
425 if(StringUtils.isNotBlank(userRecipientId)) {
426 clone.getRecipients().clear();
427
428 NotificationRecipientBo recipient = new NotificationRecipientBo();
429 recipient.setRecipientId(userRecipientId);
430 recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
431
432 clone.getRecipients().add(recipient);
433 }
434
435
436 XStream xstream = new XStream(new DomDriver());
437 xstream.alias("notification", NotificationBo.class);
438 xstream.alias("channel", NotificationChannelBo.class);
439 xstream.alias("contentType", NotificationContentTypeBo.class);
440 xstream.alias("title", String.class);
441 xstream.alias("priority", NotificationPriorityBo.class);
442 xstream.alias("producer", NotificationProducerBo.class);
443 xstream.alias("recipient", NotificationRecipientBo.class);
444 xstream.alias("sender", NotificationSenderBo.class);
445 String xml = xstream.toXML(clone);
446 return xml;
447 }
448
449
450
451
452
453 public String generateNotificationMessage(NotificationBo notification) {
454 return generateNotificationMessage(notification, null);
455 }
456
457
458
459
460
461
462 public NotificationBo parseSerializedNotificationXml(byte[] xmlAsBytes) throws Exception {
463 Document doc;
464 NotificationBo notification = new NotificationBo();
465
466 try {
467 doc = Util.parse(new InputSource(new ByteArrayInputStream(xmlAsBytes)), false, false, null);
468 } catch (Exception pce) {
469 throw new XmlException("Error obtaining XML parser", pce);
470 }
471
472 Element root = doc.getDocumentElement();
473 XPath xpath = XPathFactory.newInstance().newXPath();
474 xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc));
475
476 try {
477
478 String title = ((String) xpath.evaluate("//notification/title", root)).trim();
479
480 String channelName = ((String) xpath.evaluate("//notification/channel/name", root)).trim();
481
482 String contentTypeName = ((String) xpath.evaluate("//notification/contentType/name", root)).trim();
483
484 String priorityName = ((String) xpath.evaluate("//notification/priority/name", root)).trim();
485
486 List<String> senders = new ArrayList<String>();
487 NodeList senderNodes = (NodeList) xpath.evaluate("//notification/senders/sender/senderName", root, XPathConstants.NODESET);
488 for (int i = 0; i < senderNodes.getLength(); i++) {
489 senders.add(senderNodes.item(i).getTextContent().trim());
490 }
491
492 String deliveryType = ((String) xpath.evaluate("//notification/deliveryType", root)).trim();
493 if(deliveryType.equalsIgnoreCase(NotificationConstants.DELIVERY_TYPES.FYI)) {
494 deliveryType = NotificationConstants.DELIVERY_TYPES.FYI;
495 } else {
496 deliveryType = NotificationConstants.DELIVERY_TYPES.ACK;
497 }
498
499 String sendDateTime = ((String) xpath.evaluate("//notification/sendDateTime", root)).trim();
500
501 String autoRemoveDateTime = ((String) xpath.evaluate("//notification/autoRemoveDateTime", root)).trim();
502
503 List<String> userRecipients = new ArrayList<String>();
504 List<String> workgroupRecipients = new ArrayList<String>();
505
506 NodeList recipientIds = (NodeList) xpath.evaluate("//notification/recipients/recipient/recipientId", root, XPathConstants.NODESET);
507 NodeList recipientTypes = (NodeList) xpath.evaluate("//notification/recipients/recipient/recipientType", root, XPathConstants.NODESET);
508
509 for (int i = 0; i < recipientIds.getLength(); i++) {
510 if(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode().equalsIgnoreCase(recipientTypes.item(i).getTextContent().trim())) {
511 userRecipients.add(recipientIds.item(i).getTextContent().trim());
512 } else {
513
514
515
516 workgroupRecipients.add(recipientIds.item(i).getTextContent().trim());
517 }
518 }
519
520 String content = ((String) xpath.evaluate("//notification/content", root)).trim();
521
522
523 NotificationChannelBo channel = Util.retrieveFieldReference("channel", "name", channelName, NotificationChannelBo.class, dataObjectService);
524 notification.setChannel(channel);
525
526 NotificationPriorityBo priority = Util.retrieveFieldReference("priority", "name", priorityName, NotificationPriorityBo.class, dataObjectService);
527 notification.setPriority(priority);
528
529 NotificationContentTypeBo contentType = Util.retrieveFieldReference("contentType", "name", contentTypeName, NotificationContentTypeBo.class, dataObjectService, Boolean.TRUE);
530 notification.setContentType(contentType);
531
532 NotificationProducerBo producer = Util.retrieveFieldReference("producer", "name", NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_NAME,
533 NotificationProducerBo.class, dataObjectService);
534 notification.setProducer(producer);
535
536 for (String senderName: senders) {
537 NotificationSenderBo ns = new NotificationSenderBo();
538 ns.setSenderName(senderName);
539 notification.addSender(ns);
540 }
541
542 for (String userRecipientId: userRecipients) {
543 NotificationRecipientBo recipient = new NotificationRecipientBo();
544 recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
545 recipient.setRecipientId(userRecipientId);
546 notification.addRecipient(recipient);
547 }
548
549 for (String workgroupRecipientId: workgroupRecipients) {
550 NotificationRecipientBo recipient = new NotificationRecipientBo();
551 recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode());
552 recipient.setRecipientId(workgroupRecipientId);
553 notification.addRecipient(recipient);
554 }
555
556 if (!StringUtils.isBlank(title)) {
557 notification.setTitle(title);
558 }
559
560 notification.setDeliveryType(deliveryType);
561
562
563 synchronized (DATEFORMAT_CURR_TZ) {
564 Date d = null;
565 if(StringUtils.isNotBlank(sendDateTime)) {
566 try {
567 d = DATEFORMAT_CURR_TZ.parse(sendDateTime);
568 } catch (ParseException pe) {
569 LOG.warn("Invalid 'sendDateTime' value: " + sendDateTime, pe);
570 }
571 notification.setSendDateTimeValue(new Timestamp(d.getTime()));
572 }
573
574 Date d2 = null;
575 if(StringUtils.isNotBlank(autoRemoveDateTime)) {
576 try {
577 d2 = DATEFORMAT_CURR_TZ.parse(autoRemoveDateTime);
578 } catch (ParseException pe) {
579 LOG.warn("Invalid 'autoRemoveDateTime' value: " + autoRemoveDateTime, pe);
580 }
581 notification.setAutoRemoveDateTimeValue(new Timestamp(d2.getTime()));
582 }
583 }
584
585 notification.setContent(content);
586
587 return notification;
588 } catch (XPathExpressionException xpee) {
589 throw new XmlException("Error parsing request", xpee);
590 }
591 }
592 }