001/* 002 * Copyright 2002-2018 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.client.match; 018 019import java.io.IOException; 020import java.util.Map; 021import javax.xml.xpath.XPathExpressionException; 022 023import org.hamcrest.Matcher; 024import org.w3c.dom.Node; 025 026import org.springframework.http.client.ClientHttpRequest; 027import org.springframework.mock.http.client.MockClientHttpRequest; 028import org.springframework.test.util.XpathExpectationsHelper; 029import org.springframework.test.web.client.RequestMatcher; 030 031/** 032 * Factory methods for request content {@code RequestMatcher}'s using an XPath 033 * expression. An instance of this class is typically accessed via 034 * {@code RequestMatchers.xpath(..)}. 035 * 036 * @author Rossen Stoyanchev 037 * @since 3.2 038 */ 039public class XpathRequestMatchers { 040 041 private static final String DEFAULT_ENCODING = "UTF-8"; 042 043 private final XpathExpectationsHelper xpathHelper; 044 045 046 /** 047 * Class constructor, not for direct instantiation. Use 048 * {@link MockRestRequestMatchers#xpath(String, Object...)} or 049 * {@link MockRestRequestMatchers#xpath(String, Map, Object...)}. 050 * @param expression the XPath expression 051 * @param namespaces 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 * @throws XPathExpressionException if expression compilation failed 055 */ 056 protected XpathRequestMatchers(String expression, Map<String, String> namespaces, Object ... args) 057 throws XPathExpressionException { 058 059 this.xpathHelper = new XpathExpectationsHelper(expression, namespaces, args); 060 } 061 062 063 /** 064 * Apply the XPath and assert it with the given {@code Matcher<Node>}. 065 */ 066 public <T> RequestMatcher node(final Matcher<? super Node> matcher) { 067 return new AbstractXpathRequestMatcher() { 068 @Override 069 protected void matchInternal(MockClientHttpRequest request) throws Exception { 070 xpathHelper.assertNode(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 071 } 072 }; 073 } 074 075 /** 076 * Assert that content exists at the given XPath. 077 */ 078 public <T> RequestMatcher exists() { 079 return new AbstractXpathRequestMatcher() { 080 @Override 081 protected void matchInternal(MockClientHttpRequest request) throws Exception { 082 xpathHelper.exists(request.getBodyAsBytes(), DEFAULT_ENCODING); 083 } 084 }; 085 } 086 087 /** 088 * Assert that content does not exist at the given XPath. 089 */ 090 public <T> RequestMatcher doesNotExist() { 091 return new AbstractXpathRequestMatcher() { 092 @Override 093 protected void matchInternal(MockClientHttpRequest request) throws Exception { 094 xpathHelper.doesNotExist(request.getBodyAsBytes(), DEFAULT_ENCODING); 095 } 096 }; 097 } 098 099 /** 100 * Apply the XPath and assert the number of nodes found with the given 101 * {@code Matcher<Integer>}. 102 */ 103 public <T> RequestMatcher nodeCount(final Matcher<Integer> matcher) { 104 return new AbstractXpathRequestMatcher() { 105 @Override 106 protected void matchInternal(MockClientHttpRequest request) throws Exception { 107 xpathHelper.assertNodeCount(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 108 } 109 }; 110 } 111 112 /** 113 * Apply the XPath and assert the number of nodes found. 114 */ 115 public <T> RequestMatcher nodeCount(final int expectedCount) { 116 return new AbstractXpathRequestMatcher() { 117 @Override 118 protected void matchInternal(MockClientHttpRequest request) throws Exception { 119 xpathHelper.assertNodeCount(request.getBodyAsBytes(), DEFAULT_ENCODING, expectedCount); 120 } 121 }; 122 } 123 124 /** 125 * Apply the XPath and assert the String content found with the given matcher. 126 */ 127 public <T> RequestMatcher string(final Matcher<? super String> matcher) { 128 return new AbstractXpathRequestMatcher() { 129 @Override 130 protected void matchInternal(MockClientHttpRequest request) throws Exception { 131 xpathHelper.assertString(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 132 } 133 }; 134 } 135 136 /** 137 * Apply the XPath and assert the String content found. 138 */ 139 public RequestMatcher string(final String value) { 140 return new AbstractXpathRequestMatcher() { 141 @Override 142 protected void matchInternal(MockClientHttpRequest request) throws Exception { 143 xpathHelper.assertString(request.getBodyAsBytes(), DEFAULT_ENCODING, value); 144 } 145 }; 146 } 147 148 /** 149 * Apply the XPath and assert the number found with the given matcher. 150 */ 151 public <T> RequestMatcher number(final Matcher<? super Double> matcher) { 152 return new AbstractXpathRequestMatcher() { 153 @Override 154 protected void matchInternal(MockClientHttpRequest request) throws Exception { 155 xpathHelper.assertNumber(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 156 } 157 }; 158 } 159 160 /** 161 * Apply the XPath and assert the number of nodes found. 162 */ 163 public RequestMatcher number(final Double value) { 164 return new AbstractXpathRequestMatcher() { 165 @Override 166 protected void matchInternal(MockClientHttpRequest request) throws Exception { 167 xpathHelper.assertNumber(request.getBodyAsBytes(), DEFAULT_ENCODING, value); 168 } 169 }; 170 } 171 172 /** 173 * Apply the XPath and assert the boolean value found. 174 */ 175 public <T> RequestMatcher booleanValue(final Boolean value) { 176 return new AbstractXpathRequestMatcher() { 177 @Override 178 protected void matchInternal(MockClientHttpRequest request) throws Exception { 179 xpathHelper.assertBoolean(request.getBodyAsBytes(), DEFAULT_ENCODING, value); 180 } 181 }; 182 } 183 184 185 /** 186 * Abstract base class for XPath {@link RequestMatcher}'s. 187 */ 188 private abstract static class AbstractXpathRequestMatcher implements RequestMatcher { 189 190 @Override 191 public final void match(ClientHttpRequest request) throws IOException, AssertionError { 192 try { 193 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 194 matchInternal(mockRequest); 195 } 196 catch (Exception ex) { 197 throw new AssertionError("Failed to parse XML request content: " + ex.getMessage()); 198 } 199 } 200 201 protected abstract void matchInternal(MockClientHttpRequest request) throws Exception; 202 } 203 204}