package com.github.kristofa.test.http; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.github.kristofa.test.http.file.FileHttpResponseProvider; /** * Abstract {@link HttpResponseProvider} that contains the following functionality: * <ul> * <li>Exactly matching HttpRequests</li> * <li>In case of non exact match use submitted {@link HttpRequestMatchingFilter} to perform matching.</li> * <li>Support multiple times the same request with potentially different responses that are returned in a fixed order. * </ul> * <p/> * If you create your own {@link HttpResponseProvider} it is probably a good idea to extend this class. * * @author kristof * @see DefaultHttpResponseProvider * @see FileHttpResponseProvider */ public abstract class AbstractHttpResponseProvider implements HttpResponseProvider { private final Map<HttpRequest, List<HttpResponseProxy>> requestMap = new HashMap<HttpRequest, List<HttpResponseProxy>>(); private final List<HttpRequest> unexpectedRequests = new ArrayList<HttpRequest>(); private HttpRequestMatchingFilter requestMatcherFilter; private boolean initialized = false; /** * Adds an expected HttpRequest and response proxy combination. * * @param request Expected http request. * @param responseProxy Response proxy which gives us access to http response. */ protected final void addExpected(final HttpRequest request, final HttpResponseProxy responseProxy) { List<HttpResponseProxy> list = requestMap.get(request); if (list == null) { list = new ArrayList<HttpResponseProxy>(); requestMap.put(request, list); } list.add(responseProxy); } /** * Override this method if you want to lazily initialize requests/responses. * <p/> * This method will be called with the first call to {@link AbstractHttpResponseProvider#getResponse(HttpRequest)}. * <p/> * You can initialize expected requests and responses by calling * {@link AbstractHttpResponseProvider#addExpected(HttpRequest, HttpResponseProxy)}. */ protected void lazyInitializeExpectedRequestsAndResponses() { } /** * Clear expected request/responses as well as already received unexpected requests. * <p/> * Allows re-use for new test without having to recreate instance. */ protected final void resetState() { requestMap.clear(); unexpectedRequests.clear(); } /** * {@inheritDoc} */ @Override public final synchronized HttpResponse getResponse(final HttpRequest request) { if (!initialized) { lazyInitializeExpectedRequestsAndResponses(); initialized = true; } final HttpResponseProxy responseProxyForExactMatchingRequest = getFirstNotYetConsumedResponseProxyFor(request); if (responseProxyForExactMatchingRequest != null) { return responseProxyForExactMatchingRequest.consume(); } // Non exact matching... if (requestMatcherFilter != null) { for (final HttpRequest originalRequest : requestMap.keySet()) { final HttpResponseProxy originalResponseProxy = getFirstNotYetConsumedResponseProxyFor(originalRequest); if (originalResponseProxy == null) { continue; } HttpRequestMatchingContext context = new HttpRequestMatchingContextImpl(originalRequest, request, originalResponseProxy.getResponse()); HttpRequestMatchingFilter next = requestMatcherFilter; while (next != null) { context = next.filter(context); if (context.originalRequest().equals(context.otherRequest())) { originalResponseProxy.consume(); return context.response(); } next = next.next(); } } } unexpectedRequests.add(request); return null; } /** * Adds a {@link HttpRequestMatchingFilter} to the chain of {@link HttpRequestMatchingFilter http request matching * filters}. * * @param filter {@link HttpRequestMatchingFilter}. */ public final void addHttpRequestMatchingFilter(final HttpRequestMatchingFilter filter) { if (requestMatcherFilter == null) { requestMatcherFilter = filter; } else { HttpRequestMatchingFilter matchingFilter = requestMatcherFilter; while (matchingFilter.next() != null) { matchingFilter = matchingFilter.next(); } matchingFilter.setNext(filter); } } /** * {@inheritDoc} */ @Override public final void verify() throws UnsatisfiedExpectationException { final Collection<HttpRequest> missingRequests = new ArrayList<HttpRequest>(); for (final Entry<HttpRequest, List<HttpResponseProxy>> entry : requestMap.entrySet()) { for (final HttpResponseProxy responseProxy : entry.getValue()) { if (responseProxy.consumed() == false) { missingRequests.add(entry.getKey()); } } } if (!unexpectedRequests.isEmpty() || !missingRequests.isEmpty()) { throw new UnsatisfiedExpectationException(missingRequests, unexpectedRequests); } } private HttpResponseProxy getFirstNotYetConsumedResponseProxyFor(final HttpRequest request) { final List<HttpResponseProxy> list = requestMap.get(request); if (list != null) { for (final HttpResponseProxy proxy : list) { if (!proxy.consumed()) { return proxy; } } } return null; } }