1 /**
2 * Copyright 2005-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 package org.kuali.rice.kew.xml;
17
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertNotNull;
20 import static org.junit.Assert.assertNull;
21
22 import java.io.StringReader;
23
24 import javax.xml.xpath.XPath;
25 import javax.xml.xpath.XPathConstants;
26 import javax.xml.xpath.XPathExpressionException;
27 import javax.xml.xpath.XPathFactory;
28
29 import org.junit.Test;
30 import org.kuali.rice.test.BaseRiceTestCase;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.NodeList;
33 import org.xml.sax.InputSource;
34
35
36 public class XPathTest extends BaseRiceTestCase {
37
38 private static final String XSTREAM_SAFE_PREFIX = "wf:xstreamsafe('";
39 private static final String XSTREAM_SAFE_SUFFIX = "')";
40 public static final String XSTREAM_MATCH_ANYWHERE_PREFIX = "//";
41 public static final String XSTREAM_MATCH_RELATIVE_PREFIX = "./";
42
43 private static final String TEST_DOC = "<root name=\"root\">\n" +
44 " <child name=\"child1\">\n" +
45 " <child_1 name=\"child1_1\">\n" +
46 " <closedSimple/>\n" +
47 " <emptySimple></emptySimple>\n" +
48 " <textSimple>some text 1</textSimple>\n" +
49 " </child_1>\n" +
50 " </child>\n" +
51 " <child name=\"child2\">\n" +
52 " <child_2 name=\"child2_1\">\n" +
53 " <closedSimple/>\n" +
54 " <emptySimple></emptySimple>\n" +
55 " <textSimple>some text 2</textSimple>\n" +
56 " </child_2>\n" +
57 " </child>\n" +
58 "</root>";
59
60 private static final String TEST_ATTRIBUTE_DOC = "<root name=\"root\">\n" +
61 " <field name=\"one\" type=\"ALL\"/>\n" +
62 " <field name=\"two\" type=\"REPORT\"/>\n" +
63 " <field name=\"three\"/>\n" +
64 "</root>";
65
66 private static final XPath XPATH = XPathFactory.newInstance().newXPath();
67
68 private static final InputSource getTestInputSource() {
69 return new InputSource(new StringReader(TEST_DOC));
70 }
71
72 @Test public void testAttributeAbsence() throws XPathExpressionException {
73 NodeList nodes = (NodeList) XPATH.evaluate("/root/child[not(@nonExistentAttribute)]", getTestInputSource(), XPathConstants.NODESET);
74 assertEquals(2, nodes.getLength());
75 assertEquals("child1", nodes.item(0).getAttributes().getNamedItem("name").getNodeValue());
76 assertEquals("child2", nodes.item(1).getAttributes().getNamedItem("name").getNodeValue());
77
78 // now try with an equivalent compound expression
79 nodes = (NodeList) XPATH.evaluate("/root/*[local-name(.) = 'child' or (@nonExistentAttribute)]", getTestInputSource(), XPathConstants.NODESET);
80 assertEquals(2, nodes.getLength());
81 assertEquals("child1", nodes.item(0).getAttributes().getNamedItem("name").getNodeValue());
82 assertEquals("child2", nodes.item(1).getAttributes().getNamedItem("name").getNodeValue());
83
84 nodes = (NodeList) XPATH.evaluate("/root/child[not(@name)]", getTestInputSource(), XPathConstants.NODE);
85 assertNull(nodes);
86
87 // now use a more specific test document
88 nodes = (NodeList) XPATH.evaluate("/root/field[@type='ALL' or not(@type)]", new InputSource(new StringReader(TEST_ATTRIBUTE_DOC)), XPathConstants.NODESET);
89 assertEquals(2, nodes.getLength());
90 assertEquals("one", nodes.item(0).getAttributes().getNamedItem("name").getNodeValue());
91 assertEquals("three", nodes.item(1).getAttributes().getNamedItem("name").getNodeValue());
92 }
93
94 @Test public void testSelectJustChilds() throws XPathExpressionException {
95 NodeList nodes = (NodeList) XPATH.evaluate("/root/child", getTestInputSource(), XPathConstants.NODESET);
96 assertEquals(2, nodes.getLength());
97 assertEquals("child1", nodes.item(0).getAttributes().getNamedItem("name").getNodeValue());
98 assertEquals("child2", nodes.item(1).getAttributes().getNamedItem("name").getNodeValue());
99 }
100
101 @Test public void testSelectAbsoluteChild() throws XPathExpressionException {
102 Node node = (Node) XPATH.evaluate("/root/child", getTestInputSource(), XPathConstants.NODE);
103 assertEquals("child1", node.getAttributes().getNamedItem("name").getNodeValue());
104 }
105
106 @Test public void testSelectAnyChild() throws XPathExpressionException {
107 Node anyNode = (Node) XPATH.evaluate("//child", getTestInputSource(), XPathConstants.NODE);
108 assertEquals("child1", anyNode.getAttributes().getNamedItem("name").getNodeValue());
109 }
110
111 @Test public void testNonexistent() throws XPathExpressionException {
112 final String expr = "//child/child_1/nonExistent";
113 Node nonexistent = (Node) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.NODE);
114 assertNull(nonexistent);
115 String valueOfNonexistentElement = (String) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.STRING);
116 // a non-existent element does not have a 'null' text value but a zero-length string
117 assertEquals("", valueOfNonexistentElement);
118 }
119
120 @Test public void testClosedSimple() throws XPathExpressionException {
121 final String expr = "//child/child_1/closedSimple";
122 Node closedSimple = (Node) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.NODE);
123 assertNotNull(closedSimple);
124 assertNull(closedSimple.getFirstChild());
125 String valueOfClosedTag = (String) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.STRING);
126 // a closed element does not have a 'null' text value but a zero-length string
127 assertEquals("", valueOfClosedTag);
128 }
129
130 @Test public void testEmptySimple() throws XPathExpressionException {
131 final String expr = "//child/child_1/emptySimple";
132 Node emptySimple = (Node) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.NODE);
133 assertNotNull(emptySimple);
134 assertNull(emptySimple.getFirstChild());
135 String valueOfEmptyTag = (String) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.STRING);
136 // a closed element does not have a 'null' text value but a zero-length string
137 assertEquals("", valueOfEmptyTag);
138 }
139
140 @Test public void testText() throws XPathExpressionException {
141 final String expr = "//child/child_2[@name='child2_1']/textSimple";
142 final String expected = "some text 2";
143 Node textSimple = (Node) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.NODE);
144 assertNotNull(textSimple);
145 assertNotNull(textSimple.getFirstChild());
146 String valueOfTextTag = (String) XPATH.evaluate(expr, getTestInputSource(), XPathConstants.STRING);
147 // a closed element does not have a 'null' text value but a zero-length string
148 assertEquals(expected, valueOfTextTag);
149 }
150
151 /* @Test public void testBooleanTranslation() throws Exception {
152 String KUALI_CAMPUS_TYPE_ACTIVE_INDICATOR_XSTREAMSAFE = XSTREAM_SAFE_PREFIX + XSTREAM_MATCH_ANYWHERE_PREFIX + "campus/campusType/dataObjectMaintenanceCodeActiveIndicator" + XSTREAM_SAFE_SUFFIX;
153 String KUALI_INITIATOR_UNIVERSAL_USER_STUDENT_INDICATOR_XSTREAMSAFE = XSTREAM_SAFE_PREFIX + XSTREAM_MATCH_ANYWHERE_PREFIX + "kualiTransactionalDocumentInformation/documentInitiator/person/student" + XSTREAM_SAFE_SUFFIX;
154 DocumentContent docContent = new StandardDocumentContent(
155 "<documentContent><applicationContent><org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer>" +
156 " <kualiTransactionalDocumentInformation>" +
157 " <documentInitiator>" +
158 " <person>" +
159 " <student>false</student>" +
160 " <campus class=\"org.kuali.rice.krad.bo.CampusImpl--EnhancerByCGLIB--4968dd25\">" +
161 " <campusType>" +
162 " <dataObjectMaintenanceCodeActiveIndicator>true</dataObjectMaintenanceCodeActiveIndicator>" +
163 " </campusType>" +
164 " </campus>" +
165 " </person>" +
166 " </documentInitiator>" +
167 " </kualiTransactionalDocumentInformation>" +
168 " <document>" +
169 " <purchaseOrderCreateDate>11-09-2007</purchaseOrderCreateDate>" +
170 " <oldPurchaseOrderCreateDate></oldPurchaseOrderCreateDate>" +
171 " </document>" +
172 "</org.kuali.rice.krad.workflow.KualiDocumentXmlMaterializer></applicationContent></documentContent>"
173 );
174 // DocumentContent docContent = KualiAttributeTestUtil.getDocumentContentFromXmlFileAndPath(KualiAttributeTestUtil.PURCHASE_ORDER_DOCUMENT, KualiAttributeTestUtil.RELATIVE_PATH_IN_PROJECT_WORKFLOW, "PurchaseOrderDocument");
175 XPath xpath = XPathHelper.newXPath(docContent.getDocument());
176
177 String valueForTrue = "Yes";
178 String valueForFalse = "No";
179
180 // test campus active indicator field translation to 'Yes'
181 String xpathConditionStatement = KUALI_CAMPUS_TYPE_ACTIVE_INDICATOR_XSTREAMSAFE + " = 'true'";
182 String xpathExpression = constructXpathExpression(valueForTrue, valueForFalse, xpathConditionStatement);
183 String xpathResult = (String) xpath.evaluate(xpathExpression, docContent.getDocument(), XPathConstants.STRING);
184 assertEquals("Using translated xpath expression '" + xpathExpression + "'", valueForTrue, xpathResult);
185
186 // test user student indicator translation to 'No'
187 xpathConditionStatement = KUALI_INITIATOR_UNIVERSAL_USER_STUDENT_INDICATOR_XSTREAMSAFE + " = 'true'";
188 xpathExpression = constructXpathExpression(valueForTrue, valueForFalse, xpathConditionStatement);
189 xpathResult = (String) xpath.evaluate(xpathExpression, docContent.getDocument(), XPathConstants.STRING);
190 assertEquals("Using translated xpath expression '" + xpathExpression + "'", valueForFalse, xpathResult);
191
192 // test filled in date field translates to 'Yes'
193 String expression = XSTREAM_SAFE_PREFIX + XSTREAM_MATCH_ANYWHERE_PREFIX + "document/purchaseOrderCreateDate" + XSTREAM_SAFE_SUFFIX;
194 xpathConditionStatement = "boolean(" + expression + ") and not(" + expression + " = '')";
195 xpathExpression = constructXpathExpression(valueForTrue, valueForFalse, xpathConditionStatement);
196 xpathResult = (String) xpath.evaluate(xpathExpression, docContent.getDocument(), XPathConstants.STRING);
197 assertEquals("Using translated xpath expression '" + xpathExpression + "'", valueForTrue, xpathResult);
198
199 // test empty date field translates to 'No'
200 expression = XSTREAM_SAFE_PREFIX + XSTREAM_MATCH_ANYWHERE_PREFIX + "document/oldPurchaseOrderCreateDate" + XSTREAM_SAFE_SUFFIX;
201 xpathConditionStatement = "boolean(" + expression + ") and not(" + expression + " = '')";
202 xpathExpression = constructXpathExpression(valueForTrue, valueForFalse, xpathConditionStatement);
203 xpathResult = (String) xpath.evaluate(xpathExpression, docContent.getDocument(), XPathConstants.STRING);
204 assertEquals("Using translated xpath expression '" + xpathExpression + "'", valueForFalse, xpathResult);
205
206 // test non-existent date field translates to 'No'
207 expression = XSTREAM_SAFE_PREFIX + XSTREAM_MATCH_ANYWHERE_PREFIX + "document/newPurchaseOrderCreateDate" + XSTREAM_SAFE_SUFFIX;
208 xpathConditionStatement = "boolean(" + expression + ") and not(" + expression + " = '')";
209 xpathExpression = constructXpathExpression(valueForTrue, valueForFalse, xpathConditionStatement);
210 xpathResult = (String) xpath.evaluate(xpathExpression, docContent.getDocument(), XPathConstants.STRING);
211 assertEquals("Using translated xpath expression '" + xpathExpression + "'", valueForFalse, xpathResult);
212 }*/
213
214 private String constructXpathExpression(String valueForTrue, String valueForFalse, String booleanXPathExpression) {
215 String[] xpathElementsToInsert = new String[3];
216 xpathElementsToInsert[0] = "concat( substring('" + valueForTrue + "', number(not(";
217 xpathElementsToInsert[1] = "))*string-length('" + valueForTrue + "')+1), substring('" + valueForFalse + "', number(";
218 xpathElementsToInsert[2] = ")*string-length('" + valueForFalse + "')+1))";
219
220 StringBuffer returnableString = new StringBuffer();
221 for (int i = 0; i < xpathElementsToInsert.length; i++) {
222 String newXpathElement = xpathElementsToInsert[i];
223 returnableString.append(newXpathElement);
224
225 /*
226 * Append the given xpath expression onto the end of the stringbuffer only in the following cases - if there is only one
227 * element in the string array - if there is more than one element in the string array and if the current element is not
228 * the last element
229 */
230 if (((i + 1) != xpathElementsToInsert.length) || (xpathElementsToInsert.length == 1)) {
231 returnableString.append(booleanXPathExpression);
232 }
233 }
234 return returnableString.toString();
235
236 }
237 }