001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache 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 *      https://www.apache.org/licenses/LICENSE-2.0
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
017package org.springframework.test.web.servlet.result;
018
019import java.util.Map;
020
021import javax.xml.xpath.XPathExpressionException;
022
023import org.hamcrest.Matcher;
024import org.w3c.dom.Node;
025import org.w3c.dom.NodeList;
026
027import org.springframework.lang.Nullable;
028import org.springframework.mock.web.MockHttpServletResponse;
029import org.springframework.test.util.XpathExpectationsHelper;
030import org.springframework.test.web.servlet.ResultMatcher;
031
032/**
033 * Factory for assertions on the response content using XPath expressions.
034 *
035 * <p>An instance of this class is typically accessed via
036 * {@link MockMvcResultMatchers#xpath}.
037 *
038 * @author Rossen Stoyanchev
039 * @since 3.2
040 */
041public class XpathResultMatchers {
042
043        private final XpathExpectationsHelper xpathHelper;
044
045
046        /**
047         * Protected constructor, not for direct instantiation. Use
048         * {@link MockMvcResultMatchers#xpath(String, Object...)} or
049         * {@link MockMvcResultMatchers#xpath(String, Map, Object...)}.
050         * @param expression the XPath expression
051         * @param namespaces the XML namespaces referenced in the XPath expression, or {@code null}
052         * @param args arguments to parameterize the XPath expression with using the
053         * formatting specifiers defined in {@link String#format(String, Object...)}
054         */
055        protected XpathResultMatchers(String expression, @Nullable Map<String, String> namespaces, Object ... args)
056                        throws XPathExpressionException {
057
058                this.xpathHelper = new XpathExpectationsHelper(expression, namespaces, args);
059        }
060
061
062        /**
063         * Evaluate the XPath and assert the {@link Node} content found with the
064         * given Hamcrest {@link Matcher}.
065         */
066        public ResultMatcher node(Matcher<? super Node> matcher) {
067                return result -> {
068                        MockHttpServletResponse response = result.getResponse();
069                        this.xpathHelper.assertNode(response.getContentAsByteArray(), getDefinedEncoding(response), matcher);
070                };
071        }
072
073        /**
074         * Evaluate the XPath and assert the {@link NodeList} content found with the
075         * given Hamcrest {@link Matcher}.
076         * @since 5.2.2
077         */
078        public ResultMatcher nodeList(Matcher<? super NodeList> matcher) {
079                return result -> {
080                        MockHttpServletResponse response = result.getResponse();
081                        this.xpathHelper.assertNodeList(response.getContentAsByteArray(), getDefinedEncoding(response), matcher);
082                };
083        }
084
085        /**
086         * Get the response encoding if explicitly defined in the response, {code null} otherwise.
087         */
088        @Nullable
089        private String getDefinedEncoding(MockHttpServletResponse response) {
090                return (response.isCharset() ? response.getCharacterEncoding() : null);
091        }
092
093        /**
094         * Evaluate the XPath and assert that content exists.
095         */
096        public ResultMatcher exists() {
097                return result -> {
098                        MockHttpServletResponse response = result.getResponse();
099                        this.xpathHelper.exists(response.getContentAsByteArray(), getDefinedEncoding(response));
100                };
101        }
102
103        /**
104         * Evaluate the XPath and assert that content doesn't exist.
105         */
106        public ResultMatcher doesNotExist() {
107                return result -> {
108                        MockHttpServletResponse response = result.getResponse();
109                        this.xpathHelper.doesNotExist(response.getContentAsByteArray(), getDefinedEncoding(response));
110                };
111        }
112
113        /**
114         * Evaluate the XPath and assert the number of nodes found with the given
115         * Hamcrest {@link Matcher}.
116         */
117        public ResultMatcher nodeCount(Matcher<Integer> matcher) {
118                return result -> {
119                        MockHttpServletResponse response = result.getResponse();
120                        this.xpathHelper.assertNodeCount(response.getContentAsByteArray(), getDefinedEncoding(response), matcher);
121                };
122        }
123
124        /**
125         * Evaluate the XPath and assert the number of nodes found.
126         */
127        public ResultMatcher nodeCount(int expectedCount) {
128                return result -> {
129                        MockHttpServletResponse response = result.getResponse();
130                        this.xpathHelper.assertNodeCount(response.getContentAsByteArray(), getDefinedEncoding(response), expectedCount);
131                };
132        }
133
134        /**
135         * Apply the XPath and assert the {@link String} value found with the given
136         * Hamcrest {@link Matcher}.
137         */
138        public ResultMatcher string(Matcher<? super String> matcher) {
139                return result -> {
140                        MockHttpServletResponse response = result.getResponse();
141                        this.xpathHelper.assertString(response.getContentAsByteArray(), getDefinedEncoding(response), matcher);
142                };
143        }
144
145        /**
146         * Apply the XPath and assert the {@link String} value found.
147         */
148        public ResultMatcher string(String expectedValue) {
149                return result -> {
150                        MockHttpServletResponse response = result.getResponse();
151                        this.xpathHelper.assertString(response.getContentAsByteArray(), getDefinedEncoding(response), expectedValue);
152                };
153        }
154
155        /**
156         * Evaluate the XPath and assert the {@link Double} value found with the
157         * given Hamcrest {@link Matcher}.
158         */
159        public ResultMatcher number(Matcher<? super Double> matcher) {
160                return result -> {
161                        MockHttpServletResponse response = result.getResponse();
162                        this.xpathHelper.assertNumber(response.getContentAsByteArray(), getDefinedEncoding(response), matcher);
163                };
164        }
165
166        /**
167         * Evaluate the XPath and assert the {@link Double} value found.
168         */
169        public ResultMatcher number(Double expectedValue) {
170                return result -> {
171                        MockHttpServletResponse response = result.getResponse();
172                        this.xpathHelper.assertNumber(response.getContentAsByteArray(), getDefinedEncoding(response), expectedValue);
173                };
174        }
175
176        /**
177         * Evaluate the XPath and assert the {@link Boolean} value found.
178         */
179        public ResultMatcher booleanValue(Boolean value) {
180                return result -> {
181                        MockHttpServletResponse response = result.getResponse();
182                        this.xpathHelper.assertBoolean(response.getContentAsByteArray(), getDefinedEncoding(response), value);
183                };
184        }
185
186}