/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.flow;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.geoserver.flow.controller.BasicOWSController;
import org.geoserver.ows.HttpErrorCodeException;
import org.geoserver.ows.Request;
import org.junit.Test;
import org.springframework.web.servlet.HttpServletBean;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class ControlFlowCallbackTest {
@Test
public void testBasicFunctionality() throws IOException, ServletException {
final ControlFlowCallback callback = new ControlFlowCallback();
TestingConfigurator tc = new TestingConfigurator();
final CountingController controller = new CountingController(1, 0);
tc.controllers.add(controller);
callback.provider = new DefaultFlowControllerProvider(tc);
callback.doFilter(null, null, new FilterChain() {
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
callback.operationDispatched(null, null);
assertEquals(1, controller.requestIncomingCalls);
assertEquals(0, controller.requestCompleteCalls);
}
});
assertEquals(1, controller.requestIncomingCalls);
assertEquals(1, controller.requestCompleteCalls);
}
@Test
public void testTimeout() {
ControlFlowCallback callback = new ControlFlowCallback();
TestingConfigurator tc = new TestingConfigurator();
tc.timeout = 300;
CountingController c1 = new CountingController(2, 200);
CountingController c2 = new CountingController(1, 200);
tc.controllers.add(c1);
tc.controllers.add(c2);
callback.provider = new DefaultFlowControllerProvider(tc);
try {
callback.operationDispatched(null, null);
fail("A HTTP 503 should have been raised!");
} catch(HttpErrorCodeException e) {
assertEquals(503, e.getErrorCode());
}
assertEquals(1, c1.requestIncomingCalls);
assertEquals(0, c1.requestCompleteCalls);
assertEquals(1, c2.requestIncomingCalls);
assertEquals(0, c1.requestCompleteCalls);
callback.finished(null);
}
@Test
public void testDelayHeader() {
ControlFlowCallback callback = new ControlFlowCallback();
TestingConfigurator tc = new TestingConfigurator();
tc.timeout = Integer.MAX_VALUE;
CountingController cc = new CountingController(2, 50);
tc.controllers.add(cc);
callback.provider = new DefaultFlowControllerProvider(tc);
Request request = new Request();
MockHttpServletResponse httpResponse = new MockHttpServletResponse();
request.setHttpResponse(httpResponse);
callback.operationDispatched(request, null);
callback.finished(null);
String delayHeader = httpResponse.getHeader(ControlFlowCallback.X_RATELIMIT_DELAY);
assertNotNull(delayHeader);
long delay = Long.parseLong(delayHeader);
assertTrue("Delay should be greater than 50 " + delay, delay >= 50);
}
@Test
public void testFailBeforeOperationDispatch() {
ControlFlowCallback callback = new ControlFlowCallback();
callback.init((Request) null);
callback.finished(null);
assertEquals(0, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
}
@Test
public void testRequestReplaced() {
// setup a controller hitting on GWC
ControlFlowCallback callback = new ControlFlowCallback();
TestingConfigurator tc = new TestingConfigurator();
BasicOWSController controller = new BasicOWSController("GWC", 1);
tc.controllers.add(controller);
callback.provider = new DefaultFlowControllerProvider(tc);
Request r1 = new Request();
r1.setService("GWC");
MockHttpServletResponse httpResponse = new MockHttpServletResponse();
r1.setHttpResponse(httpResponse);
// setup external request
callback.operationDispatched(r1, null);
assertEquals(1, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
// fake a nested WMS request with some code that
Request r2 = new Request(r1);
r2.setService("WMS");
callback.operationDispatched(r2, null);
// no locking happened on the nested one
assertEquals(1, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
// finish nested
callback.finished(r2);
assertEquals(1, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
// finish outer, but simulate code that does set back the outer request (so it's again called with r2)
callback.finished(r2);
// the callback machinery is not fooled and clear stuff anyways
assertEquals(0, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
}
@Test
public void testFinishedNotCalled() throws IOException, ServletException {
// setup a controller hitting on GWC
final ControlFlowCallback callback = new ControlFlowCallback();
TestingConfigurator tc = new TestingConfigurator();
final BasicOWSController controller = new BasicOWSController("GWC", 1);
tc.controllers.add(controller);
callback.provider = new DefaultFlowControllerProvider(tc);
// outer request
final Request r1 = new Request();
r1.setService("GWC");
MockHttpServletRequest httpRequest = new MockHttpServletRequest();
httpRequest.setMethod("GET");
MockHttpServletResponse httpResponse = new MockHttpServletResponse();
r1.setHttpRequest(httpRequest);
r1.setHttpResponse(httpResponse);
final AtomicBoolean servletCalled = new AtomicBoolean(false);
MockFilterChain filterChain = new MockFilterChain(new HttpServletBean() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
servletCalled.set(true);
// setup external request
callback.operationDispatched(r1, null);
assertEquals(1, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
assertEquals(1, controller.getRequestsInQueue());
// fail to call finished
}
}, callback);
filterChain.doFilter(httpRequest, httpResponse);
// check the servlet doing the test has been called
assertTrue(servletCalled.get());
// the callback machinery is not fooled and clears stuff anyways
assertEquals(0, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
assertEquals(0, controller.getRequestsInQueue());
}
@Test
public void testFailNestedRequestParse() throws IOException, ServletException {
// setup a controller hitting on GWC
final ControlFlowCallback callback = new ControlFlowCallback();
TestingConfigurator tc = new TestingConfigurator();
final BasicOWSController controller = new BasicOWSController("GWC", 1);
tc.controllers.add(controller);
callback.provider = new DefaultFlowControllerProvider(tc);
// outer request
final Request r1 = new Request();
r1.setService("GWC");
MockHttpServletRequest httpRequest = new MockHttpServletRequest();
httpRequest.setMethod("GET");
MockHttpServletResponse httpResponse = new MockHttpServletResponse();
r1.setHttpRequest(httpRequest);
r1.setHttpResponse(httpResponse);
final AtomicBoolean servletCalled = new AtomicBoolean(false);
MockFilterChain filterChain = new MockFilterChain(new HttpServletBean() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
servletCalled.set(true);
// setup external request
callback.operationDispatched(r1, null);
assertEquals(1, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
// call the nested one
Request r2 = new Request(r1);
callback.operationDispatched(r2, null);
assertEquals(1, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
assertEquals(1, controller.getRequestsInQueue());
// fail to call finished on either
}
}, callback);
filterChain.doFilter(httpRequest, httpResponse);
// check the servlet doing the test has been called
assertTrue(servletCalled.get());
// the callback machinery is not fooled and clears stuff anyways
assertEquals(0, callback.getRunningRequests());
assertEquals(0, callback.getBlockedRequests());
assertEquals(0, controller.getRequestsInQueue());
}
/**
* A wide open configurator to be used for testing
*/
static class TestingConfigurator implements ControlFlowConfigurator {
List<FlowController> controllers = new ArrayList<FlowController>();
long timeout;
boolean stale = true;
public Collection<FlowController> buildFlowControllers() throws Exception {
stale = false;
return controllers;
}
public long getTimeout() {
return timeout;
}
public boolean isStale() {
return stale;
}
}
/**
* A controller counting requests, can also be used to check for timeouts
*/
static class CountingController implements FlowController {
int priority;
long delay;
int requestCompleteCalls;
int requestIncomingCalls;
public CountingController(int priority, long delay) {
this.priority = priority;
this.delay = delay;
}
public int getPriority() {
return priority;
}
public void requestComplete(Request request) {
requestCompleteCalls++;
}
public boolean requestIncoming(Request request, long timeout) {
requestIncomingCalls++;
if(delay > 0)
if(timeout > delay) {
try {
Thread.sleep(delay);
} catch(InterruptedException e) {
throw new RuntimeException("This is unexpected");
}
} else {
return false;
}
return true;
}
}
}