Coverage Report - org.kuali.rice.ken.service.impl.NotificationMessageContentServiceImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
NotificationMessageContentServiceImpl
0%
0/218
0%
0/52
6.667
 
 1  
 /*
 2  
  * Copyright 2007 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  
 package org.kuali.rice.ken.service.impl;
 17  
 
 18  
 import java.io.ByteArrayInputStream;
 19  
 import java.io.IOException;
 20  
 import java.io.InputStream;
 21  
 import java.sql.Timestamp;
 22  
 import java.text.DateFormat;
 23  
 import java.text.ParseException;
 24  
 import java.text.SimpleDateFormat;
 25  
 import java.util.ArrayList;
 26  
 import java.util.Date;
 27  
 import java.util.HashMap;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 
 31  
 import javax.xml.parsers.ParserConfigurationException;
 32  
 import javax.xml.transform.TransformerException;
 33  
 import javax.xml.xpath.XPath;
 34  
 import javax.xml.xpath.XPathConstants;
 35  
 import javax.xml.xpath.XPathExpressionException;
 36  
 import javax.xml.xpath.XPathFactory;
 37  
 
 38  
 import org.apache.commons.lang.StringUtils;
 39  
 import org.apache.log4j.Logger;
 40  
 import org.kuali.rice.core.dao.GenericDao;
 41  
 import org.kuali.rice.ken.bo.Notification;
 42  
 import org.kuali.rice.ken.bo.NotificationChannel;
 43  
 import org.kuali.rice.ken.bo.NotificationContentType;
 44  
 import org.kuali.rice.ken.bo.NotificationPriority;
 45  
 import org.kuali.rice.ken.bo.NotificationProducer;
 46  
 import org.kuali.rice.ken.bo.NotificationRecipient;
 47  
 import org.kuali.rice.ken.bo.NotificationResponse;
 48  
 import org.kuali.rice.ken.bo.NotificationSender;
 49  
 import org.kuali.rice.ken.exception.InvalidXMLException;
 50  
 import org.kuali.rice.ken.service.NotificationContentTypeService;
 51  
 import org.kuali.rice.ken.service.NotificationMessageContentService;
 52  
 import org.kuali.rice.ken.util.CompoundNamespaceContext;
 53  
 import org.kuali.rice.ken.util.ConfiguredNamespaceContext;
 54  
 import org.kuali.rice.ken.util.NotificationConstants;
 55  
 import org.kuali.rice.ken.util.Util;
 56  
 import org.kuali.rice.kew.util.Utilities;
 57  
 import org.kuali.rice.kim.service.KIMServiceLocator;
 58  
 import org.kuali.rice.kim.util.KimConstants.KimGroupMemberTypes;
 59  
 import org.w3c.dom.Document;
 60  
 import org.w3c.dom.Element;
 61  
 import org.w3c.dom.Node;
 62  
 import org.w3c.dom.NodeList;
 63  
 import org.xml.sax.InputSource;
 64  
 import org.xml.sax.SAXException;
 65  
 
 66  
 import com.thoughtworks.xstream.XStream;
 67  
 import com.thoughtworks.xstream.io.xml.DomDriver;
 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, InvalidXMLException {
 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, InvalidXMLException {
 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 = Util.readFully(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 InvalidXMLException
 139  
      */
 140  
     private Notification parseNotificationRequestMessage(byte[] bytes) throws IOException, InvalidXMLException {
 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 InvalidXMLException("Error obtaining XML parser", pce);
 147  0
         } catch (SAXException se) {
 148  0
             throw new InvalidXMLException("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(KIMServiceLocator.getIdentityManagementService().getGroupByName(Utilities.parseGroupNamespaceCode(node.getTextContent()), Utilities.parseGroupName(node.getTextContent())).getGroupId());
 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 InvalidXMLException("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 InvalidXMLException("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 InvalidXMLException("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 InvalidXMLException("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 InvalidXMLException("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 InvalidXMLException("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  
                 try {
 331  0
                     content = Util.writeNode(contentNode, true);
 332  0
                 } catch (TransformerException te) {
 333  0
                     throw new InvalidXMLException("Error serializing notification request content.", te);
 334  0
                 }
 335  
             }
 336  
 
 337  0
             notification.setContent(content);
 338  
 
 339  0
             LOG.debug("Content type: " + contentType.getName());
 340  0
             LOG.debug("Content: " + content);
 341  
 
 342  
             /* double check that we got content of the type that was declared, not just any valid
 343  
                content type! (e.g., can't send valid Event content for a Simple notification type)
 344  
              */
 345  0
             validateContent(notification, contentType.getName(), contentElement, content);
 346  
 
 347  0
             return notification;
 348  0
         } catch (XPathExpressionException xpee) {
 349  0
             throw new InvalidXMLException("Error parsing request", xpee);
 350  
         }
 351  
     }
 352  
 
 353  
 
 354  
 
 355  
     /**
 356  
      * This method validates the content of a notification message by matching up the namespace of the expected content type
 357  
      * to the actual namespace that is passed in as part of the XML message.
 358  
      *
 359  
      * This is possibly redundant because we are using qualified XPath expressions to obtain content under the correct namespace.
 360  
      *
 361  
      * @param notification
 362  
      * @param contentType
 363  
      * @param contentElement
 364  
      * @param content
 365  
      * @throws IOException
 366  
      * @throws InvalidXMLException
 367  
      */
 368  
     private void validateContent(Notification notification, String contentType, Element contentElement, String content) throws IOException, InvalidXMLException {
 369  
         // this debugging relies on a DOM 3 API that is only available with Xerces 2.7.1+ (TypeInfo)
 370  
         // commented out for now
 371  
         /*LOG.debug(contentElement.getSchemaTypeInfo());
 372  
         LOG.debug(contentElement.getSchemaTypeInfo().getTypeName());
 373  
         LOG.debug(contentElement.getSchemaTypeInfo().getTypeNamespace());
 374  
         LOG.debug(contentElement.getNamespaceURI());
 375  
         LOG.debug(contentElement.getLocalName());
 376  
         LOG.debug(contentElement.getNodeName());*/
 377  
 
 378  0
         String contentTypeTitleCase = Character.toTitleCase(contentType.charAt(0)) + contentType.substring(1);
 379  0
         String expectedNamespaceURI = CONTENT_TYPE_NAMESPACE_PREFIX + contentTypeTitleCase;
 380  0
         String actualNamespaceURI = contentElement.getNamespaceURI();
 381  0
         if (!actualNamespaceURI.equals(expectedNamespaceURI)) {
 382  0
             throw new InvalidXMLException("Namespace URI of 'content' node, '" + actualNamespaceURI + "', does not match expected namespace URI, '" + expectedNamespaceURI + "', for content type '" + contentType + "'");
 383  
         }
 384  0
     }
 385  
 
 386  
     /**
 387  
      * This method will marshall out the NotificationResponse object as a String of XML, using XStream.
 388  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationResponseMessage(org.kuali.rice.ken.bo.NotificationResponse)
 389  
      */
 390  
     public String generateNotificationResponseMessage(NotificationResponse response) {
 391  0
         XStream xstream = new XStream(new DomDriver());
 392  0
         xstream.alias("response", NotificationResponse.class);
 393  0
         xstream.alias("status", String.class);
 394  0
         xstream.alias("message", String.class);
 395  0
         xstream.alias("notificationId", Long.class);
 396  0
         String xml = xstream.toXML(response);
 397  0
         return xml;
 398  
     }
 399  
 
 400  
     /**
 401  
      * This method will marshall out the Notification object as a String of XML, using XStream and replaces the
 402  
      * full recipient list with just a single recipient.
 403  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.Notification, java.lang.String)
 404  
      */
 405  
     public String generateNotificationMessage(Notification notification, String userRecipientId) {
 406  
         // create a new fresh instance so we don't screw up any references
 407  0
         Notification clone = Util.cloneNotificationWithoutObjectReferences(notification);
 408  
 
 409  
         /* TODO: modify clone recipient list so that:
 410  
              1. only the specified user is listed as a recipient (no other users or groups)
 411  
              2. if the specified user was resolved from a group, make sure to include
 412  
                 that group in the list so it can be searched against for this
 413  
                 particular per-user notification
 414  
 
 415  
            Group1 --> testuser1 --> "Group1 testuser1"
 416  
                   --> testuser2 --> "Group1 testuser2"
 417  
 
 418  
         */
 419  
 
 420  
         // inject only the single specified recipient
 421  0
         if(StringUtils.isNotBlank(userRecipientId)) {
 422  0
             clone.getRecipients().clear();
 423  
 
 424  0
             NotificationRecipient recipient = new NotificationRecipient();
 425  0
             recipient.setRecipientId(userRecipientId);
 426  0
             recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
 427  
 
 428  0
             clone.getRecipients().add(recipient);
 429  
         }
 430  
 
 431  
         // now marshall out to XML
 432  0
         XStream xstream = new XStream(new DomDriver());
 433  0
         xstream.alias("notification", Notification.class);
 434  0
         xstream.alias("channel", NotificationChannel.class);
 435  0
         xstream.alias("contentType", NotificationContentType.class);
 436  0
         xstream.alias("title", String.class);
 437  0
         xstream.alias("priority", NotificationPriority.class);
 438  0
         xstream.alias("producer", NotificationProducer.class);
 439  0
         xstream.alias("recipient", NotificationRecipient.class);
 440  0
         xstream.alias("sender", NotificationSender.class);
 441  0
         String xml = xstream.toXML(clone);
 442  0
         return xml;
 443  
     }
 444  
 
 445  
     /**
 446  
      * This method will marshall out the Notification object as a String of XML, using XStream.
 447  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#generateNotificationMessage(org.kuali.rice.ken.bo.Notification)
 448  
      */
 449  
     public String generateNotificationMessage(Notification notification) {
 450  0
         return generateNotificationMessage(notification, null);
 451  
     }
 452  
 
 453  
     /**
 454  
      * Uses XPath to parse out the serialized Notification xml into a Notification instance.
 455  
      * Warning: this method does NOT validate the payload content XML
 456  
      * @see org.kuali.rice.ken.service.NotificationMessageContentService#parseNotificationXml(byte[])
 457  
      */
 458  
     public Notification parseSerializedNotificationXml(byte[] xmlAsBytes) throws Exception {
 459  
         Document doc;
 460  0
         Notification notification = new Notification();
 461  
 
 462  
         try {
 463  0
             doc = Util.parse(new InputSource(new ByteArrayInputStream(xmlAsBytes)), false, false, null);
 464  0
         } catch (Exception pce) {
 465  0
             throw new InvalidXMLException("Error obtaining XML parser", pce);
 466  0
         }
 467  
 
 468  0
         Element root = doc.getDocumentElement();
 469  0
         XPath xpath = XPathFactory.newInstance().newXPath();
 470  0
         xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc));
 471  
 
 472  
         try {
 473  
             // pull data out of the application content
 474  0
             String title = ((String) xpath.evaluate("//notification/title", root)).trim();
 475  
 
 476  0
             String channelName = ((String) xpath.evaluate("//notification/channel/name", root)).trim();
 477  
 
 478  0
             String contentTypeName = ((String) xpath.evaluate("//notification/contentType/name", root)).trim();
 479  
 
 480  0
             String priorityName = ((String) xpath.evaluate("//notification/priority/name", root)).trim();
 481  
 
 482  0
             List<String> senders = new ArrayList<String>();
 483  0
             NodeList senderNodes = (NodeList) xpath.evaluate("//notification/senders/sender/senderName", root, XPathConstants.NODESET);
 484  0
             for (int i = 0; i < senderNodes.getLength(); i++) {
 485  0
                 senders.add(senderNodes.item(i).getTextContent().trim());
 486  
             }
 487  
 
 488  0
             String deliveryType = ((String) xpath.evaluate("//notification/deliveryType", root)).trim();
 489  0
             if(deliveryType.equalsIgnoreCase(NotificationConstants.DELIVERY_TYPES.FYI)) {
 490  0
                 deliveryType = NotificationConstants.DELIVERY_TYPES.FYI;
 491  
             } else {
 492  0
                 deliveryType = NotificationConstants.DELIVERY_TYPES.ACK;
 493  
             }
 494  
 
 495  0
             String sendDateTime = ((String) xpath.evaluate("//notification/sendDateTime", root)).trim();
 496  
 
 497  0
             String autoRemoveDateTime = ((String) xpath.evaluate("//notification/autoRemoveDateTime", root)).trim();
 498  
 
 499  0
             List<String> userRecipients = new ArrayList<String>();
 500  0
             List<String> workgroupRecipients = new ArrayList<String>();
 501  
 
 502  0
             NodeList recipientIds = (NodeList) xpath.evaluate("//notification/recipients/recipient/recipientId", root, XPathConstants.NODESET);
 503  0
             NodeList recipientTypes = (NodeList) xpath.evaluate("//notification/recipients/recipient/recipientType", root, XPathConstants.NODESET);
 504  
 
 505  0
             for (int i = 0; i < recipientIds.getLength(); i++) {
 506  0
                     if(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.equalsIgnoreCase(recipientTypes.item(i).getTextContent().trim())) {
 507  0
                         userRecipients.add(recipientIds.item(i).getTextContent().trim());
 508  
                     } else {
 509  
                         //String groupName = recipientIds.item(i).getTextContent().trim();
 510  
                         //KimGroup recipGroup = KIMServiceLocator.getIdentityManagementService().getGroupByName(Utilities.parseGroupNamespaceCode(groupName), Utilities.parseGroupName(groupName));
 511  
                         //workgroupRecipients.add(recipGroup.getGroupId());
 512  0
                         workgroupRecipients.add(recipientIds.item(i).getTextContent().trim());
 513  
                     }
 514  
             }
 515  
 
 516  0
             String content = ((String) xpath.evaluate("//notification/content", root)).trim();
 517  
 
 518  
             // now populate the notification BO instance
 519  0
             NotificationChannel channel = Util.retrieveFieldReference("channel", "name", channelName, NotificationChannel.class, boDao);
 520  0
             notification.setChannel(channel);
 521  
 
 522  0
             NotificationPriority priority = Util.retrieveFieldReference("priority", "name", priorityName, NotificationPriority.class, boDao);
 523  0
             notification.setPriority(priority);
 524  
 
 525  0
             NotificationContentType contentType = Util.retrieveFieldReference("contentType", "name", contentTypeName, NotificationContentType.class, boDao);
 526  0
             notification.setContentType(contentType);
 527  
 
 528  0
             NotificationProducer producer = Util.retrieveFieldReference("producer", "name", NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_NAME,
 529  
                     NotificationProducer.class, boDao);
 530  0
             notification.setProducer(producer);
 531  
 
 532  0
             for (String senderName: senders) {
 533  0
                 NotificationSender ns = new NotificationSender();
 534  0
                 ns.setSenderName(senderName);
 535  0
                 notification.addSender(ns);
 536  0
             }
 537  
 
 538  0
             for (String userRecipientId: userRecipients) {
 539  0
                 NotificationRecipient recipient = new NotificationRecipient();
 540  0
                 recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE);
 541  0
                 recipient.setRecipientId(userRecipientId);
 542  0
                 notification.addRecipient(recipient);
 543  0
             }
 544  
 
 545  0
             for (String workgroupRecipientId: workgroupRecipients) {
 546  0
                 NotificationRecipient recipient = new NotificationRecipient();
 547  0
                 recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE);
 548  0
                 recipient.setRecipientId(workgroupRecipientId);
 549  0
                 notification.addRecipient(recipient);
 550  0
             }
 551  
 
 552  0
             if (!StringUtils.isBlank(title)) {
 553  0
                 notification.setTitle(title);
 554  
             }
 555  
 
 556  0
             notification.setDeliveryType(deliveryType);
 557  
 
 558  
             // simpledateformat is not threadsafe, have to sync and validate
 559  0
             synchronized (DATEFORMAT_CURR_TZ) {
 560  0
                 Date d = null;
 561  0
                 if(StringUtils.isNotBlank(sendDateTime)) {
 562  
                     try {
 563  0
                         d = DATEFORMAT_CURR_TZ.parse(sendDateTime);
 564  0
                     } catch (ParseException pe) {
 565  0
                         LOG.warn("Invalid 'sendDateTime' value: " + sendDateTime, pe);
 566  0
                     }
 567  0
                     notification.setSendDateTime(new Timestamp(d.getTime()));
 568  
                 }
 569  
 
 570  0
                 Date d2 = null;
 571  0
                 if(StringUtils.isNotBlank(autoRemoveDateTime)) {
 572  
                     try {
 573  0
                         d2 = DATEFORMAT_CURR_TZ.parse(autoRemoveDateTime);
 574  0
                     } catch (ParseException pe) {
 575  0
                         LOG.warn("Invalid 'autoRemoveDateTime' value: " + autoRemoveDateTime, pe);
 576  0
                     }
 577  0
                     notification.setAutoRemoveDateTime(new Timestamp(d2.getTime()));
 578  
                 }
 579  0
             }
 580  
 
 581  0
             notification.setContent(content);
 582  
 
 583  0
             return notification;
 584  0
         } catch (XPathExpressionException xpee) {
 585  0
             throw new InvalidXMLException("Error parsing request", xpee);
 586  
         }
 587  
     }
 588  
 }