001/** 002 * Copyright 2005-2016 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.core.framework.persistence.dao.GenericDao; 026import org.kuali.rice.ken.bo.NotificationBo; 027import org.kuali.rice.ken.bo.NotificationChannelBo; 028import org.kuali.rice.ken.bo.NotificationContentTypeBo; 029import org.kuali.rice.ken.bo.NotificationPriorityBo; 030import org.kuali.rice.ken.bo.NotificationProducerBo; 031import org.kuali.rice.ken.bo.NotificationRecipientBo; 032import org.kuali.rice.ken.bo.NotificationResponseBo; 033import org.kuali.rice.ken.bo.NotificationSenderBo; 034import org.kuali.rice.ken.service.NotificationContentTypeService; 035import org.kuali.rice.ken.service.NotificationMessageContentService; 036import org.kuali.rice.ken.util.CompoundNamespaceContext; 037import org.kuali.rice.ken.util.ConfiguredNamespaceContext; 038import org.kuali.rice.ken.util.NotificationConstants; 039import org.kuali.rice.ken.util.Util; 040import org.kuali.rice.kew.util.Utilities; 041import org.kuali.rice.kim.api.KimConstants.KimGroupMemberTypes; 042import org.kuali.rice.kim.api.services.KimApiServiceLocator; 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 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 NotificationBo 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 NotificationBo 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 NotificationBo 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<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 // 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 NotificationBo notification = new NotificationBo(); 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 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 /* 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.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 /* we have to look up priority and content type in the system also */ 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 /* 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(NotificationBo 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.NotificationResponseBo) 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 * 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.NotificationBo, java.lang.String) 401 */ 402 public String generateNotificationMessage(NotificationBo notification, String userRecipientId) { 403 // create a new fresh instance so we don't screw up any references 404 NotificationBo 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 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 // now marshall out to XML 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 * 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.NotificationBo) 445 */ 446 public String generateNotificationMessage(NotificationBo 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 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 // 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 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 // 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.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}