001/**
002 * Copyright 2005-2016 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.kew.support.xstream;
017
018import org.junit.Before;
019import org.junit.Test;
020import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
021import org.kuali.rice.kew.xml.xstream.XStreamSafeEvaluator;
022import org.w3c.dom.Document;
023import org.w3c.dom.Node;
024import org.w3c.dom.NodeList;
025
026import javax.xml.parsers.DocumentBuilderFactory;
027import javax.xml.xpath.XPath;
028import javax.xml.xpath.XPathConstants;
029import java.io.ByteArrayInputStream;
030
031import static org.junit.Assert.assertEquals;
032import static org.junit.Assert.assertNotNull;
033
034
035public 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}