001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.ken.service.impl;
017
018 import com.thoughtworks.xstream.XStream;
019 import com.thoughtworks.xstream.io.xml.DomDriver;
020 import org.apache.commons.io.IOUtils;
021 import org.apache.commons.lang.StringUtils;
022 import org.apache.log4j.Logger;
023 import org.kuali.rice.core.api.util.xml.XmlException;
024 import org.kuali.rice.core.api.util.xml.XmlJotter;
025 import org.kuali.rice.core.framework.persistence.dao.GenericDao;
026 import org.kuali.rice.ken.bo.Notification;
027 import org.kuali.rice.ken.bo.NotificationChannel;
028 import org.kuali.rice.ken.bo.NotificationContentType;
029 import org.kuali.rice.ken.bo.NotificationPriority;
030 import org.kuali.rice.ken.bo.NotificationProducer;
031 import org.kuali.rice.ken.bo.NotificationRecipient;
032 import org.kuali.rice.ken.bo.NotificationResponse;
033 import org.kuali.rice.ken.bo.NotificationSender;
034 import org.kuali.rice.ken.service.NotificationContentTypeService;
035 import org.kuali.rice.ken.service.NotificationMessageContentService;
036 import org.kuali.rice.ken.util.CompoundNamespaceContext;
037 import org.kuali.rice.ken.util.ConfiguredNamespaceContext;
038 import org.kuali.rice.ken.util.NotificationConstants;
039 import org.kuali.rice.ken.util.Util;
040 import org.kuali.rice.kew.util.Utilities;
041 import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes;
042 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
043 import org.w3c.dom.Document;
044 import org.w3c.dom.Element;
045 import org.w3c.dom.Node;
046 import org.w3c.dom.NodeList;
047 import org.xml.sax.InputSource;
048 import org.xml.sax.SAXException;
049
050 import javax.xml.parsers.ParserConfigurationException;
051 import javax.xml.xpath.XPath;
052 import javax.xml.xpath.XPathConstants;
053 import javax.xml.xpath.XPathExpressionException;
054 import javax.xml.xpath.XPathFactory;
055 import java.io.ByteArrayInputStream;
056 import java.io.IOException;
057 import java.io.InputStream;
058 import java.sql.Timestamp;
059 import java.text.DateFormat;
060 import java.text.ParseException;
061 import java.text.SimpleDateFormat;
062 import java.util.ArrayList;
063 import java.util.Date;
064 import java.util.HashMap;
065 import java.util.List;
066 import java.util.Map;
067
068 /**
069 * NotificationMessageContentService implementation - uses both Xalan and XStream in various places to manage the marshalling/unmarshalling of
070 * Notification data for processing by various components in the system.
071 * @see NotificationMessageContentService
072 * @author Kuali Rice Team (rice.collab@kuali.org)
073 */
074 public class NotificationMessageContentServiceImpl implements NotificationMessageContentService {
075 private static final Logger LOG = Logger.getLogger(NotificationMessageContentServiceImpl.class);
076
077 /**
078 * Prefix that content type schemas should start with
079 */
080 static final String CONTENT_TYPE_NAMESPACE_PREFIX = "ns:notification/ContentType";
081
082 // Date format of current timezone necessary for intra-system XML parsing via send form
083 private static final DateFormat DATEFORMAT_CURR_TZ = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
084
085 /**
086 * Our BusinessObjectDao persistence layer
087 */
088 private GenericDao boDao;
089 /**
090 * NotificationContentTypeService impl
091 */
092 private NotificationContentTypeService notificationContentTypeService;
093
094 /**
095 * Constructor which takes a GenericDao
096 * Constructs a NotificationMessageContentServiceImpl.java.
097 * @param boDao
098 */
099 public NotificationMessageContentServiceImpl(GenericDao boDao, NotificationContentTypeService notificationContentTypeService) {
100 this.boDao = boDao;
101 this.notificationContentTypeService = notificationContentTypeService;
102 }
103
104 /**
105 * This method implements by taking in a String and then converting that to a byte[];
106 * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationRequestMessage(java.lang.String)
107 */
108 public Notification parseNotificationRequestMessage(String notificationMessageAsXml) throws IOException, XmlException {
109 // this is sort of redundant...but DOM does not perform validation
110 // so we have to read all the bytes and then hand them to DOM
111 // after our first-pass validation, for a second parse
112 byte[] bytes = notificationMessageAsXml.getBytes();
113
114 return parseNotificationRequestMessage(bytes);
115 }
116
117 /**
118 * This method implements by taking in an InputStream and then coverting that to a byte[].
119 * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationRequestMessage(java.io.InputStream)
120 */
121 public Notification parseNotificationRequestMessage(InputStream stream) throws IOException, XmlException {
122 // this is sort of redundant...but DOM does not perform validation
123 // so we have to read all the bytes and then hand them to DOM
124 // after our first-pass validation, for a second parse
125 byte[] bytes = IOUtils.toByteArray(stream);
126
127 return parseNotificationRequestMessage(bytes);
128 }
129
130 /**
131 * This method is the meat of the notification message parsing. It uses DOM to parse out the notification
132 * message XML and into a Notification BO. It handles lookup of reference objects' primary keys so that it
133 * can properly populate the notification object.
134 * @param bytes
135 * @return Notification
136 * @throws IOException
137 * @throws XmlException
138 */
139 private Notification parseNotificationRequestMessage(byte[] bytes) throws IOException, XmlException {
140 /* First we'll fully parse the DOM with validation turned on */
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 /* XPath is namespace-aware, so if the DOM that XPath will be evaluating has fully qualified elements
152 (because, e.g., it has been parsed with a validating DOM parser as above, then we need to set a
153 "NamespaceContext" which essentially declares the defined namespace mappings to XPath.
154
155 Unfortunately there is no EASY way (that I have found at least) to automatically expose the namespaces
156 that have been discovered in the XML document parsed into DOM to XPath (an oversight in my opinion as
157 this requires duplicate footwork to re-expose known definitions).
158
159 So what we do is create a set of helper classes that will expose both the "known" core Notification system
160 namespaces, as well as those that can be derived from the DOM Document (Document exposes these but through a
161 different API than XPath NamespaceContext). We create CompoundNamespaceContext that consists of both of these
162 constituent namespace contexts (so that our core NamespaceContext takes precedent...nobody should be redefining
163 these!).
164
165 We can *then* use fully qualified XPath expressions like: /nreq:notification/nreq:channel ...
166
167 (Another alternative would be to REPARSE the incoming XML with validation turned off so we can have simpler XPath
168 expresssions. This is less correct, but also not ideal as we will want to use qualified XPath expressions with
169 notification content type also)
170 */
171 XPath xpath = XPathFactory.newInstance().newXPath();
172 xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc));
173
174 /* First parse immediate/primitive Notification member data */
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<NotificationRecipient> recipients = new ArrayList<NotificationRecipient>();
190 for (int i = 0; i < nodes.getLength(); i++) {
191 Node node = nodes.item(i);
192 NotificationRecipient recipient = new NotificationRecipient();
193 // NOTE: assumes validation has occurred; does not check validity of element name
194 if (NotificationConstants.RECIPIENT_TYPES.GROUP.equalsIgnoreCase(node.getLocalName())) {
195 //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.GROUP);
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 //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.USER);
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 /* Construct the Notification business object */
220
221 Notification notification = new Notification();
222
223 if (!StringUtils.isBlank(title)) {
224 notification.setTitle(title);
225 }
226
227 /* channel and producer require lookups in the system (i.e. we can't just create new instances out of whole cloth), so
228 we call a helper method to retrieve references to the respective objects
229 */
230 NotificationChannel channel = Util.retrieveFieldReference("channel", "name", channelName, NotificationChannel.class, boDao);
231 notification.setChannel(channel);
232
233 NotificationProducer producer = Util.retrieveFieldReference("producer", "name", producerName, NotificationProducer.class, boDao);
234 notification.setProducer(producer);
235
236 for (String sender: senders) {
237 NotificationSender ns = new NotificationSender();
238 LOG.debug("Setting sender: " + sender);
239 ns.setSenderName(sender);
240 notification.addSender(ns);
241 }
242
243 for (NotificationRecipient recipient: recipients) {
244 LOG.debug("Setting recipient id: "+ recipient.getRecipientId());
245 notification.addRecipient(recipient);
246 }
247
248 /* validate the delivery type */
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 /* If we have gotten this far, then these dates have obviously already passed XML schema validation,
257 but as that may be volatile we make sure to validate programmatically.
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.setSendDateTime(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.setAutoRemoveDateTime(new Timestamp(d.getTime()));
275 }
276
277
278 /* we have to look up priority and content type in the system also */
279 NotificationPriority priority = Util.retrieveFieldReference("priority", "name", priorityName, NotificationPriority.class, boDao);
280 notification.setPriority(priority);
281
282 NotificationContentType contentType = Util.retrieveFieldReference("contentType", "name", contentTypeName, NotificationContentType.class, boDao);
283 notification.setContentType(contentType);
284
285 /* Now handle and validate actual notification content. This is a tricky part.
286 Our job is to validate the incoming content xml blob. However that content could be under ANY namespace
287 (since we have pluggable content types). So how can we construct an XPath expression, that refers to
288 node names that are fully qualified with the correct namespace/uri, when we don't KNOW at this point what that
289 correct namespace URI is?
290
291 The solution is to use a namespace naming convention coupled with the defined content type name in order to generate
292 the canonical namespace uri for any custom content type.
293
294 ns:notification/Content<Content Type name>
295
296 e.g. ns:notification/ContentSimple, or ns:notification/ContentEvent
297
298 We then construct an "ephemeral" namespace prefix to use in the NamespaceContext/XPath expressions to refer to this namespace URI.
299
300 e.g. contentNS_<unique number>
301
302 It doesn't (shouldn't!) matter what this ephemeral namespace is.
303
304 We then define a temporary NamespaceContext that consists only of this ephemeral namespace mapping, and wrap the existing
305 XPath NamespaceContext (the nice one we set up above to do our original qualifizzizing) with it. Then we are off and on our
306 way and can use XPath to parse the content type of arbitrary namespace.
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 /* Since we have had to use <any processContents="lax" minOccurs="1" maxOccurs="1"/> for the content element
316 * (since there is no way to specify a mandatory element of specified name, but unspecified type), we need to
317 * make sure to *programmatically* enforce its existence, since schema won't (the above statement says any
318 * element occuring once, but we don't want "any" element, we want an element named 'content').
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 // don't know what could possibly cause this
326 throw new XmlException("The 'content' node is not an Element! (???).");
327 }
328 contentElement = (Element) contentNode;
329 /* Take the literal XML content value of the DOM node.
330 This should be symmetric/reversable */
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 /* double check that we got content of the type that was declared, not just any valid
340 content type! (e.g., can't send valid Event content for a Simple notification type)
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 * This method validates the content of a notification message by matching up the namespace of the expected content type
354 * to the actual namespace that is passed in as part of the XML message.
355 *
356 * This is possibly redundant because we are using qualified XPath expressions to obtain content under the correct namespace.
357 *
358 * @param notification
359 * @param contentType
360 * @param contentElement
361 * @param content
362 * @throws IOException
363 * @throws XmlException
364 */
365 private void validateContent(Notification notification, String contentType, Element contentElement, String content) throws IOException, XmlException {
366 // this debugging relies on a DOM 3 API that is only available with Xerces 2.7.1+ (TypeInfo)
367 // commented out for now
368 /*LOG.debug(contentElement.getSchemaTypeInfo());
369 LOG.debug(contentElement.getSchemaTypeInfo().getTypeName());
370 LOG.debug(contentElement.getSchemaTypeInfo().getTypeNamespace());
371 LOG.debug(contentElement.getNamespaceURI());
372 LOG.debug(contentElement.getLocalName());
373 LOG.debug(contentElement.getNodeName());*/
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 * This method will marshall out the NotificationResponse object as a String of XML, using XStream.
385 * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationResponseMessage(org.kuali.rice.ken.bo.NotificationResponse)
386 */
387 public String generateNotificationResponseMessage(NotificationResponse response) {
388 XStream xstream = new XStream(new DomDriver());
389 xstream.alias("response", NotificationResponse.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 * This method will marshall out the Notification object as a String of XML, using XStream and replaces the
399 * full recipient list with just a single recipient.
400 * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.Notification, java.lang.String)
401 */
402 public String generateNotificationMessage(Notification notification, String userRecipientId) {
403 // create a new fresh instance so we don't screw up any references
404 Notification clone = Util.cloneNotificationWithoutObjectReferences(notification);
405
406 /* TODO: modify clone recipient list so that:
407 1. only the specified user is listed as a recipient (no other users or groups)
408 2. if the specified user was resolved from a group, make sure to include
409 that group in the list so it can be searched against for this
410 particular per-user notification
411
412 Group1 --> testuser1 --> "Group1 testuser1"
413 --> testuser2 --> "Group1 testuser2"
414
415 */
416
417 // inject only the single specified recipient
418 if(StringUtils.isNotBlank(userRecipientId)) {
419 clone.getRecipients().clear();
420
421 NotificationRecipient recipient = new NotificationRecipient();
422 recipient.setRecipientId(userRecipientId);
423 recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
424
425 clone.getRecipients().add(recipient);
426 }
427
428 // now marshall out to XML
429 XStream xstream = new XStream(new DomDriver());
430 xstream.alias("notification", Notification.class);
431 xstream.alias("channel", NotificationChannel.class);
432 xstream.alias("contentType", NotificationContentType.class);
433 xstream.alias("title", String.class);
434 xstream.alias("priority", NotificationPriority.class);
435 xstream.alias("producer", NotificationProducer.class);
436 xstream.alias("recipient", NotificationRecipient.class);
437 xstream.alias("sender", NotificationSender.class);
438 String xml = xstream.toXML(clone);
439 return xml;
440 }
441
442 /**
443 * This method will marshall out the Notification object as a String of XML, using XStream.
444 * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.Notification)
445 */
446 public String generateNotificationMessage(Notification notification) {
447 return generateNotificationMessage(notification, null);
448 }
449
450 /**
451 * Uses XPath to parse out the serialized Notification xml into a Notification instance.
452 * Warning: this method does NOT validate the payload content XML
453 * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationXml(byte[])
454 */
455 public Notification parseSerializedNotificationXml(byte[] xmlAsBytes) throws Exception {
456 Document doc;
457 Notification notification = new Notification();
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 // pull data out of the application content
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 //String groupName = recipientIds.item(i).getTextContent().trim();
507 //KimGroup recipGroup = KimApiServiceLocator.getIdentityManagementService().getGroupByNamespaceCodeAndName(Utilities.parseGroupNamespaceCode(groupName), Utilities.parseGroupName(groupName));
508 //workgroupRecipients.add(recipGroup.getGroupId());
509 workgroupRecipients.add(recipientIds.item(i).getTextContent().trim());
510 }
511 }
512
513 String content = ((String) xpath.evaluate("//notification/content", root)).trim();
514
515 // now populate the notification BO instance
516 NotificationChannel channel = Util.retrieveFieldReference("channel", "name", channelName, NotificationChannel.class, boDao);
517 notification.setChannel(channel);
518
519 NotificationPriority priority = Util.retrieveFieldReference("priority", "name", priorityName, NotificationPriority.class, boDao);
520 notification.setPriority(priority);
521
522 NotificationContentType contentType = Util.retrieveFieldReference("contentType", "name", contentTypeName, NotificationContentType.class, boDao);
523 notification.setContentType(contentType);
524
525 NotificationProducer producer = Util.retrieveFieldReference("producer", "name", NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_NAME,
526 NotificationProducer.class, boDao);
527 notification.setProducer(producer);
528
529 for (String senderName: senders) {
530 NotificationSender ns = new NotificationSender();
531 ns.setSenderName(senderName);
532 notification.addSender(ns);
533 }
534
535 for (String userRecipientId: userRecipients) {
536 NotificationRecipient recipient = new NotificationRecipient();
537 recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
538 recipient.setRecipientId(userRecipientId);
539 notification.addRecipient(recipient);
540 }
541
542 for (String workgroupRecipientId: workgroupRecipients) {
543 NotificationRecipient recipient = new NotificationRecipient();
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 // simpledateformat is not threadsafe, have to sync and validate
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.setSendDateTime(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.setAutoRemoveDateTime(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 }