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;
018
019import java.io.IOException;
020import java.util.LinkedList;
021import java.util.List;
022
023import org.springframework.http.client.ClientHttpRequest;
024import org.springframework.http.client.ClientHttpResponse;
025import org.springframework.lang.Nullable;
026import org.springframework.util.Assert;
027
028/**
029 * Default implementation of {@code RequestExpectation} that simply delegates
030 * to the request matchers and the response creator it contains.
031 *
032 * @author Rossen Stoyanchev
033 * @since 4.3
034 */
035public class DefaultRequestExpectation implements RequestExpectation {
036
037        private final RequestCount requestCount;
038
039        private final List<RequestMatcher> requestMatchers = new LinkedList<>();
040
041        @Nullable
042        private ResponseCreator responseCreator;
043
044
045        /**
046         * Create a new request expectation that should be called a number of times
047         * as indicated by {@code RequestCount}.
048         * @param expectedCount the expected request expectedCount
049         */
050        public DefaultRequestExpectation(ExpectedCount expectedCount, RequestMatcher requestMatcher) {
051                Assert.notNull(expectedCount, "ExpectedCount is required");
052                Assert.notNull(requestMatcher, "RequestMatcher is required");
053                this.requestCount = new RequestCount(expectedCount);
054                this.requestMatchers.add(requestMatcher);
055        }
056
057
058        protected RequestCount getRequestCount() {
059                return this.requestCount;
060        }
061
062        protected List<RequestMatcher> getRequestMatchers() {
063                return this.requestMatchers;
064        }
065
066        @Nullable
067        protected ResponseCreator getResponseCreator() {
068                return this.responseCreator;
069        }
070
071        @Override
072        public ResponseActions andExpect(RequestMatcher requestMatcher) {
073                Assert.notNull(requestMatcher, "RequestMatcher is required");
074                this.requestMatchers.add(requestMatcher);
075                return this;
076        }
077
078        @Override
079        public void andRespond(ResponseCreator responseCreator) {
080                Assert.notNull(responseCreator, "ResponseCreator is required");
081                this.responseCreator = responseCreator;
082        }
083
084        @Override
085        public void match(ClientHttpRequest request) throws IOException {
086                for (RequestMatcher matcher : getRequestMatchers()) {
087                        matcher.match(request);
088                }
089        }
090
091        /**
092         * Note that as of 5.0.3, the creation of the response, which may block
093         * intentionally, is separated from request count tracking, and this
094         * method no longer increments the count transparently. Instead
095         * {@link #incrementAndValidate()} must be invoked independently.
096         */
097        @Override
098        public ClientHttpResponse createResponse(@Nullable ClientHttpRequest request) throws IOException {
099                ResponseCreator responseCreator = getResponseCreator();
100                Assert.state(responseCreator != null, "createResponse() called before ResponseCreator was set");
101                return responseCreator.createResponse(request);
102        }
103
104        @Override
105        public boolean hasRemainingCount() {
106                return getRequestCount().hasRemainingCount();
107        }
108
109        @Override
110        public void incrementAndValidate() {
111                getRequestCount().incrementAndValidate();
112        }
113
114        @Override
115        public boolean isSatisfied() {
116                return getRequestCount().isSatisfied();
117        }
118
119
120        /**
121         * Helper class that keeps track of actual vs expected request count.
122         */
123        protected static class RequestCount {
124
125                private final ExpectedCount expectedCount;
126
127                private int matchedRequestCount;
128
129                public RequestCount(ExpectedCount expectedCount) {
130                        this.expectedCount = expectedCount;
131                }
132
133                public ExpectedCount getExpectedCount() {
134                        return this.expectedCount;
135                }
136
137                public int getMatchedRequestCount() {
138                        return this.matchedRequestCount;
139                }
140
141                public void incrementAndValidate() {
142                        this.matchedRequestCount++;
143                        if (getMatchedRequestCount() > getExpectedCount().getMaxCount()) {
144                                throw new AssertionError("No more calls expected.");
145                        }
146                }
147
148                public boolean hasRemainingCount() {
149                        return (getMatchedRequestCount() < getExpectedCount().getMaxCount());
150                }
151
152                public boolean isSatisfied() {
153                        // Only validate min count since max count is checked on every request...
154                        return (getMatchedRequestCount() >= getExpectedCount().getMinCount());
155                }
156        }
157
158}