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 }