View Javadoc
1   /*
2    * Copyright 2002-2012 the original author or authors.
3    *
4    * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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  
17  package org.springframework.test.util;
18  
19  import java.io.StringReader;
20  import java.util.Collections;
21  import java.util.Map;
22  import javax.xml.namespace.QName;
23  import javax.xml.parsers.DocumentBuilder;
24  import javax.xml.parsers.DocumentBuilderFactory;
25  import javax.xml.xpath.XPath;
26  import javax.xml.xpath.XPathConstants;
27  import javax.xml.xpath.XPathExpression;
28  import javax.xml.xpath.XPathExpressionException;
29  import javax.xml.xpath.XPathFactory;
30  
31  import org.hamcrest.Matcher;
32  import org.w3c.dom.Document;
33  import org.w3c.dom.Node;
34  import org.w3c.dom.NodeList;
35  import org.xml.sax.InputSource;
36  
37  import org.springframework.util.CollectionUtils;
38  import org.springframework.util.xml.SimpleNamespaceContext;
39  
40  import static org.springframework.test.util.AssertionErrors.*;
41  import static org.springframework.test.util.MatcherAssertionErrors.*;
42  
43  /**
44   * A helper class for applying assertions via XPath expressions.
45   *
46   * @author Rossen Stoyanchev
47   * @since 3.2
48   */
49  public class XpathExpectationsHelper {
50  
51  	private final String expression;
52  
53  	private final XPathExpression xpathExpression;
54  
55  	private final boolean hasNamespaces;
56  
57  
58  	/**
59  	 * Class constructor.
60  	 *
61  	 * @param expression the XPath expression
62  	 * @param namespaces XML namespaces referenced in the XPath expression, or {@code null}
63  	 * @param args arguments to parameterize the XPath expression with using the
64  	 * formatting specifiers defined in {@link String#format(String, Object...)}
65  	 * @throws XPathExpressionException
66  	 */
67  	public XpathExpectationsHelper(String expression, Map<String, String> namespaces, Object... args)
68  			throws XPathExpressionException {
69  
70  		this.expression = String.format(expression, args);
71  		this.xpathExpression = compileXpathExpression(this.expression, namespaces);
72  		this.hasNamespaces = !CollectionUtils.isEmpty(namespaces);
73  	}
74  
75  	private XPathExpression compileXpathExpression(String expression, Map<String, String> namespaces)
76  			throws XPathExpressionException {
77  
78  		SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
79  		namespaceContext.setBindings((namespaces != null) ? namespaces : Collections.<String, String> emptyMap());
80  		XPath xpath = XPathFactory.newInstance().newXPath();
81  		xpath.setNamespaceContext(namespaceContext);
82  		return xpath.compile(expression);
83  	}
84  
85  	/**
86  	 * @return the compiled XPath expression.
87  	 */
88  	protected XPathExpression getXpathExpression() {
89  		return this.xpathExpression;
90  	}
91  
92  	/**
93  	 * Parse the content, evaluate the XPath expression as a {@link Node}, and
94  	 * assert it with the given {@code Matcher<Node>}.
95  	 */
96  	public void assertNode(String content, final Matcher<? super Node> matcher) throws Exception {
97  		Document document = parseXmlString(content);
98  		Node node = evaluateXpath(document, XPathConstants.NODE, Node.class);
99  		assertThat("XPath " + this.expression, node, matcher);
100 	}
101 
102 	/**
103 	 * Parse the given XML content to a {@link Document}.
104 	 *
105 	 * @param xml the content to parse
106 	 * @return the parsed document
107 	 * @throws Exception in case of errors
108 	 */
109 	protected Document parseXmlString(String xml) throws Exception  {
110 		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
111 		factory.setNamespaceAware(this.hasNamespaces);
112 		DocumentBuilder documentBuilder = factory.newDocumentBuilder();
113 		InputSource inputSource = new InputSource(new StringReader(xml));
114 		Document document = documentBuilder.parse(inputSource);
115 		return document;
116 	}
117 
118 	/**
119 	 * Apply the XPath expression to given document.
120 	 * @throws XPathExpressionException
121 	 */
122 	@SuppressWarnings("unchecked")
123 	protected <T> T evaluateXpath(Document document, QName evaluationType, Class<T> expectedClass)
124 			throws XPathExpressionException {
125 
126 		return (T) getXpathExpression().evaluate(document, evaluationType);
127 	}
128 
129 	/**
130 	 * Apply the XPath expression and assert the resulting content exists.
131 	 * @throws Exception if content parsing or expression evaluation fails
132 	 */
133 	public void exists(String content) throws Exception {
134 		Document document = parseXmlString(content);
135 		Node node = evaluateXpath(document, XPathConstants.NODE, Node.class);
136 		assertTrue("XPath " + this.expression + " does not exist", node != null);
137 	}
138 
139 	/**
140 	 * Apply the XPath expression and assert the resulting content does not exist.
141 	 * @throws Exception if content parsing or expression evaluation fails
142 	 */
143 	public void doesNotExist(String content) throws Exception {
144 		Document document = parseXmlString(content);
145 		Node node = evaluateXpath(document, XPathConstants.NODE, Node.class);
146 		assertTrue("XPath " + this.expression + " exists", node == null);
147 	}
148 
149 	/**
150 	 * Apply the XPath expression and assert the resulting content with the
151 	 * given Hamcrest matcher.
152 	 *
153 	 * @throws Exception if content parsing or expression evaluation fails
154 	 */
155 	public void assertNodeCount(String content, Matcher<Integer> matcher) throws Exception {
156 		Document document = parseXmlString(content);
157 		NodeList nodeList = evaluateXpath(document, XPathConstants.NODESET, NodeList.class);
158 		assertThat("nodeCount for XPath " + this.expression, nodeList.getLength(), matcher);
159 	}
160 
161 	/**
162 	 * Apply the XPath expression and assert the resulting content as an integer.
163 	 * @throws Exception if content parsing or expression evaluation fails
164 	 */
165 	public void assertNodeCount(String content, int expectedCount) throws Exception {
166 		Document document = parseXmlString(content);
167 		NodeList nodeList = evaluateXpath(document, XPathConstants.NODESET, NodeList.class);
168 		assertEquals("nodeCount for XPath " + this.expression, expectedCount, nodeList.getLength());
169 	}
170 
171 	/**
172 	 * Apply the XPath expression and assert the resulting content with the
173 	 * given Hamcrest matcher.
174 	 *
175 	 * @throws Exception if content parsing or expression evaluation fails
176 	 */
177 	public void assertString(String content, Matcher<? super String> matcher) throws Exception {
178 		Document document = parseXmlString(content);
179 		String result = evaluateXpath(document,  XPathConstants.STRING, String.class);
180 		assertThat("XPath " + this.expression, result, matcher);
181 	}
182 
183 	/**
184 	 * Apply the XPath expression and assert the resulting content as a String.
185 	 * @throws Exception if content parsing or expression evaluation fails
186 	 */
187 	public void assertString(String content, String expectedValue) throws Exception {
188 		Document document = parseXmlString(content);
189 		String actual = evaluateXpath(document,  XPathConstants.STRING, String.class);
190 		assertEquals("XPath " + this.expression, expectedValue, actual);
191 	}
192 
193 	/**
194 	 * Apply the XPath expression and assert the resulting content with the
195 	 * given Hamcrest matcher.
196 	 *
197 	 * @throws Exception if content parsing or expression evaluation fails
198 	 */
199 	public void assertNumber(String content, Matcher<? super Double> matcher) throws Exception {
200 		Document document = parseXmlString(content);
201 		Double result = evaluateXpath(document, XPathConstants.NUMBER, Double.class);
202 		assertThat("XPath " + this.expression, result, matcher);
203 	}
204 
205 	/**
206 	 * Apply the XPath expression and assert the resulting content as a Double.
207 	 * @throws Exception if content parsing or expression evaluation fails
208 	 */
209 	public void assertNumber(String content, Double expectedValue) throws Exception {
210 		Document document = parseXmlString(content);
211 		Double actual = evaluateXpath(document, XPathConstants.NUMBER, Double.class);
212 		assertEquals("XPath " + this.expression, expectedValue, actual);
213 	}
214 
215 	/**
216 	 * Apply the XPath expression and assert the resulting content as a Boolean.
217 	 * @throws Exception if content parsing or expression evaluation fails
218 	 */
219 	public void assertBoolean(String content, boolean expectedValue) throws Exception {
220 		Document document = parseXmlString(content);
221 		String actual = evaluateXpath(document, XPathConstants.STRING, String.class);
222 		assertEquals("XPath " + this.expression, expectedValue, Boolean.parseBoolean(actual));
223 	}
224 
225 }