Coverage Report - org.kuali.rice.ken.service.impl.NotificationMessageContentServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
NotificationMessageContentServiceImpl
0%
0/215
0%
0/52
6.444
 
 1  
 /*
 2  
  * Copyright 2006-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 package org.kuali.rice.ken.service.impl;
 18  
 
 19  
 import com.thoughtworks.xstream.XStream;
 20  
 import com.thoughtworks.xstream.io.xml.DomDriver;
 21  
 import org.apache.commons.io.IOUtils;
 22  
 import org.apache.commons.lang.StringUtils;
 23  
 import org.apache.log4j.Logger;
 24  
 import org.kuali.rice.core.api.util.xml.XmlException;
 25  
 import org.kuali.rice.core.api.util.xml.XmlJotter;
 26  
 import org.kuali.rice.core.framework.persistence.dao.GenericDao;
 27  
 import org.kuali.rice.ken.bo.Notification;
 28  
 import org.kuali.rice.ken.bo.NotificationChannel;
 29  
 import org.kuali.rice.ken.bo.NotificationContentType;
 30  
 import org.kuali.rice.ken.bo.NotificationPriority;
 31  
 import org.kuali.rice.ken.bo.NotificationProducer;
 32  
 import org.kuali.rice.ken.bo.NotificationRecipient;
 33  
 import org.kuali.rice.ken.bo.NotificationResponse;
 34  
 import org.kuali.rice.ken.bo.NotificationSender;
 35  
 import org.kuali.rice.ken.service.NotificationContentTypeService;
 36  
 import org.kuali.rice.ken.service.NotificationMessageContentService;
 37  
 import org.kuali.rice.ken.util.CompoundNamespaceContext;
 38  
 import org.kuali.rice.ken.util.ConfiguredNamespaceContext;
 39  
 import org.kuali.rice.ken.util.NotificationConstants;
 40  
 import org.kuali.rice.ken.util.Util;
 41  
 import org.kuali.rice.kew.util.Utilities;
 42  
 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
 43  
 import org.kuali.rice.kim.util.KimConstants.KimGroupMemberTypes;
 44  
 import org.w3c.dom.Document;
 45  
 import org.w3c.dom.Element;
 46  
 import org.w3c.dom.Node;
 47  
 import org.w3c.dom.NodeList;
 48  
 import org.xml.sax.InputSource;
 49  
 import org.xml.sax.SAXException;
 50  
 
 51  
 import javax.xml.parsers.ParserConfigurationException;
 52  
 import javax.xml.xpath.XPath;
 53  
 import javax.xml.xpath.XPathConstants;
 54  
 import javax.xml.xpath.XPathExpressionException;
 55  
 import javax.xml.xpath.XPathFactory;
 56  
 import java.io.ByteArrayInputStream;
 57  
 import java.io.IOException;
 58  
 import java.io.InputStream;
 59  
 import java.sql.Timestamp;
 60  
 import java.text.DateFormat;
 61  
 import java.text.ParseException;
 62  
 import java.text.SimpleDateFormat;
 63  
 import java.util.ArrayList;
 64  
 import java.util.Date;
 65  
 import java.util.HashMap;
 66  
 import java.util.List;
 67  
 import java.util.Map;
 68  
 
 69  
 /**
 70  
  * NotificationMessageContentService implementation - uses both Xalan and XStream in various places to manage the marshalling/unmarshalling of
 71  
  * Notification data for processing by various components in the system.
 72  
  * @see NotificationMessageContentService
 73  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 74  
  */
 75  
 public class NotificationMessageContentServiceImpl implements NotificationMessageContentService {
 76  0
     private static final Logger LOG = Logger.getLogger(NotificationMessageContentServiceImpl.class);
 77  
 
 78  
     /**
 79  
      * Prefix that content type schemas should start with
 80  
      */
 81  
     static final String CONTENT_TYPE_NAMESPACE_PREFIX = "ns:notification/ContentType";
 82  
 
 83  
     // Date format of current timezone necessary for intra-system XML parsing via send form
 84  0
     private static final DateFormat DATEFORMAT_CURR_TZ = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
 85  
 
 86  
     /**
 87  
      * Our BusinessObjectDao persistence layer
 88  
      */
 89  
     private GenericDao boDao;
 90  
     /**
 91  
      * NotificationContentTypeService impl
 92  
      */
 93  
     private NotificationContentTypeService notificationContentTypeService;
 94  
 
 95  
     /**
 96  
      * Constructor which takes a GenericDao
 97  
      * Constructs a NotificationMessageContentServiceImpl.java.
 98  
      * @param boDao
 99  
      */
 100  0
     public NotificationMessageContentServiceImpl(GenericDao boDao,  NotificationContentTypeService notificationContentTypeService) {
 101  0
         this.boDao = boDao;
 102  0
         this.notificationContentTypeService = notificationContentTypeService;
 103  0
     }
 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 Notification 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  0
         byte[] bytes = notificationMessageAsXml.getBytes();
 114  
 
 115  0
         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 Notification 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  0
         byte[] bytes = IOUtils.toByteArray(stream);
 127  
 
 128  0
         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 Notification parseNotificationRequestMessage(byte[] bytes) throws IOException, XmlException {
 141  
         /* First we'll fully parse the DOM with validation turned on */
 142  
         Document doc;
 143  
         try {
 144  0
             doc = Util.parseWithNotificationEntityResolver(new InputSource(new ByteArrayInputStream(bytes)), true, true, notificationContentTypeService);
 145  0
         } catch (ParserConfigurationException pce) {
 146  0
             throw new XmlException("Error obtaining XML parser", pce);
 147  0
         } catch (SAXException se) {
 148  0
             throw new XmlException("Error validating notification request", se);
 149  0
         }
 150  
 
 151  0
         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  0
         XPath xpath = XPathFactory.newInstance().newXPath();
 173  0
         xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc));
 174  
 
 175  
         /* First parse immediate/primitive Notification member data */
 176  0
         LOG.debug("URI: " + xpath.getNamespaceContext().getNamespaceURI("nreq"));
 177  
         try {
 178  0
             String channelName = (String) xpath.evaluate("/nreq:notification/nreq:channel", root);
 179  0
             LOG.debug("CHANNELNAME: "+ channelName);
 180  0
             String producerName = xpath.evaluate("/nreq:notification/nreq:producer", root);
 181  
 
 182  0
             List<String> senders = new ArrayList<String>();
 183  0
             NodeList nodes = (NodeList) xpath.evaluate("/nreq:notification/nreq:senders/nreq:sender", root, XPathConstants.NODESET);
 184  0
             for (int i = 0; i < nodes.getLength(); i++) {
 185  0
                 LOG.debug("sender node: " + nodes.item(i));
 186  0
                 LOG.debug("sender node VALUE: " + nodes.item(i).getTextContent());
 187  0
                 senders.add(nodes.item(i).getTextContent());
 188  
             }
 189  0
             nodes = (NodeList) xpath.evaluate("/nreq:notification/nreq:recipients/nreq:group|/nreq:notification/nreq:recipients/nreq:user", root, XPathConstants.NODESET);
 190  0
             List<NotificationRecipient> recipients = new ArrayList<NotificationRecipient>();
 191  0
             for (int i = 0; i < nodes.getLength(); i++) {
 192  0
                 Node node = nodes.item(i);
 193  0
                 NotificationRecipient recipient = new NotificationRecipient();
 194  
                 // NOTE: assumes validation has occurred; does not check validity of element name
 195  0
                 if (NotificationConstants.RECIPIENT_TYPES.GROUP.equalsIgnoreCase(node.getLocalName())) {
 196  
                     //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.GROUP);
 197  0
                     recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE);
 198  0
                     recipient.setRecipientId(KimApiServiceLocator.getGroupService().getGroupByName(Utilities.parseGroupNamespaceCode(node.getTextContent()), Utilities.parseGroupName(node.getTextContent())).getId());
 199  0
                 } else if (NotificationConstants.RECIPIENT_TYPES.USER.equalsIgnoreCase(node.getLocalName())){
 200  
                     //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.USER);
 201  0
                     recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
 202  0
                     recipient.setRecipientId(node.getTextContent());
 203  
                 } else {
 204  0
                     throw new XmlException("Invalid 'recipientType' value: '" + node.getLocalName() +
 205  
                             "'.  Needs to either be 'user' or 'group'");
 206  
                 }
 207  0
                 recipients.add(recipient);
 208  
             }
 209  
 
 210  0
             String deliveryType = xpath.evaluate("/nreq:notification/nreq:deliveryType", root);
 211  0
             String sendDateTime = xpath.evaluate("/nreq:notification/nreq:sendDateTime", root);
 212  0
             String autoRemoveDateTime = xpath.evaluate("/nreq:notification/nreq:autoRemoveDateTime", root);
 213  
 
 214  0
             String priorityName = xpath.evaluate("/nreq:notification/nreq:priority", root);
 215  0
             String title = xpath.evaluate("/nreq:notification/nreq:title", root);
 216  0
             String contentTypeName = xpath.evaluate("/nreq:notification/nreq:contentType", root);
 217  
 
 218  
             /* Construct the Notification business object */
 219  
 
 220  0
             Notification notification = new Notification();
 221  
 
 222  0
             if (!StringUtils.isBlank(title)) {
 223  0
                 notification.setTitle(title);
 224  
             }
 225  
 
 226  
             /* channel and producer require lookups in the system (i.e. we can't just create new instances out of whole cloth), so
 227  
                we call a helper method to retrieve references to the respective objects
 228  
              */
 229  0
             NotificationChannel channel = Util.retrieveFieldReference("channel", "name", channelName, NotificationChannel.class, boDao);
 230  0
             notification.setChannel(channel);
 231  
 
 232  0
             NotificationProducer producer = Util.retrieveFieldReference("producer", "name", producerName, NotificationProducer.class, boDao);
 233  0
             notification.setProducer(producer);
 234  
 
 235  0
             for (String sender: senders) {
 236  0
                 NotificationSender ns = new NotificationSender();
 237  0
                 LOG.debug("Setting sender: " + sender);
 238  0
                 ns.setSenderName(sender);
 239  0
                 notification.addSender(ns);
 240  0
             }
 241  
 
 242  0
             for (NotificationRecipient recipient: recipients) {
 243  0
                 LOG.debug("Setting recipient id: "+ recipient.getRecipientId());
 244  0
                 notification.addRecipient(recipient);
 245  
             }
 246  
 
 247  
             /* validate the delivery type */
 248  0
             if(!NotificationConstants.DELIVERY_TYPES.ACK.equalsIgnoreCase(deliveryType) &&
 249  
                !NotificationConstants.DELIVERY_TYPES.FYI.equalsIgnoreCase(deliveryType)) {
 250  0
                 throw new XmlException("Invalid 'deliveryType' value: '" + deliveryType +
 251  
                     "'.  Must be either 'ACK' or 'FYI'.");
 252  
             }
 253  0
             notification.setDeliveryType(deliveryType);
 254  
 
 255  
             /* If we have gotten this far, then these dates have obviously already passed XML schema validation,
 256  
                but as that may be volatile we make sure to validate programmatically.
 257  
              */
 258  
             Date d;
 259  0
             if(StringUtils.isNotBlank(sendDateTime)) {
 260  
                 try {
 261  0
                     d = Util.parseXSDDateTime(sendDateTime);
 262  0
                 } catch (ParseException pe) {
 263  0
                     throw new XmlException("Invalid 'sendDateTime' value: " + sendDateTime, pe);
 264  0
                 }
 265  0
                 notification.setSendDateTime(new Timestamp(d.getTime()));
 266  
             }
 267  0
             if(StringUtils.isNotBlank(autoRemoveDateTime)) {
 268  
                 try {
 269  0
                     d = Util.parseXSDDateTime(autoRemoveDateTime);
 270  0
                 } catch (ParseException pe) {
 271  0
                     throw new XmlException("Invalid 'autoRemoveDateTime' value: " + autoRemoveDateTime, pe);
 272  0
                 }
 273  0
                 notification.setAutoRemoveDateTime(new Timestamp(d.getTime()));
 274  
             }
 275  
 
 276  
 
 277  
             /* we have to look up priority and content type in the system also */
 278  0
             NotificationPriority priority = Util.retrieveFieldReference("priority", "name", priorityName, NotificationPriority.class, boDao);
 279  0
             notification.setPriority(priority);
 280  
 
 281  0
             NotificationContentType contentType = Util.retrieveFieldReference("contentType", "name", contentTypeName, NotificationContentType.class, boDao);
 282  0
             notification.setContentType(contentType);
 283  
 
 284  
             /* Now handle and validate actual notification content.  This is a tricky part.
 285  
                Our job is to validate the incoming content xml blob.  However that content could be under ANY namespace
 286  
                (since we have pluggable content types).  So how can we construct an XPath expression, that refers to
 287  
                node names that are fully qualified with the correct namespace/uri, when we don't KNOW at this point what that
 288  
                correct namespace URI is?
 289  
 
 290  
                The solution is to use a namespace naming convention coupled with the defined content type name in order to generate
 291  
                the canonical namespace uri for any custom content type.
 292  
 
 293  
                ns:notification/Content<Content Type name>
 294  
 
 295  
                e.g. ns:notification/ContentSimple, or ns:notification/ContentEvent
 296  
 
 297  
                We then construct an "ephemeral" namespace prefix to use in the NamespaceContext/XPath expressions to refer to this namespace URI.
 298  
 
 299  
                e.g. contentNS_<unique number>
 300  
 
 301  
                It doesn't (shouldn't!) matter what this ephemeral namespace is.
 302  
 
 303  
                We then define a temporary NamespaceContext that consists only of this ephemeral namespace mapping, and wrap the existing
 304  
                XPath NamespaceContext (the nice one we set up above to do our original qualifizzizing) with it.  Then we are off and on our
 305  
                way and can use XPath to parse the content type of arbitrary namespace.
 306  
              */
 307  0
             Map<String, String> contentTypeNamespace = new HashMap<String, String>();
 308  0
             String ephemeralNamespace = "contentNS_" + System.currentTimeMillis();
 309  0
             contentTypeNamespace.put(ephemeralNamespace, CONTENT_TYPE_NAMESPACE_PREFIX + contentType.getName());
 310  0
             xpath.setNamespaceContext(new CompoundNamespaceContext(new ConfiguredNamespaceContext(contentTypeNamespace), xpath.getNamespaceContext()));
 311  0
             Node contentNode = (Node) xpath.evaluate("/nreq:notification/" + ephemeralNamespace + ":content", root, XPathConstants.NODE);
 312  0
             Element contentElement = null;
 313  0
             String content = "";
 314  
             /* Since we have had to use <any processContents="lax" minOccurs="1" maxOccurs="1"/> for the content element
 315  
              * (since there is no way to specify a mandatory element of specified name, but unspecified type), we need to
 316  
              * make sure to *programmatically* enforce its existence, since schema won't (the above statement says any
 317  
              * element occuring once, but we don't want "any" element, we want an element named 'content').
 318  
              */
 319  0
             if (contentNode == null) {
 320  0
                 throw new XmlException("The 'content' element is mandatory.");
 321  
             }
 322  0
             if (contentNode != null) {
 323  0
                 if (!(contentNode instanceof Element)) {
 324  
                     // don't know what could possibly cause this
 325  0
                     throw new XmlException("The 'content' node is not an Element! (???).");
 326  
                 }
 327  0
                 contentElement = (Element) contentNode;
 328  
                 /* Take the literal XML content value of the DOM node.
 329  
                    This should be symmetric/reversable */
 330  0
                 content = XmlJotter.jotNode(contentNode, true);
 331  
             }
 332  
 
 333  0
             notification.setContent(content);
 334  
 
 335  0
             LOG.debug("Content type: " + contentType.getName());
 336  0
             LOG.debug("Content: " + content);
 337  
 
 338  
             /* double check that we got content of the type that was declared, not just any valid
 339  
                content type! (e.g., can't send valid Event content for a Simple notification type)
 340  
              */
 341  0
             validateContent(notification, contentType.getName(), contentElement, content);
 342  
 
 343  0
             return notification;
 344  0
         } catch (XPathExpressionException xpee) {
 345  0
             throw new XmlException("Error parsing request", xpee);
 346  
         }
 347  
     }
 348  
 
 349  
 
 350  
 
 351  
     /**
 352  
      * This method validates the content of a notification message by matching up the namespace of the expected content type
 353  
      * to the actual namespace that is passed in as part of the XML message.
 354  
      *
 355  
      * This is possibly redundant because we are using qualified XPath expressions to obtain content under the correct namespace.
 356  
      *
 357  
      * @param notification
 358  
      * @param contentType
 359  
      * @param contentElement
 360  
      * @param content
 361  
      * @throws IOException
 362  
      * @throws XmlException
 363  
      */
 364  
     private void validateContent(Notification notification, String contentType, Element contentElement, String content) throws IOException, XmlException {
 365  
         // this debugging relies on a DOM 3 API that is only available with Xerces 2.7.1+ (TypeInfo)
 366  
         // commented out for now
 367  
         /*LOG.debug(contentElement.getSchemaTypeInfo());
 368  
         LOG.debug(contentElement.getSchemaTypeInfo().getTypeName());
 369  
         LOG.debug(contentElement.getSchemaTypeInfo().getTypeNamespace());
 370  
         LOG.debug(contentElement.getNamespaceURI());
 371  
         LOG.debug(contentElement.getLocalName());
 372  
         LOG.debug(contentElement.getNodeName());*/
 373  
 
 374  0
         String contentTypeTitleCase = Character.toTitleCase(contentType.charAt(0)) + contentType.substring(1);
 375  0
         String expectedNamespaceURI = CONTENT_TYPE_NAMESPACE_PREFIX + contentTypeTitleCase;
 376  0
         String actualNamespaceURI = contentElement.getNamespaceURI();
 377  0
         if (!actualNamespaceURI.equals(expectedNamespaceURI)) {
 378  0
             throw new XmlException("Namespace URI of 'content' node, '" + actualNamespaceURI + "', does not match expected namespace URI, '" + expectedNamespaceURI + "', for content type '" + contentType + "'");
 379  
         }
 380  0
     }
 381  
 
 382  
     /**
 383  
      * This method will marshall out the NotificationResponse object as a String of XML, using XStream.
 384  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationResponseMessage(org.kuali.rice.ken.bo.NotificationResponse)
 385  
      */
 386  
     public String generateNotificationResponseMessage(NotificationResponse response) {
 387  0
         XStream xstream = new XStream(new DomDriver());
 388  0
         xstream.alias("response", NotificationResponse.class);
 389  0
         xstream.alias("status", String.class);
 390  0
         xstream.alias("message", String.class);
 391  0
         xstream.alias("notificationId", Long.class);
 392  0
         String xml = xstream.toXML(response);
 393  0
         return xml;
 394  
     }
 395  
 
 396  
     /**
 397  
      * This method will marshall out the Notification object as a String of XML, using XStream and replaces the
 398  
      * full recipient list with just a single recipient.
 399  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.Notification, java.lang.String)
 400  
      */
 401  
     public String generateNotificationMessage(Notification notification, String userRecipientId) {
 402  
         // create a new fresh instance so we don't screw up any references
 403  0
         Notification clone = Util.cloneNotificationWithoutObjectReferences(notification);
 404  
 
 405  
         /* TODO: modify clone recipient list so that:
 406  
              1. only the specified user is listed as a recipient (no other users or groups)
 407  
              2. if the specified user was resolved from a group, make sure to include
 408  
                 that group in the list so it can be searched against for this
 409  
                 particular per-user notification
 410  
 
 411  
            Group1 --> testuser1 --> "Group1 testuser1"
 412  
                   --> testuser2 --> "Group1 testuser2"
 413  
 
 414  
         */
 415  
 
 416  
         // inject only the single specified recipient
 417  0
         if(StringUtils.isNotBlank(userRecipientId)) {
 418  0
             clone.getRecipients().clear();
 419  
 
 420  0
             NotificationRecipient recipient = new NotificationRecipient();
 421  0
             recipient.setRecipientId(userRecipientId);
 422  0
             recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
 423  
 
 424  0
             clone.getRecipients().add(recipient);
 425  
         }
 426  
 
 427  
         // now marshall out to XML
 428  0
         XStream xstream = new XStream(new DomDriver());
 429  0
         xstream.alias("notification", Notification.class);
 430  0
         xstream.alias("channel", NotificationChannel.class);
 431  0
         xstream.alias("contentType", NotificationContentType.class);
 432  0
         xstream.alias("title", String.class);
 433  0
         xstream.alias("priority", NotificationPriority.class);
 434  0
         xstream.alias("producer", NotificationProducer.class);
 435  0
         xstream.alias("recipient", NotificationRecipient.class);
 436  0
         xstream.alias("sender", NotificationSender.class);
 437  0
         String xml = xstream.toXML(clone);
 438  0
         return xml;
 439  
     }
 440  
 
 441  
     /**
 442  
      * This method will marshall out the Notification object as a String of XML, using XStream.
 443  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.Notification)
 444  
      */
 445  
     public String generateNotificationMessage(Notification notification) {
 446  0
         return generateNotificationMessage(notification, null);
 447  
     }
 448  
 
 449  
     /**
 450  
      * Uses XPath to parse out the serialized Notification xml into a Notification instance.
 451  
      * Warning: this method does NOT validate the payload content XML
 452  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationXml(byte[])
 453  
      */
 454  
     public Notification parseSerializedNotificationXml(byte[] xmlAsBytes) throws Exception {
 455  
         Document doc;
 456  0
         Notification notification = new Notification();
 457  
 
 458  
         try {
 459  0
             doc = Util.parse(new InputSource(new ByteArrayInputStream(xmlAsBytes)), false, false, null);
 460  0
         } catch (Exception pce) {
 461  0
             throw new XmlException("Error obtaining XML parser", pce);
 462  0
         }
 463  
 
 464  0
         Element root = doc.getDocumentElement();
 465  0
         XPath xpath = XPathFactory.newInstance().newXPath();
 466  0
         xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc));
 467  
 
 468  
         try {
 469  
             // pull data out of the application content
 470  0
             String title = ((String) xpath.evaluate("//notification/title", root)).trim();
 471  
 
 472  0
             String channelName = ((String) xpath.evaluate("//notification/channel/name", root)).trim();
 473  
 
 474  0
             String contentTypeName = ((String) xpath.evaluate("//notification/contentType/name", root)).trim();
 475  
 
 476  0
             String priorityName = ((String) xpath.evaluate("//notification/priority/name", root)).trim();
 477  
 
 478  0
             List<String> senders = new ArrayList<String>();
 479  0
             NodeList senderNodes = (NodeList) xpath.evaluate("//notification/senders/sender/senderName", root, XPathConstants.NODESET);
 480  0
             for (int i = 0; i < senderNodes.getLength(); i++) {
 481  0
                 senders.add(senderNodes.item(i).getTextContent().trim());
 482  
             }
 483  
 
 484  0
             String deliveryType = ((String) xpath.evaluate("//notification/deliveryType", root)).trim();
 485  0
             if(deliveryType.equalsIgnoreCase(NotificationConstants.DELIVERY_TYPES.FYI)) {
 486  0
                 deliveryType = NotificationConstants.DELIVERY_TYPES.FYI;
 487  
             } else {
 488  0
                 deliveryType = NotificationConstants.DELIVERY_TYPES.ACK;
 489  
             }
 490  
 
 491  0
             String sendDateTime = ((String) xpath.evaluate("//notification/sendDateTime", root)).trim();
 492  
 
 493  0
             String autoRemoveDateTime = ((String) xpath.evaluate("//notification/autoRemoveDateTime", root)).trim();
 494  
 
 495  0
             List<String> userRecipients = new ArrayList<String>();
 496  0
             List<String> workgroupRecipients = new ArrayList<String>();
 497  
 
 498  0
             NodeList recipientIds = (NodeList) xpath.evaluate("//notification/recipients/recipient/recipientId", root, XPathConstants.NODESET);
 499  0
             NodeList recipientTypes = (NodeList) xpath.evaluate("//notification/recipients/recipient/recipientType", root, XPathConstants.NODESET);
 500  
 
 501  0
             for (int i = 0; i < recipientIds.getLength(); i++) {
 502  0
                     if(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.equalsIgnoreCase(recipientTypes.item(i).getTextContent().trim())) {
 503  0
                         userRecipients.add(recipientIds.item(i).getTextContent().trim());
 504  
                     } else {
 505  
                         //String groupName = recipientIds.item(i).getTextContent().trim();
 506  
                         //KimGroup recipGroup = KimApiServiceLocator.getIdentityManagementService().getGroupByName(Utilities.parseGroupNamespaceCode(groupName), Utilities.parseGroupName(groupName));
 507  
                         //workgroupRecipients.add(recipGroup.getGroupId());
 508  0
                         workgroupRecipients.add(recipientIds.item(i).getTextContent().trim());
 509  
                     }
 510  
             }
 511  
 
 512  0
             String content = ((String) xpath.evaluate("//notification/content", root)).trim();
 513  
 
 514  
             // now populate the notification BO instance
 515  0
             NotificationChannel channel = Util.retrieveFieldReference("channel", "name", channelName, NotificationChannel.class, boDao);
 516  0
             notification.setChannel(channel);
 517  
 
 518  0
             NotificationPriority priority = Util.retrieveFieldReference("priority", "name", priorityName, NotificationPriority.class, boDao);
 519  0
             notification.setPriority(priority);
 520  
 
 521  0
             NotificationContentType contentType = Util.retrieveFieldReference("contentType", "name", contentTypeName, NotificationContentType.class, boDao);
 522  0
             notification.setContentType(contentType);
 523  
 
 524  0
             NotificationProducer producer = Util.retrieveFieldReference("producer", "name", NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_NAME,
 525  
                     NotificationProducer.class, boDao);
 526  0
             notification.setProducer(producer);
 527  
 
 528  0
             for (String senderName: senders) {
 529  0
                 NotificationSender ns = new NotificationSender();
 530  0
                 ns.setSenderName(senderName);
 531  0
                 notification.addSender(ns);
 532  0
             }
 533  
 
 534  0
             for (String userRecipientId: userRecipients) {
 535  0
                 NotificationRecipient recipient = new NotificationRecipient();
 536  0
                 recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
 537  0
                 recipient.setRecipientId(userRecipientId);
 538  0
                 notification.addRecipient(recipient);
 539  0
             }
 540  
 
 541  0
             for (String workgroupRecipientId: workgroupRecipients) {
 542  0
                 NotificationRecipient recipient = new NotificationRecipient();
 543  0
                 recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE);
 544  0
                 recipient.setRecipientId(workgroupRecipientId);
 545  0
                 notification.addRecipient(recipient);
 546  0
             }
 547  
 
 548  0
             if (!StringUtils.isBlank(title)) {
 549  0
                 notification.setTitle(title);
 550  
             }
 551  
 
 552  0
             notification.setDeliveryType(deliveryType);
 553  
 
 554  
             // simpledateformat is not threadsafe, have to sync and validate
 555  0
             synchronized (DATEFORMAT_CURR_TZ) {
 556  0
                 Date d = null;
 557  0
                 if(StringUtils.isNotBlank(sendDateTime)) {
 558  
                     try {
 559  0
                         d = DATEFORMAT_CURR_TZ.parse(sendDateTime);
 560  0
                     } catch (ParseException pe) {
 561  0
                         LOG.warn("Invalid 'sendDateTime' value: " + sendDateTime, pe);
 562  0
                     }
 563  0
                     notification.setSendDateTime(new Timestamp(d.getTime()));
 564  
                 }
 565  
 
 566  0
                 Date d2 = null;
 567  0
                 if(StringUtils.isNotBlank(autoRemoveDateTime)) {
 568  
                     try {
 569  0
                         d2 = DATEFORMAT_CURR_TZ.parse(autoRemoveDateTime);
 570  0
                     } catch (ParseException pe) {
 571  0
                         LOG.warn("Invalid 'autoRemoveDateTime' value: " + autoRemoveDateTime, pe);
 572  0
                     }
 573  0
                     notification.setAutoRemoveDateTime(new Timestamp(d2.getTime()));
 574  
                 }
 575  0
             }
 576  
 
 577  0
             notification.setContent(content);
 578  
 
 579  0
             return notification;
 580  0
         } catch (XPathExpressionException xpee) {
 581  0
             throw new XmlException("Error parsing request", xpee);
 582  
         }
 583  
     }
 584  
 }