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