Example usage for javax.xml.xpath XPath getNamespaceContext

List of usage examples for javax.xml.xpath XPath getNamespaceContext

Introduction

In this page you can find the example usage for javax.xml.xpath XPath getNamespaceContext.

Prototype

public NamespaceContext getNamespaceContext();

Source Link

Document

Return the current namespace context.

Usage

From source file:ch.entwine.weblounge.common.impl.util.xml.XPathHelper.java

/**
 * Returns the query result or <code>null</code>.
 * /* w  ww  .java  2s.  c om*/
 * @param node
 *          the context node
 * @param xpathExpression
 *          the xpath expression
 * @param defaultValue
 *          the default value
 * @param processor
 *          the xpath engine
 * 
 * @return the selected string or <code>defaultValue</code> if the query
 *         didn't yield a result
 */
public static String valueOf(Node node, String xpathExpression, String defaultValue, XPath processor) {

    if (node == null || processor == null)
        return null;

    try {
        String value = StringUtils.trimToNull(processor.evaluate(xpathExpression, node));

        // If we are running in test mode, we may neglect namespaces
        if (value == null) {
            NamespaceContext ctx = processor.getNamespaceContext();
            if (ctx instanceof XPathNamespaceContext && ((XPathNamespaceContext) ctx).isTest()) {
                if (xpathExpression.matches("(.*)[a-zA-Z0-9]+\\:[a-zA-Z0-9]+(.*)")) {
                    String xpNs = xpathExpression.replaceAll("[a-zA-Z0-9]+\\:", "");
                    value = StringUtils.trimToNull(processor.evaluate(xpNs, node));
                }
            }
        }
        return (value != null) ? value : defaultValue;
    } catch (XPathExpressionException e) {
        logger.warn("Error when selecting '{}': {}", xpathExpression, e.getMessage());
        return null;
    }
}

From source file:ch.entwine.weblounge.common.impl.util.xml.XPathHelper.java

/**
 * Returns the query result as a <code>Node</code> or <code>null</code> if the
 * xpath expression doesn't yield a resulting node.
 * /*from  w  ww .j a  v  a2 s  .c om*/
 * @param node
 *          the context node
 * @param xpathExpression
 *          the xpath expression
 * @param processor
 *          the xpath processor
 * @return the selected node
 */
public static Node select(Node node, String xpathExpression, XPath processor) {
    if (node == null || processor == null) {
        return null;
    }
    try {
        Node result = (Node) processor.evaluate(xpathExpression, node, XPathConstants.NODE);

        // If we are running in test mode, we may neglect namespaces
        if (result == null) {
            NamespaceContext ctx = processor.getNamespaceContext();
            if (ctx instanceof XPathNamespaceContext && ((XPathNamespaceContext) ctx).isTest()) {
                if (xpathExpression.matches("(.*)[a-zA-Z0-9]+\\:[a-zA-Z0-9]+(.*)")) {
                    String xpNs = xpathExpression.replaceAll("[a-zA-Z0-9]+\\:", "");
                    result = (Node) processor.evaluate(xpNs, node, XPathConstants.NODE);
                }
            }
        }
        return result;
    } catch (XPathExpressionException e) {
        logger.warn("Error when selecting '{}' from {}", new Object[] { xpathExpression, node, e });
        return null;
    }
}

From source file:ch.entwine.weblounge.common.impl.util.xml.XPathHelper.java

/**
 * Returns the query result as a <code>NodeList</code> or <code>null</code> if
 * the xpath expression doesn't yield a result set.
 * /* www.  j  a  va 2  s  .  c  om*/
 * @param node
 *          the context node
 * @param xpathExpression
 *          the xpath expression
 * @param processor
 *          the xpath processor
 * @return the selected node
 */
public static NodeList selectList(Node node, String xpathExpression, XPath processor) {
    if (node == null || processor == null) {
        return null;
    }
    try {
        NodeList result = (NodeList) processor.evaluate(xpathExpression, node, XPathConstants.NODESET);

        // If we are running in test mode, we may neglect namespaces
        if (result == null || result.getLength() == 0) {
            NamespaceContext ctx = processor.getNamespaceContext();
            if (ctx instanceof XPathNamespaceContext && ((XPathNamespaceContext) ctx).isTest()) {
                if (xpathExpression.matches("(.*)[a-zA-Z0-9]+\\:[a-zA-Z0-9]+(.*)")) {
                    String xpNs = xpathExpression.replaceAll("[a-zA-Z0-9]+\\:", "");
                    result = (NodeList) processor.evaluate(xpNs, node, XPathConstants.NODESET);
                }
            }
        }
        return result;

    } catch (XPathExpressionException e) {
        logger.warn("Error when selecting '{}' from {}", new Object[] { xpathExpression, node, e });
        return null;
    }
}

From source file:gov.nih.nci.ncicb.tcga.dcc.qclive.common.action.validation.ClinicalXmlValidator.java

/**
 * This method parses the clinical XML file for dates to validate, regardless of formatting (whitespaces).
 *
 * @param xmlFile the XML file to validate
 * @param xPath the xpath object//from   w w w . j av  a 2 s.c  o  m
 * @param document the document parsed from the xml
 * @param context the application context
 * @param dateNameToValueMap a Map to store the dates that need to be compared later
 * @param datePrecision map to store precision of dates (day, month, or year)
 * @return <code>true</code> if the dates are all valid, <code>false</code> otherwise
 * @throws IOException
 */
protected boolean checkDateValidation(final File xmlFile, final XPath xPath, final Document document,
        final QcContext context, final Map<String, Date> dateNameToValueMap,
        final Map<String, String> datePrecision) throws IOException {

    boolean result = true;

    try {
        //Iterate on the list of element's name suffixes to validate the different dates
        final Iterator<String> xpathSuffixIterator = getDatesToValidate().iterator();
        String xpathSuffix;
        while (xpathSuffixIterator.hasNext()) {

            xpathSuffix = xpathSuffixIterator.next();
            int indexOfColon = xpathSuffix.indexOf(":");
            String namespacePrefix = null;
            if (indexOfColon > 0) {
                namespacePrefix = xpathSuffix.substring(0, indexOfColon);
            }
            // only check the date if it is in a namespace contained in this document
            if (namespacePrefix == null
                    || xPath.getNamespaceContext().getNamespaceURI(namespacePrefix) != null) {
                //Update result
                result = checkDateForXPath(document, xPath, xpathSuffix, context, dateNameToValueMap,
                        datePrecision) && result;
            }
        }

    } catch (XPathExpressionException e) {

        //Update result
        result = false;
        context.addError(MessageFormat.format(MessagePropertyType.XML_FILE_PROCESSING_ERROR, xmlFile.getName(),
                new StringBuilder().append("XPath Expression error reading '").append(xmlFile.getName())
                        .append("': ").append(e.getMessage()).toString()));
    } catch (UnexpectedInput e) {

        //Update result
        result = false;
        context.addError(MessageFormat.format(MessagePropertyType.XML_FILE_PROCESSING_ERROR, xmlFile.getName(),
                new StringBuilder().append("Unexpected input error reading '").append(xmlFile.getName())
                        .append("': ").append(e.getMessage()).toString()));
    }

    return result;
}

From source file:gov.nih.nci.ncicb.tcga.dcc.qclive.common.action.ClinicalDateObscurer.java

/**
 * This methods goes through the original XML document and obscure a few selected dates by changing the time reference
 * to a chosen Date element in the XML./*  www  .ja  v a 2s .  co m*/
 *
 * @param document the XML DOM Document
 * @param xpath an XPath instance
 * @param basisYear the new time reference year
 * @param basisMonth the new time reference month
 * @param basisDay the new time reference day
 * @param basisPrecision the time reference precision
 * @param ageAtBasisDate the age of the patient based on the new time reference
 * @param patientBirthYearAttributes the birth year attributes for the patient
 * @param bcrPatientOverCutoffAgeBarcodeList a list to store the barcode of patient whose age is over the cutoff age
 * @return the Document with the dates to be obscured replaced by an elapsed time since the new time reference
 * @throws ProcessorException
 */
private Document obscureElements(final Document document, final XPath xpath, final String basisYear,
        final String basisMonth, final String basisDay, final String basisPrecision, final long ageAtBasisDate,
        final Hashtable<String, String> patientBirthYearAttributes,
        final List<String> bcrPatientOverCutoffAgeBarcodeList) throws ProcessorException {

    //Iterate on the list of element's name suffixes to validate the different dates
    final Map<String, String> datesToObscure = getDatesToObscure(document, xpath);
    final Iterator<String> xpathSuffixIterator = datesToObscure.keySet().iterator();
    String xpathSuffix = null;
    while (xpathSuffixIterator.hasNext()) {

        xpathSuffix = xpathSuffixIterator.next();
        String elementNamePrefix = getElapsedElementBase();
        String elementNameSuffix = xpathSuffix;
        String namespacePrefix = null;
        int indexOfColon = xpathSuffix.indexOf(":");
        if (indexOfColon > 0) {
            elementNameSuffix = xpathSuffix.substring(indexOfColon + 1);
            namespacePrefix = xpathSuffix.substring(0, indexOfColon);
        }

        if (namespacePrefix == null || xpath.getNamespaceContext().getNamespaceURI(namespacePrefix) != null) {

            try {
                final String yearOfNodesAttributesExpression = getXPathExpressionIgnoringNamespace(yearOfPrefix,
                        xpathSuffix);
                final String monthOfNodesAttributesExpression = getXPathExpressionIgnoringNamespace(
                        monthOfPrefix, xpathSuffix);
                final String dayOfNodesAttributesExpression = getXPathExpressionIgnoringNamespace(dayOfPrefix,
                        xpathSuffix);

                final NodeList yearOfNodes = getNodeListFromXPathExpression(document, xpath,
                        yearOfNodesAttributesExpression);
                final NodeList monthOfNodes = getNodeListFromXPathExpression(document, xpath,
                        monthOfNodesAttributesExpression);
                final NodeList dayOfNodes = getNodeListFromXPathExpression(document, xpath,
                        dayOfNodesAttributesExpression);

                // must have the same number of year, month, and day nodes
                if (yearOfNodes.getLength() != monthOfNodes.getLength()) {
                    throw new ProcessorException(
                            getNodeListLengthMismatchErrorMessage(yearOfNodesAttributesExpression,
                                    monthOfNodesAttributesExpression, yearOfNodes, monthOfNodes));
                }

                if (yearOfNodes.getLength() != dayOfNodes.getLength()) {
                    throw new ProcessorException(
                            getNodeListLengthMismatchErrorMessage(yearOfNodesAttributesExpression,
                                    dayOfNodesAttributesExpression, yearOfNodes, dayOfNodes));
                }

                //Iterate through the selected element names that need to be obscured and obscure the dates
                for (int nodeIndex = 0; nodeIndex < yearOfNodes.getLength(); nodeIndex++) {

                    final Node yearOfNode = yearOfNodes.item(nodeIndex);
                    final Node monthOfNode = monthOfNodes.item(nodeIndex);
                    final Node dayOfNode = dayOfNodes.item(nodeIndex);
                    final Node parentNode = yearOfNode.getParentNode();

                    final String yearOf = yearOfNode.getTextContent().trim();
                    final String monthOf = monthOfNode.getTextContent().trim();
                    final String dayOf = dayOfNode.getTextContent().trim();

                    String monthOfProcurementStatus = null;
                    String yearOfOwner = null;
                    String yearOfProcurementStatus = null;

                    final Node yearOfProcurementStatusNode = yearOfNode.getAttributes()
                            .getNamedItem(PROCUREMENT_STATUS);
                    if (yearOfProcurementStatusNode != null) {
                        yearOfProcurementStatus = yearOfProcurementStatusNode.getTextContent().trim();
                    }

                    final Node yearOfOwnerNode = yearOfNode.getAttributes().getNamedItem(OWNER);
                    if (yearOfOwnerNode != null) {
                        yearOfOwner = yearOfOwnerNode.getTextContent().trim();
                    }

                    final Node monthOfProcurementStatusNode = monthOfNode.getAttributes()
                            .getNamedItem(PROCUREMENT_STATUS);
                    if (monthOfProcurementStatusNode != null) {
                        monthOfProcurementStatus = monthOfProcurementStatusNode.getTextContent().trim();
                    }

                    if (parentNode != null) {
                        // find the namespace from the yearOf node
                        String namespace = "";
                        String yearNodeName = yearOfNode.getNodeName();
                        if (yearNodeName.contains(":")) {
                            namespace = yearNodeName.substring(0, yearNodeName.indexOf(":") + 1); // include the ':'
                        }
                        //Update document

                        //Replace dayOfPrefix node by elapsedElementBase node

                        String elementValue = "";
                        String cdeAttributeValue = datesToObscure.get(xpathSuffix);
                        String ownerAttributeValue = yearOfOwner;

                        String elementPrecision = getPrecisionForElementDate(yearOf, monthOf, dayOf,
                                basisPrecision);
                        boolean elementValueFloored = false;
                        if (elementPrecision.equals(PRECISION_YEAR) || elementPrecision.equals("")) {
                            // set precision to empty since we are not going to do the calculation
                            elementPrecision = "";
                        } else {
                            Date elementDate = makeDateFromParts(yearOf, monthOf, dayOf, elementPrecision);
                            Date basisDate = makeDateFromParts(basisYear, basisMonth, basisDay,
                                    elementPrecision);
                            Long elapsedDays = calculateElapsedDaysBetween(elementDate, basisDate);

                            //The 'days to birth' value needs to be floored if it's lower than a lower bound
                            if (xpathSuffix.equals(getBirthDateName())
                                    && elapsedDays < getDaysToBirthLowerBound()) {
                                elapsedDays = getDaysToBirthLowerBound().longValue();
                                elementValueFloored = true;
                            }

                            elementValue = (elapsedDays == null ? null : String.valueOf(elapsedDays));
                        }

                        // Procurement status should be set to 'Completed' if the element has a non blank value,
                        // otherwise it should be set to the year of's procurement status
                        final String procurementStatusAttributeValue = StringUtils.isBlank(elementValue)
                                ? yearOfProcurementStatus
                                : ProcurementStatus.Completed.toString();
                        final Node yearOfNodeTierAttribute = yearOfNode.getAttributes().getNamedItem(TIER);
                        final String tierAttributeValue = (yearOfNodeTierAttribute != null
                                ? yearOfNodeTierAttribute.getTextContent().trim()
                                : null);

                        String xsdVerAttribute = getXsdVersionAttributeValue(yearOfNode);
                        final String precisionAttributeValue = elementPrecision;

                        Node daysToNode = createElementNode(document, namespace + elementNamePrefix,
                                elementNameSuffix, elementValue, cdeAttributeValue, ownerAttributeValue,
                                procurementStatusAttributeValue, tierAttributeValue, xsdVerAttribute,
                                precisionAttributeValue, elementValueFloored);

                        parentNode.replaceChild(daysToNode, dayOfNode);

                        //Remove monthOfPrefix node if <code>xpathSuffix</code> is different from <code>basisDateNameForClinical</code>,
                        //otherwise replace it by a new ageAtPrefix + <code>basisDateNameForClinical</code> Element
                        if (!xpathSuffix.equals(getBasisDateNameForClinical())) {

                            removeChildNode(parentNode, monthOfNode);
                        } else {

                            boolean ageAtBasisDateValueFloored = false;
                            String ageAtBasisDateValue = "";
                            if (ageAtBasisDate != -1) {

                                if (ageAtBasisDate > getCutoffAgeAtInitialDiagnosis()) {
                                    ageAtBasisDateValue = getCutoffAgeAtInitialDiagnosis().toString(); // the patient's age > cutoff age, floor it at the cutoff age
                                    ageAtBasisDateValueFloored = true;

                                    //add the patient barcode to the list of patient whose age is over the cutoff age
                                    bcrPatientOverCutoffAgeBarcodeList
                                            .add(getBcrPatientBarcode(document, xpath));

                                } else {
                                    ageAtBasisDateValue = String.valueOf(ageAtBasisDate);
                                }
                            }

                            final String ageAtOwnerAttributeValue = patientBirthYearAttributes.get(OWNER);
                            final String yearOfBirthProcurementStatus = patientBirthYearAttributes
                                    .get(PROCUREMENT_STATUS);

                            // Procurement status should be set to 'Completed' if the element has a non blank value,
                            // otherwise it should be set to the patient year of birth's procurement status
                            final String ageAtProcurementStatusAttributeValue = StringUtils
                                    .isBlank(ageAtBasisDateValue) ? yearOfBirthProcurementStatus
                                            : ProcurementStatus.Completed.toString();

                            final String ageAtTierAttributeValue = patientBirthYearAttributes.get(TIER);

                            final String monthOfNodeNamespace = getNamespaceFromNodeName(monthOfNode);

                            final Node ageAtNode = createElementNode(document,
                                    monthOfNodeNamespace + ageAtPrefix, elementNameSuffix, ageAtBasisDateValue,
                                    ageAtBasisDateCDE, ageAtOwnerAttributeValue,
                                    ageAtProcurementStatusAttributeValue, ageAtTierAttributeValue,
                                    xsdVerAttribute, null, ageAtBasisDateValueFloored);

                            parentNode.replaceChild(ageAtNode, monthOfNode);
                        }

                        //Remove yearOfPrefix node if:
                        // <code>xpathSuffix</code> is different from <code>basisDateNameForClinical</code>
                        if (!xpathSuffix.equals(getBasisDateNameForClinical())) {
                            removeChildNode(parentNode, yearOfNode);
                        }

                    } else {
                        final String xMsg = "The Parent Node is null for the XPath expression: " + yearOfPrefix
                                + xpathSuffix + "[" + nodeIndex + "]";
                        throw new ProcessorException(xMsg);
                    }
                }

            } catch (XPathExpressionException x) {
                throw new ProcessorException("Xpath Expression error", x);
            }
        } // else this date is in a namespace not in this document, so skip it, since it must not be in here (or would have failed validation)
    }

    return document;
}

From source file:org.kuali.rice.ken.service.impl.NotificationMessageContentServiceImpl.java

/**
 * This method is the meat of the notification message parsing.  It uses DOM to parse out the notification
 * message XML and into a Notification BO.  It handles lookup of reference objects' primary keys so that it
 * can properly populate the notification object.
 * @param bytes/*from   w w w . ja  v  a 2s .c  o  m*/
 * @return Notification
 * @throws IOException
 * @throws XmlException
 */
private NotificationBo parseNotificationRequestMessage(byte[] bytes) throws IOException, XmlException {
    /* First we'll fully parse the DOM with validation turned on */
    Document doc;
    try {
        doc = Util.parseWithNotificationEntityResolver(new InputSource(new ByteArrayInputStream(bytes)), true,
                true, notificationContentTypeService);
    } catch (ParserConfigurationException pce) {
        throw new XmlException("Error obtaining XML parser", pce);
    } catch (SAXException se) {
        throw new XmlException("Error validating notification request", se);
    }

    Element root = doc.getDocumentElement();
    /* XPath is namespace-aware, so if the DOM that XPath will be evaluating has fully qualified elements
       (because, e.g., it has been parsed with a validating DOM parser as above, then we need to set a
       "NamespaceContext" which essentially declares the defined namespace mappings to XPath.
            
       Unfortunately there is no EASY way (that I have found at least) to automatically expose the namespaces
       that have been discovered in the XML document parsed into DOM to XPath (an oversight in my opinion as
       this requires duplicate footwork to re-expose known definitions).
            
       So what we do is create a set of helper classes that will expose both the "known" core Notification system
       namespaces, as well as those that can be derived from the DOM Document (Document exposes these but through a
       different API than XPath NamespaceContext).  We create CompoundNamespaceContext that consists of both of these
       constituent namespace contexts (so that our core NamespaceContext takes precedent...nobody should be redefining
       these!).
            
       We can *then* use fully qualified XPath expressions like: /nreq:notification/nreq:channel ...
            
       (Another alternative would be to REPARSE the incoming XML with validation turned off so we can have simpler XPath
       expresssions.  This is less correct, but also not ideal as we will want to use qualified XPath expressions with
       notification content type also)
     */
    XPath xpath = XPathFactory.newInstance().newXPath();
    xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc));

    /* First parse immediate/primitive Notification member data */
    LOG.debug("URI: " + xpath.getNamespaceContext().getNamespaceURI("nreq"));
    try {
        NotificationBo notification = new NotificationBo();

        String channelName = (String) xpath.evaluate("/nreq:notification/nreq:channel", root);
        LOG.debug("CHANNELNAME: " + channelName);
        String producerName = xpath.evaluate("/nreq:notification/nreq:producer", root);

        List<String> senders = new ArrayList<String>();
        NodeList nodes = (NodeList) xpath.evaluate("/nreq:notification/nreq:senders/nreq:sender", root,
                XPathConstants.NODESET);
        for (int i = 0; i < nodes.getLength(); i++) {
            LOG.debug("sender node: " + nodes.item(i));
            LOG.debug("sender node VALUE: " + nodes.item(i).getTextContent());
            senders.add(nodes.item(i).getTextContent());
        }
        nodes = (NodeList) xpath.evaluate(
                "/nreq:notification/nreq:recipients/nreq:group|/nreq:notification/nreq:recipients/nreq:user",
                root, XPathConstants.NODESET);
        List<NotificationRecipientBo> recipients = new ArrayList<NotificationRecipientBo>();
        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            NotificationRecipientBo recipient = new NotificationRecipientBo();
            // NOTE: assumes validation has occurred; does not check validity of element name
            if (NotificationConstants.RECIPIENT_TYPES.GROUP.equalsIgnoreCase(node.getLocalName())) {
                //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.GROUP);
                recipient.setRecipientType(KimGroupMemberTypes.GROUP_MEMBER_TYPE.getCode());
                recipient.setRecipientId(KimApiServiceLocator.getGroupService()
                        .getGroupByNamespaceCodeAndName(
                                Utilities.parseGroupNamespaceCode(node.getTextContent()),
                                Utilities.parseGroupName(node.getTextContent()))
                        .getId());
            } else if (NotificationConstants.RECIPIENT_TYPES.USER.equalsIgnoreCase(node.getLocalName())) {
                //recipient.setRecipientType(NotificationConstants.RECIPIENT_TYPES.USER);
                recipient.setRecipientType(KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode());
                recipient.setRecipientId(node.getTextContent());
            } else {
                throw new XmlException("Invalid 'recipientType' value: '" + node.getLocalName()
                        + "'.  Needs to either be 'user' or 'group'");
            }
            recipient.setNotification(notification);
            recipients.add(recipient);
        }

        String deliveryType = xpath.evaluate("/nreq:notification/nreq:deliveryType", root);
        String sendDateTime = xpath.evaluate("/nreq:notification/nreq:sendDateTime", root);
        String autoRemoveDateTime = xpath.evaluate("/nreq:notification/nreq:autoRemoveDateTime", root);

        String priorityName = xpath.evaluate("/nreq:notification/nreq:priority", root);
        String title = xpath.evaluate("/nreq:notification/nreq:title", root);
        String contentTypeName = xpath.evaluate("/nreq:notification/nreq:contentType", root);

        /* Construct the Notification business object */

        if (!StringUtils.isBlank(title)) {
            notification.setTitle(title);
        }

        /* channel and producer require lookups in the system (i.e. we can't just create new instances out of whole cloth), so
           we call a helper method to retrieve references to the respective objects
         */
        NotificationChannelBo channel = Util.retrieveFieldReference("channel", "name", channelName,
                NotificationChannelBo.class, dataObjectService);
        notification.setChannel(channel);

        NotificationProducerBo producer = Util.retrieveFieldReference("producer", "name", producerName,
                NotificationProducerBo.class, dataObjectService);
        notification.setProducer(producer);

        for (String sender : senders) {
            NotificationSenderBo ns = new NotificationSenderBo();
            LOG.debug("Setting sender: " + sender);
            ns.setSenderName(sender);
            ns.setNotification(notification);
            notification.addSender(ns);
        }

        for (NotificationRecipientBo recipient : recipients) {
            LOG.debug("Setting recipient id: " + recipient.getRecipientId());
            //                recipient.setNotification(notification);
            notification.addRecipient(recipient);
        }

        /* validate the delivery type */
        if (!NotificationConstants.DELIVERY_TYPES.ACK.equalsIgnoreCase(deliveryType)
                && !NotificationConstants.DELIVERY_TYPES.FYI.equalsIgnoreCase(deliveryType)) {
            throw new XmlException(
                    "Invalid 'deliveryType' value: '" + deliveryType + "'.  Must be either 'ACK' or 'FYI'.");
        }
        notification.setDeliveryType(deliveryType);

        /* If we have gotten this far, then these dates have obviously already passed XML schema validation,
           but as that may be volatile we make sure to validate programmatically.
         */
        Date d;
        if (StringUtils.isNotBlank(sendDateTime)) {
            try {
                d = Util.parseXSDDateTime(sendDateTime);
            } catch (ParseException pe) {
                throw new XmlException("Invalid 'sendDateTime' value: " + sendDateTime, pe);
            }
            notification.setSendDateTimeValue(new Timestamp(d.getTime()));
        }
        if (StringUtils.isNotBlank(autoRemoveDateTime)) {
            try {
                d = Util.parseXSDDateTime(autoRemoveDateTime);
            } catch (ParseException pe) {
                throw new XmlException("Invalid 'autoRemoveDateTime' value: " + autoRemoveDateTime, pe);
            }
            notification.setAutoRemoveDateTimeValue(new Timestamp(d.getTime()));
        }

        /* we have to look up priority and content type in the system also */
        NotificationPriorityBo priority = Util.retrieveFieldReference("priority", "name", priorityName,
                NotificationPriorityBo.class, dataObjectService);
        notification.setPriority(priority);

        NotificationContentTypeBo contentType = Util.retrieveFieldReference("contentType", "name",
                contentTypeName, NotificationContentTypeBo.class, dataObjectService, Boolean.TRUE);
        notification.setContentType(contentType);

        /* Now handle and validate actual notification content.  This is a tricky part.
           Our job is to validate the incoming content xml blob.  However that content could be under ANY namespace
           (since we have pluggable content types).  So how can we construct an XPath expression, that refers to
           node names that are fully qualified with the correct namespace/uri, when we don't KNOW at this point what that
           correct namespace URI is?
                
           The solution is to use a namespace naming convention coupled with the defined content type name in order to generate
           the canonical namespace uri for any custom content type.
                
           ns:notification/Content<Content Type name>
                
           e.g. ns:notification/ContentSimple, or ns:notification/ContentEvent
                
           We then construct an "ephemeral" namespace prefix to use in the NamespaceContext/XPath expressions to refer to this namespace URI.
                
           e.g. contentNS_<unique number>
                
           It doesn't (shouldn't!) matter what this ephemeral namespace is.
                
           We then define a temporary NamespaceContext that consists only of this ephemeral namespace mapping, and wrap the existing
           XPath NamespaceContext (the nice one we set up above to do our original qualifizzizing) with it.  Then we are off and on our
           way and can use XPath to parse the content type of arbitrary namespace.
         */
        Map<String, String> contentTypeNamespace = new HashMap<String, String>();
        String ephemeralNamespace = "contentNS_" + System.currentTimeMillis();
        contentTypeNamespace.put(ephemeralNamespace, CONTENT_TYPE_NAMESPACE_PREFIX + contentType.getName());
        xpath.setNamespaceContext(new CompoundNamespaceContext(
                new ConfiguredNamespaceContext(contentTypeNamespace), xpath.getNamespaceContext()));
        Node contentNode = (Node) xpath.evaluate("/nreq:notification/" + ephemeralNamespace + ":content", root,
                XPathConstants.NODE);
        Element contentElement = null;
        String content = "";
        /* Since we have had to use <any processContents="lax" minOccurs="1" maxOccurs="1"/> for the content element
         * (since there is no way to specify a mandatory element of specified name, but unspecified type), we need to
         * make sure to *programmatically* enforce its existence, since schema won't (the above statement says any
         * element occuring once, but we don't want "any" element, we want an element named 'content').
         */
        if (contentNode == null) {
            throw new XmlException("The 'content' element is mandatory.");
        }
        if (contentNode != null) {
            if (!(contentNode instanceof Element)) {
                // don't know what could possibly cause this
                throw new XmlException("The 'content' node is not an Element! (???).");
            }
            contentElement = (Element) contentNode;
            /* Take the literal XML content value of the DOM node.
               This should be symmetric/reversable */
            content = XmlJotter.jotNode(contentNode, true);
        }

        notification.setContent(content);

        LOG.debug("Content type: " + contentType.getName());
        LOG.debug("Content: " + content);

        /* double check that we got content of the type that was declared, not just any valid
           content type! (e.g., can't send valid Event content for a Simple notification type)
         */
        validateContent(notification, contentType.getName(), contentElement, content);

        return notification;
    } catch (XPathExpressionException xpee) {
        throw new XmlException("Error parsing request", xpee);
    }
}