001/** 002 * Copyright 2005-2017 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 */ 016package org.kuali.rice.ken.service.impl; 017 018import com.thoughtworks.xstream.XStream; 019import com.thoughtworks.xstream.io.xml.DomDriver; 020import org.apache.commons.io.IOUtils; 021import org.apache.commons.lang.StringUtils; 022import org.apache.log4j.Logger; 023import org.kuali.rice.core.api.util.xml.XmlException; 024import org.kuali.rice.core.api.util.xml.XmlJotter; 025import org.kuali.rice.ken.bo.NotificationBo; 026import org.kuali.rice.ken.bo.NotificationChannelBo; 027import org.kuali.rice.ken.bo.NotificationContentTypeBo; 028import org.kuali.rice.ken.bo.NotificationPriorityBo; 029import org.kuali.rice.ken.bo.NotificationProducerBo; 030import org.kuali.rice.ken.bo.NotificationRecipientBo; 031import org.kuali.rice.ken.bo.NotificationResponseBo; 032import org.kuali.rice.ken.bo.NotificationSenderBo; 033import org.kuali.rice.ken.service.NotificationContentTypeService; 034import org.kuali.rice.ken.service.NotificationMessageContentService; 035import org.kuali.rice.ken.util.CompoundNamespaceContext; 036import org.kuali.rice.ken.util.ConfiguredNamespaceContext; 037import org.kuali.rice.ken.util.NotificationConstants; 038import org.kuali.rice.ken.util.Util; 039import org.kuali.rice.kew.util.Utilities; 040import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes; 041import org.kuali.rice.kim.api.services.KimApiServiceLocator; 042import org.kuali.rice.krad.data.DataObjectService; 043import org.w3c.dom.Document; 044import org.w3c.dom.Element; 045import org.w3c.dom.Node; 046import org.w3c.dom.NodeList; 047import org.xml.sax.InputSource; 048import org.xml.sax.SAXException; 049 050import javax.xml.parsers.ParserConfigurationException; 051import javax.xml.xpath.XPath; 052import javax.xml.xpath.XPathConstants; 053import javax.xml.xpath.XPathExpressionException; 054import javax.xml.xpath.XPathFactory; 055import java.io.ByteArrayInputStream; 056import java.io.IOException; 057import java.io.InputStream; 058import java.sql.Timestamp; 059import java.text.DateFormat; 060import java.text.ParseException; 061import java.text.SimpleDateFormat; 062import java.util.ArrayList; 063import java.util.Date; 064import java.util.HashMap; 065import java.util.List; 066import 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 */ 074public 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 DataObjectService persistence layer 087 */ 088 private DataObjectService dataObjectService; 089 /** 090 * NotificationContentTypeService impl 091 */ 092 private NotificationContentTypeService notificationContentTypeService; 093 094 /** 095 * Constructor which takes a {@link DataObjectService} 096 * Constructs a NotificationMessageContentServiceImpl.java. 097 * @param dataObjectService persistence layer 098 * @param notificationContentTypeService {@link NotificationContentTypeService} 099 */ 100 public NotificationMessageContentServiceImpl(DataObjectService dataObjectService, NotificationContentTypeService notificationContentTypeService) { 101 this.dataObjectService = dataObjectService; 102 this.notificationContentTypeService = notificationContentTypeService; 103 } 104 105 /** 106 * This method implements by taking in a String and then converting that to a byte[]; 107 * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationRequestMessage(java.lang.String) 108 */ 109 public NotificationBo parseNotificationRequestMessage(String notificationMessageAsXml) throws IOException, XmlException { 110 // this is sort of redundant...but DOM does not perform validation 111 // so we have to read all the bytes and then hand them to DOM 112 // after our first-pass validation, for a second parse 113 byte[] bytes = notificationMessageAsXml.getBytes(); 114 115 return parseNotificationRequestMessage(bytes); 116 } 117 118 /** 119 * This method implements by taking in an InputStream and then coverting that to a byte[]. 120 * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationRequestMessage(java.io.InputStream) 121 */ 122 public NotificationBo parseNotificationRequestMessage(InputStream stream) throws IOException, XmlException { 123 // this is sort of redundant...but DOM does not perform validation 124 // so we have to read all the bytes and then hand them to DOM 125 // after our first-pass validation, for a second parse 126 byte[] bytes = IOUtils.toByteArray(stream); 127 128 return parseNotificationRequestMessage(bytes); 129 } 130 131 /** 132 * This method is the meat of the notification message parsing. It uses DOM to parse out the notification 133 * message XML and into a Notification BO. It handles lookup of reference objects' primary keys so that it 134 * can properly populate the notification object. 135 * @param bytes 136 * @return Notification 137 * @throws IOException 138 * @throws XmlException 139 */ 140 private NotificationBo parseNotificationRequestMessage(byte[] bytes) throws IOException, XmlException { 141 /* First we'll fully parse the DOM with validation turned on */ 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 /* XPath is namespace-aware, so if the DOM that XPath will be evaluating has fully qualified elements 153 (because, e.g., it has been parsed with a validating DOM parser as above, then we need to set a 154 "NamespaceContext" which essentially declares the defined namespace mappings to XPath. 155 156 Unfortunately there is no EASY way (that I have found at least) to automatically expose the namespaces 157 that have been discovered in the XML document parsed into DOM to XPath (an oversight in my opinion as 158 this requires duplicate footwork to re-expose known definitions). 159 160 So what we do is create a set of helper classes that will expose both the "known" core Notification system 161 namespaces, as well as those that can be derived from the DOM Document (Document exposes these but through a 162 different API than XPath NamespaceContext). We create CompoundNamespaceContext that consists of both of these 163 constituent namespace contexts (so that our core NamespaceContext takes precedent...nobody should be redefining 164 these!). 165 166 We can *then* use fully qualified XPath expressions like: /nreq:notification/nreq:channel ... 167 168 (Another alternative would be to REPARSE the incoming XML with validation turned off so we can have simpler XPath 169 expresssions. This is less correct, but also not ideal as we will want to use qualified XPath expressions with 170 notification content type also) 171 */ 172 XPath xpath = XPathFactory.newInstance().newXPath(); 173 xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc)); 174 175 /* First parse immediate/primitive Notification member data */ 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 // NOTE: assumes validation has occurred; does not check validity of element name 197 if (NotificationConstants.RECIPIENT_TYPES.GROUP.equalsIgnoreCase(node.getLocalName())) { 198 //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.GROUP); 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 //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.USER); 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 /* Construct the Notification business object */ 224 225 226 227 if (!StringUtils.isBlank(title)) { 228 notification.setTitle(title); 229 } 230 231 /* channel and producer require lookups in the system (i.e. we can't just create new instances out of whole cloth), so 232 we call a helper method to retrieve references to the respective objects 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// recipient.setNotification(notification); 251 notification.addRecipient(recipient); 252 } 253 254 /* validate the delivery type */ 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 /* If we have gotten this far, then these dates have obviously already passed XML schema validation, 263 but as that may be volatile we make sure to validate programmatically. 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 /* we have to look up priority and content type in the system also */ 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 /* Now handle and validate actual notification content. This is a tricky part. 293 Our job is to validate the incoming content xml blob. However that content could be under ANY namespace 294 (since we have pluggable content types). So how can we construct an XPath expression, that refers to 295 node names that are fully qualified with the correct namespace/uri, when we don't KNOW at this point what that 296 correct namespace URI is? 297 298 The solution is to use a namespace naming convention coupled with the defined content type name in order to generate 299 the canonical namespace uri for any custom content type. 300 301 ns:notification/Content<Content Type name> 302 303 e.g. ns:notification/ContentSimple, or ns:notification/ContentEvent 304 305 We then construct an "ephemeral" namespace prefix to use in the NamespaceContext/XPath expressions to refer to this namespace URI. 306 307 e.g. contentNS_<unique number> 308 309 It doesn't (shouldn't!) matter what this ephemeral namespace is. 310 311 We then define a temporary NamespaceContext that consists only of this ephemeral namespace mapping, and wrap the existing 312 XPath NamespaceContext (the nice one we set up above to do our original qualifizzizing) with it. Then we are off and on our 313 way and can use XPath to parse the content type of arbitrary namespace. 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 /* Since we have had to use <any processContents="lax" minOccurs="1" maxOccurs="1"/> for the content element 323 * (since there is no way to specify a mandatory element of specified name, but unspecified type), we need to 324 * make sure to *programmatically* enforce its existence, since schema won't (the above statement says any 325 * element occuring once, but we don't want "any" element, we want an element named 'content'). 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 // don't know what could possibly cause this 333 throw new XmlException("The 'content' node is not an Element! (???)."); 334 } 335 contentElement = (Element) contentNode; 336 /* Take the literal XML content value of the DOM node. 337 This should be symmetric/reversable */ 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 /* double check that we got content of the type that was declared, not just any valid 347 content type! (e.g., can't send valid Event content for a Simple notification type) 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 * This method validates the content of a notification message by matching up the namespace of the expected content type 361 * to the actual namespace that is passed in as part of the XML message. 362 * 363 * This is possibly redundant because we are using qualified XPath expressions to obtain content under the correct namespace. 364 * 365 * @param notification 366 * @param contentType 367 * @param contentElement 368 * @param content 369 * @throws IOException 370 * @throws XmlException 371 */ 372 private void validateContent(NotificationBo notification, String contentType, Element contentElement, String content) throws IOException, XmlException { 373 // this debugging relies on a DOM 3 API that is only available with Xerces 2.7.1+ (TypeInfo) 374 // commented out for now 375 /*LOG.debug(contentElement.getSchemaTypeInfo()); 376 LOG.debug(contentElement.getSchemaTypeInfo().getTypeName()); 377 LOG.debug(contentElement.getSchemaTypeInfo().getTypeNamespace()); 378 LOG.debug(contentElement.getNamespaceURI()); 379 LOG.debug(contentElement.getLocalName()); 380 LOG.debug(contentElement.getNodeName());*/ 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 * This method will marshall out the NotificationResponse object as a String of XML, using XStream. 392 * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationResponseMessage(org.kuali.rice.ken.bo.NotificationResponseBo) 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 * This method will marshall out the Notification object as a String of XML, using XStream and replaces the 406 * full recipient list with just a single recipient. 407 * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.NotificationBo, java.lang.String) 408 */ 409 public String generateNotificationMessage(NotificationBo notification, String userRecipientId) { 410 // create a new fresh instance so we don't screw up any references 411 NotificationBo clone = Util.cloneNotificationWithoutObjectReferences(notification); 412 413 /* TODO: modify clone recipient list so that: 414 1. only the specified user is listed as a recipient (no other users or groups) 415 2. if the specified user was resolved from a group, make sure to include 416 that group in the list so it can be searched against for this 417 particular per-user notification 418 419 Group1 --> testuser1 --> "Group1 testuser1" 420 --> testuser2 --> "Group1 testuser2" 421 422 */ 423 424 // inject only the single specified recipient 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 // now marshall out to XML 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 * This method will marshall out the Notification object as a String of XML, using XStream. 451 * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.NotificationBo) 452 */ 453 public String generateNotificationMessage(NotificationBo notification) { 454 return generateNotificationMessage(notification, null); 455 } 456 457 /** 458 * Uses XPath to parse out the serialized Notification xml into a Notification instance. 459 * Warning: this method does NOT validate the payload content XML 460 * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationXml(byte[]) 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 // pull data out of the application content 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 //String groupName = recipientIds.item(i).getTextContent().trim(); 514 //KimGroup recipGroup = KimApiServiceLocator.getIdentityManagementService().getGroupByNamespaceCodeAndName(Utilities.parseGroupNamespaceCode(groupName), Utilities.parseGroupName(groupName)); 515 //workgroupRecipients.add(recipGroup.getGroupId()); 516 workgroupRecipients.add(recipientIds.item(i).getTextContent().trim()); 517 } 518 } 519 520 String content = ((String) xpath.evaluate("//notification/content", root)).trim(); 521 522 // now populate the notification BO instance 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 // simpledateformat is not threadsafe, have to sync and validate 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}