001/** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.ken.xpath; 017 018import org.apache.commons.io.IOUtils; 019import org.apache.xerces.jaxp.JAXPConstants; 020import org.junit.Test; 021import org.kuali.rice.ken.test.KENTestCase; 022import org.kuali.rice.ken.util.DocumentNamespaceContext; 023import org.kuali.rice.ken.util.SimpleErrorHandler; 024import org.kuali.rice.ken.util.Util; 025import org.kuali.rice.test.BaselineTestCase.BaselineMode; 026import org.kuali.rice.test.BaselineTestCase.Mode; 027import org.w3c.dom.Document; 028import org.xml.sax.InputSource; 029 030import javax.xml.parsers.DocumentBuilder; 031import javax.xml.parsers.DocumentBuilderFactory; 032import javax.xml.xpath.XPath; 033import javax.xml.xpath.XPathConstants; 034import javax.xml.xpath.XPathFactory; 035import java.io.IOException; 036import java.io.InputStream; 037 038import static org.junit.Assert.assertEquals; 039 040 041/** 042 * Unit test that tests the affects of various document parsing (DocumentBuilderFactory) 043 * and XPath (XPath) flags, such as validation, namespace awareness, and namespace context. 044 * Lessons learned: 045 * <ul> 046 * <li>DocumentBuilder namespace awareness needs to be turned on for validation to work</li> 047 * <li>XPath absolutely requires a working NamespaceContext and qualified node names in expressions 048 * if operating against a DOM which is the result of a validating parse</li> 049 * <li>There is no apparent way to set the "default" namespace for XPath...so even when NamespaceContext 050 * is set, nodes must be qualified. The only way to obtain the "default" namespace is to explicitly 051 * qualify with an empty namespace, e.g. /:notification/:channel (which is ugly and potentially confusing)</li> 052 * <li>When deriving NamespaceContext from validated DOM, the "default" namespace must therefore be explicitly 053 * registered with a prefix (which is redundant) so the NamespaceContext lookup can succeed. The alternative 054 * is to predefine the prefix in the NamespaceContent (which can be done by using a CompositeNamespaceContenxt 055 * consisting of a ConfiguredNamespaceContext and a DocumentNamespaceContext).</li> 056 * </ul> 057 * @author Kuali Rice Team (rice.collab@kuali.org) 058 */ 059@BaselineMode(Mode.CLEAR_DB) 060public class XPathTest extends KENTestCase { 061 private static final String TEST_XML = "sample_message_event_type.xml"; 062 063 protected InputSource getTestXMLInputSource() { 064 InputStream is = XPathTest.class.getResourceAsStream(TEST_XML); 065 if (is != null) { 066 try { 067 LOG.info(IOUtils.toString(is)); 068 } catch (IOException e) { 069 throw new RuntimeException(e); 070 } finally { 071 IOUtils.closeQuietly(is); 072 } 073 074 is = XPathTest.class.getResourceAsStream(TEST_XML); 075 } 076 return new InputSource(is); 077 } 078 079 protected XPath getXPath(Document doc) { 080 XPath xpath = XPathFactory.newInstance().newXPath(); 081 if (doc != null) { 082 xpath.setNamespaceContext(new DocumentNamespaceContext(doc)); 083 } else { 084 xpath.setNamespaceContext(Util.NOTIFICATION_NAMESPACE_CONTEXT); 085 } 086 return xpath; 087 } 088 089 protected Document getDocument(boolean namespaceAware, boolean validate) throws Exception { 090 // TODO: optimize this 091 final InputSource source = getTestXMLInputSource(); 092 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 093 dbf.setValidating(validate); 094 dbf.setNamespaceAware(namespaceAware); 095 dbf.setAttribute(JAXPConstants.JAXP_SCHEMA_LANGUAGE, JAXPConstants.W3C_XML_SCHEMA); 096 DocumentBuilder db = dbf.newDocumentBuilder(); 097 LOG.info("Setting entityresolver"); 098 db.setEntityResolver(Util.getNotificationEntityResolver(services.getNotificationContentTypeService())); 099 db.setErrorHandler(new SimpleErrorHandler(LOG)); 100 return db.parse(source); 101 } 102 103 @Test 104 public void testXPathWithPlainDOM() throws Exception { 105 Document doc = getDocument(false, false); 106 XPath xpath = getXPath(null); 107 String channelName = (String) xpath.evaluate("/notification/channel", doc.getDocumentElement(), XPathConstants.STRING); 108 assertEquals("Test Channel #1", channelName); 109 } 110 @Test 111 public void testXPathWithNamespaceAwareDOM() throws Exception { 112 Document doc = getDocument(true, false); 113 XPath xpath = getXPath(null); 114 String channelName = (String) xpath.evaluate("/nreq:notification/nreq:channel", doc.getDocumentElement(), XPathConstants.STRING); 115 assertEquals("Test Channel #1", channelName); 116 } 117 @Test 118 public void testXPathWithValidatedDOMFixedNamespace() throws Exception { 119 LOG.debug("TEST"); 120 Document doc = getDocument(true, true); 121 LOG.info("Default namespace: " + doc.lookupNamespaceURI(null)); 122 XPath xpath = getXPath(null); 123 String channelName = (String) xpath.evaluate("/nreq:notification/nreq:channel", doc.getDocumentElement(), XPathConstants.STRING); 124 assertEquals("Test Channel #1", channelName); 125 } 126 @Test 127 public void testXPathWithValidatedDOMDocNamespace() throws Exception { 128 LOG.debug("TEST"); 129 Document doc = getDocument(true, true); 130 LOG.info("Default namespace: " + doc.lookupNamespaceURI(null)); 131 LOG.info("default prefix: " + doc.lookupPrefix(doc.lookupNamespaceURI(null))); 132 XPath xpath = XPathFactory.newInstance().newXPath(); 133 xpath.setNamespaceContext(Util.getNotificationNamespaceContext(doc)); 134 String channelName = (String) xpath.evaluate("/nreq:notification/nreq:channel", doc.getDocumentElement(), XPathConstants.STRING); 135 assertEquals("Test Channel #1", channelName); 136 } 137}