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.client.match; 018 019import java.util.Map; 020 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.lang.Nullable; 028import org.springframework.mock.http.client.MockClientHttpRequest; 029import org.springframework.test.util.XpathExpectationsHelper; 030import org.springframework.test.web.client.RequestMatcher; 031 032/** 033 * Factory methods for request content {@code RequestMatcher} implementations 034 * that use an XPath expression. 035 * 036 * <p>An instance of this class is typically accessed via 037 * {@link MockRestRequestMatchers#xpath(String, Object...)} or 038 * {@link MockRestRequestMatchers#xpath(String, Map, Object...)}. 039 * 040 * @author Rossen Stoyanchev 041 * @author Sam Brannen 042 * @since 3.2 043 */ 044public class XpathRequestMatchers { 045 046 private static final String DEFAULT_ENCODING = "UTF-8"; 047 048 private final XpathExpectationsHelper xpathHelper; 049 050 051 /** 052 * Class constructor, not for direct instantiation. 053 * <p>Use {@link MockRestRequestMatchers#xpath(String, Object...)} or 054 * {@link MockRestRequestMatchers#xpath(String, Map, Object...)}. 055 * @param expression the XPath expression 056 * @param namespaces the XML namespaces referenced in the XPath expression, or {@code null} 057 * @param args arguments to parameterize the XPath expression with, using the 058 * formatting specifiers defined in {@link String#format(String, Object...)} 059 * @throws XPathExpressionException if expression compilation failed 060 */ 061 protected XpathRequestMatchers(String expression, @Nullable Map<String, String> namespaces, Object... args) 062 throws XPathExpressionException { 063 064 this.xpathHelper = new XpathExpectationsHelper(expression, namespaces, args); 065 } 066 067 068 /** 069 * Apply the XPath and assert it with the given {@code Matcher<Node>}. 070 */ 071 public RequestMatcher node(Matcher<? super Node> matcher) { 072 return (XpathRequestMatcher) request -> 073 this.xpathHelper.assertNode(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 074 } 075 076 /** 077 * Assert that content exists at the given XPath. 078 */ 079 public RequestMatcher exists() { 080 return (XpathRequestMatcher) request -> 081 this.xpathHelper.exists(request.getBodyAsBytes(), DEFAULT_ENCODING); 082 } 083 084 /** 085 * Assert that content does not exist at the given XPath. 086 */ 087 public RequestMatcher doesNotExist() { 088 return (XpathRequestMatcher) request -> 089 this.xpathHelper.doesNotExist(request.getBodyAsBytes(), DEFAULT_ENCODING); 090 } 091 092 /** 093 * Apply the XPath and assert the number of nodes found with the given 094 * {@code Matcher<Integer>}. 095 */ 096 public RequestMatcher nodeCount(Matcher<Integer> matcher) { 097 return (XpathRequestMatcher) request -> 098 this.xpathHelper.assertNodeCount(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 099 } 100 101 /** 102 * Apply the XPath and assert the number of nodes found. 103 */ 104 public RequestMatcher nodeCount(int expectedCount) { 105 return (XpathRequestMatcher) request -> 106 this.xpathHelper.assertNodeCount(request.getBodyAsBytes(), DEFAULT_ENCODING, expectedCount); 107 } 108 109 /** 110 * Apply the XPath and assert the String content found with the given matcher. 111 */ 112 public RequestMatcher string(Matcher<? super String> matcher) { 113 return (XpathRequestMatcher) request -> 114 this.xpathHelper.assertString(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 115 } 116 117 /** 118 * Apply the XPath and assert the String content found. 119 */ 120 public RequestMatcher string(String content) { 121 return (XpathRequestMatcher) request -> 122 this.xpathHelper.assertString(request.getBodyAsBytes(), DEFAULT_ENCODING, content); 123 } 124 125 /** 126 * Apply the XPath and assert the number found with the given matcher. 127 */ 128 public RequestMatcher number(Matcher<? super Double> matcher) { 129 return (XpathRequestMatcher) request -> 130 this.xpathHelper.assertNumber(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); 131 } 132 133 /** 134 * Apply the XPath and assert the number value found. 135 */ 136 public RequestMatcher number(Double value) { 137 return (XpathRequestMatcher) request -> 138 this.xpathHelper.assertNumber(request.getBodyAsBytes(), DEFAULT_ENCODING, value); 139 } 140 141 /** 142 * Apply the XPath and assert the boolean value found. 143 */ 144 public RequestMatcher booleanValue(Boolean value) { 145 return (XpathRequestMatcher) request -> 146 this.xpathHelper.assertBoolean(request.getBodyAsBytes(), DEFAULT_ENCODING, value); 147 } 148 149 150 /** 151 * Functional interface for XPath {@link RequestMatcher} implementations. 152 */ 153 @FunctionalInterface 154 private interface XpathRequestMatcher extends RequestMatcher { 155 156 @Override 157 default void match(ClientHttpRequest request) { 158 try { 159 matchInternal((MockClientHttpRequest) request); 160 } 161 catch (Exception ex) { 162 throw new AssertionError("Failed to parse XML request content", ex); 163 } 164 } 165 166 void matchInternal(MockClientHttpRequest request) throws Exception; 167 } 168 169}