001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.kew.support.xstream;
017    
018    import org.junit.Before;
019    import org.junit.Test;
020    import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
021    import org.kuali.rice.kew.xml.xstream.XStreamSafeEvaluator;
022    import org.w3c.dom.Document;
023    import org.w3c.dom.Node;
024    import org.w3c.dom.NodeList;
025    
026    import javax.xml.parsers.DocumentBuilderFactory;
027    import javax.xml.xpath.XPath;
028    import javax.xml.xpath.XPathConstants;
029    import java.io.ByteArrayInputStream;
030    
031    import static org.junit.Assert.assertEquals;
032    import static org.junit.Assert.assertNotNull;
033    
034    
035    public class XStreamSafeEvaluatorTest {
036                    
037            private static final String XML =
038                            "<document>"+
039                            "  <beans>"+
040                            "    <testBean1>"+
041                            "      <bean1Name>Bean One #1</bean1Name>"+
042                            "      <bean2>" +
043                            "        <seeHowFarTheRabbitHoleGoes><value>20</value></seeHowFarTheRabbitHoleGoes>"+
044                            "        <bean2Name>Bean Two #1</bean2Name>"+
045                            "        <redPill reference=\"../../../redPill\"/>"+
046                            "        <redPill reference=\"../seeHowFarTheRabbitHoleGoes\"/>"+
047                            "        <redPill><value>30</value></redPill>"+
048                            "      </bean2>" +
049                            "    </testBean1>"+
050                            "    <testBean1>" +
051                            "      <bean1Name>Bean One #2</bean1Name>" +
052                            "      <bean2 reference=\"../../testBean1/bean2\"/>" +
053                            "    </testBean1>" +
054                            "    <redPill><value>10</value></redPill>" +
055                            "  </beans>" +
056                            "</document>";
057            
058            private static final String XML2 = 
059                    "<document>"+
060                    "  <test1 reference=\"../test2\"/>"+
061                    "  <test2 reference=\"../test3\"/>"+
062                    "  <test3>test3</test3>"+
063                    "</document>";
064    
065            private static final String XPATH_NO_REF = "//document/beans/testBean1/bean1Name";
066            private static final String XPATH_THROUGH_REF = "//document/beans/testBean1/bean2/bean2Name"; 
067            private static final String XPATH_RED_PILL = "//document/beans/testBean1/bean2/redPill/value";
068            
069            private static final String XPATH2_TEST1 = "/document/test1";
070            private static final String XPATH2_TEST2 = "//test2";
071            private static final String XPATH2_TEST3 = "//document/test3";
072            
073            private static final String XPATH_GET_FOR_RELATIVE = "/document/beans/testBean1/bean2";
074            //private static final String XPATH_GLOBAL_VALUE = ".//value";
075            private static final String XPATH_VALUE_20_RELATIVE = "./seeHowFarTheRabbitHoleGoes/value";
076            private static final String XPATH_VALUE_10_20_30_RELATIVE = "./redPill/value";
077            
078            
079            private Document document;
080            private XStreamSafeEvaluator eval = new XStreamSafeEvaluator();
081            private XPath xpath;
082            
083            /**
084             * Set up an XPath instance using our XPathHelper which should configure the namespace and
085             * WorkflowFunctionResolver for us.
086             */
087        @Before
088            public void setUp() throws Exception {
089                    document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(XML.getBytes()));
090                    xpath = XPathHelper.newXPath(document);
091            }
092            
093            @Test public void testEvaluation() throws Exception {
094                    // test against straight xpath first
095                    NodeList nodeList = (NodeList)xpath.evaluate(XPATH_NO_REF, document, XPathConstants.NODESET);
096                    assertEquals("Should find 2 nodes.", 2, nodeList.getLength());
097                    nodeList = (NodeList)xpath.evaluate(XPATH_THROUGH_REF, document, XPathConstants.NODESET);
098                    assertEquals("Should find 1 nodes.", 1, nodeList.getLength());
099                    
100                    // test against our evaluator, it should be able to follow our reference path
101                    nodeList = eval.evaluate(XPATH_NO_REF, document);
102                    assertEquals("Should find 2 nodes.", 2, nodeList.getLength());
103                    nodeList = eval.evaluate(XPATH_THROUGH_REF, document);
104                    assertEquals("Should find 2 nodes.", 2, nodeList.getLength());
105                    
106                    // now test our evaluator exposed as an XPath function
107                    nodeList = (NodeList)xpath.evaluate(wrapXStreamSafe(XPATH_NO_REF), document, XPathConstants.NODESET);
108                    assertEquals("Should find 2 nodes.", 2, nodeList.getLength());
109                    nodeList = (NodeList)xpath.evaluate(wrapXStreamSafe(XPATH_THROUGH_REF), document, XPathConstants.NODESET);
110                    assertEquals("Should find 2 nodes.", 2, nodeList.getLength());
111                    
112                    // now test a bit more complicated XML
113                    nodeList = (NodeList)xpath.evaluate(XPATH_RED_PILL, document, XPathConstants.NODESET);
114                    assertEquals("Without XStream safe evaulation, should only find 1 node.", 1, nodeList.getLength());
115                    System.out.println("\n\n\n\n");
116                    nodeList = (NodeList)xpath.evaluate(wrapXStreamSafe(XPATH_RED_PILL), document, XPathConstants.NODESET);
117                    assertEquals("Should have 6 nodes.", 6, nodeList.getLength());
118                    String totalValue = xpath.evaluate("sum("+wrapXStreamSafe(XPATH_RED_PILL)+")", document);
119                    assertEquals("Sum should be 120.", "120", totalValue);
120            }
121            
122            /**
123             * Tests the case where the last node that resulted from the evaulation of the xpath has a "reference" attribute
124             * on it.  I found a bug where this last "reference" attribute was not being evaluated and resolved to the
125             * proper node.
126             * 
127             * This method also does some testing of chaining of reference nodes (i.e. a node has a reference to a node
128             * which has a reference to another node).
129             */
130            @Test public void testTerminalReferences() throws Exception {
131                    document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(XML2.getBytes()));
132                    xpath = XPathHelper.newXPath(document);
133                    
134                    // test against straight xpath first
135                    NodeList nodeList = (NodeList)xpath.evaluate(XPATH2_TEST1, document, XPathConstants.NODESET);
136                    assertEquals("There should be one node.", 1, nodeList.getLength());
137                    assertEquals("The first node should be named 'test1'", "test1", nodeList.item(0).getNodeName());
138                    assertEquals("The node should have no children.", 0, nodeList.item(0).getChildNodes().getLength());
139                    assertNotNull("The node should have a reference attribute.", nodeList.item(0).getAttributes().getNamedItem("reference"));
140                    
141                    String test3Value = xpath.evaluate(XPATH2_TEST3, document);
142                    assertEquals("test3", test3Value);
143                    
144                    // test using the xstreamsafe function
145                    String test1Value = xpath.evaluate(wrapXStreamSafe(XPATH2_TEST1), document);
146                    assertEquals("test3", test1Value);
147                    
148                    String test2Value = xpath.evaluate(wrapXStreamSafe(XPATH2_TEST2), document);
149                    assertEquals("test3", test2Value);
150                    
151                    test3Value = xpath.evaluate(wrapXStreamSafe(XPATH2_TEST3), document);
152                    assertEquals("test3", test3Value);
153            }
154            
155            /**
156             * Tests the usage of XPath expressions which are evaluated relative to a particular context (i.e. starts with a ".").
157             * This tests the resolution to EN-100.
158             */
159            @Test public void testContextRelativeExpressions() throws Exception {
160                    // drill down in the document to an element which we will use as our context
161                    NodeList nodeList = (NodeList)xpath.evaluate(XPATH_GET_FOR_RELATIVE, document, XPathConstants.NODESET);
162                    assertEquals("There should be two nodes.", 2, nodeList.getLength());
163                    // run the same xpath through the xstreamsafe evaluator
164                    nodeList = (NodeList)xpath.evaluate(wrapXStreamSafe(XPATH_GET_FOR_RELATIVE), document, XPathConstants.NODESET);
165                    assertEquals("There should be two nodes.", 2, nodeList.getLength());
166                    
167                    // now drill down using the first node in the list as the context
168                    Node node = nodeList.item(0);
169                    // reconstruct the XPath instance for the given root node
170                    xpath = XPathHelper.newXPath(node);
171                    // look for <value> elements, there should be two
172                    
173                    // TODO we currently can't support xpath expressions that start with .//  When we can, uncomment this test code.
174    //              nodeList = (NodeList)xpath.evaluate(XPATH_GLOBAL_VALUE, node, XPathConstants.NODESET);
175    //              assertEquals("There should be two value elements.", 2, nodeList.getLength());
176    //              // evaluate as xstreamsafe
177    //              nodeList = (NodeList)xpath.evaluate(wrapXStreamSafe(XPATH_GLOBAL_VALUE), node, XPathConstants.NODESET);
178    //              assertEquals("There should be three value elements now.", 3, nodeList.getLength());
179                    
180                    // try to find the value inside of redpill section
181                    String value = xpath.evaluate(XPATH_VALUE_20_RELATIVE, node);
182                    assertEquals("20", value);
183                    // do with xstreamsafe
184                    value = xpath.evaluate(wrapXStreamSafe(XPATH_VALUE_20_RELATIVE), node);
185                    assertEquals("20", value);
186                                    
187                    // test non xstreamsafe, should only be 1 node
188                    nodeList = (NodeList)xpath.evaluate(XPATH_VALUE_10_20_30_RELATIVE, node, XPathConstants.NODESET);
189                    assertEquals("NodeList should have 1 elements.", 1, nodeList.getLength());
190            
191                    // test with xstreamsafe, should be 3 nodes
192                    nodeList = (NodeList)xpath.evaluate(wrapXStreamSafe(XPATH_VALUE_10_20_30_RELATIVE), node, XPathConstants.NODESET);
193                    assertEquals("NodeList should have 3 elements.", 3, nodeList.getLength());
194            }
195            
196            private String wrapXStreamSafe(String xPathExpression) {
197                    return "wf:xstreamsafe('"+xPathExpression+"')";
198            }
199            
200    }