public class AnnotatedActionMockController {

	@MockAnnotation
	public void action(Request request, Response response) {}

} @MockAnnotation
public class AnnotatedMockController {

	public void action(Request request, Response response) {}

} public interface MiddlewareChain {

	/**
	 * Calls the next middleware.
	 * 
	 * @throws Exception
	 */
	void next() throws Exception;
	
} public class MockController {

	public void init(Request request, Response response) {}

	public void show(Request request, Response response) {}

} public interface Request {

	String getHost();

	/**
	 * Returns the full URL including scheme, host, port, path and query string.
	 *
	 * @return a String object with the URL.
	 */
	String getUrl();

	/**
	 * Retrieves the path that was requested without the context path - if any. For example, if the URL is
	 * "http://localhost:8080/app/users/1", the path would be "/users/1".
	 *
	 * @return a String object with the path of the request.
	 */
	String getPath();

	/**
	 * Retrieves the path variables. Path variables are wildcards that you can add to the url in the routes.config
	 * file.
	 *
	 * @return a Map<String,String> object with the path variables.
	 */
	Map<String,String> getPathVariables();

	/**
	 * Retrieves the value of a path variable.
	 *
	 * @param name the name of the path variable.
	 *
	 * @return a String object. Null if it doesn't exists.
	 */
	String getPathVariable(String name);

	/**
	 * Retrieves the raw query string part of the URL.
	 *
	 * @return a String object with the query string part of the URL. An empty String if there is no query string.
	 */
	String getQueryString();

	/**
	 * Retrieves the request parameters.
	 *
	 * @return a Map<String,String> object with the request parameters.
	 */
	Map<String,String> getParameters();

	/**
	 * Retrieves the value of a request parameter.
	 *
	 * @param name the name of the parameter
	 *
	 * @return a String object. Null if it doesn't exists.
	 */
	String getParameter(String name);

	/**
	 * Returns the HTTP method of the request.
	 *
	 * @return a String object with the HTTP method.
	 */
	String getMethod();

	String getRemoteAddress();

	String getContentType();

	int getPort();

	boolean isSecure();

	/**
	 * Tells whether the request was done using an AJAX call or not.
	 *
	 * @return true if this was an AJAX call, false otherwise.
	 */
	boolean isAjax();

	Map<String,Cookie> getCookies();

	/**
	 * Retrieves the {@link Cookie} with the specified name.
	 *
	 * @param name the name of the cookie to be retrieved.
	 *
	 * @return itself for method chaining.
	 */
	Cookie getCookie(String name);

	Map<String,String> getHeaders();

	String getHeader(String name);

	/**
	 * Retrieves the files from the request (if any).
	 *
	 * @return an array of {@link FileItem} objects or an empty array if no files.
	 */
	FileItem[] getFiles();

	/**
	 * Returns an object that will allow us to retrieve the body in multiple ways.
	 *
	 * @return a {@link BodyParser} implementation.
	 */
	BodyParser getBody();
	
	void setRoute(Route route);

	/**
	 * This is what we returned when the {@link Request#getBody()} is called. Provides convenient methods to parse the
	 * request body.
	 *
	 * @author German Escobar
	 */
	interface BodyParser {

		/**
		 * Returns the body of the request as a string.
		 *
		 * @return a String object with the body.
		 */
		String asString();

		/**
		 * @return the body as an InputStream.
		 */
		InputStream asInputStream();

	}

} public interface Response {

	int OK = 200;
	int CREATED = 201;
	int ACCEPTED = 202;
	int PARTIAL_INFO = 203;
	int NO_RESPONSE = 204;
	int MOVED = 301;
	int FOUND = 302;
	int METHOD = 303;
	int NOT_MODIFIED = 304;
	int BAD_REQUEST = 400;
	int UNAUTHORIZED = 401;
	int PAYMENT_REQUIRED = 402;
	int FORBIDDEN = 403;
	int NOT_FOUND = 404;
	int CONFLICT = 409;
	int INTERNAL_ERROR = 500;
	int NOT_IMPLEMENTED = 501;
	int OVERLOADED = 502;
	int GATEWAY_TIMEOUT = 503;

	int getStatus();

	/**
	 * Sets the status of the response to the received argument.
	 *
	 * @param status the status to set to the response.
	 *
	 * @return itself for method chaining.
	 */
	Response status(int status);

	/**
	 * Sets the status of the response to 400 - Bad Request.
	 *
	 * @return itself for method chaining.
	 */
	Response badRequest();

	/**
	 * Sets the status of the response to 401 - Unauthorized.
	 *
	 * @return itself for method chaining.
	 */
	Response unauthorized();

	/**
	 * Sets the status of the response to 404 - Not Found.
	 *
	 * @return itself for method chaining.
	 */
	Response notFound();

	/**
	 * Sets the status of the response to 409 - Conflict
	 *
	 * @return itself for method chaining.
	 */
	Response conflict();

	String getContentType();

	Response contentType(String contentType);

	String getHeader(String name);

	Response setHeader(String name, String value);

	/**
	 * Sets a cookie.
	 *
	 * @param cookie the {@link Cookie} to be set.
	 *
	 * @return itself for method chaining.
	 */
	Response setCookie(Cookie cookie);

	/**
	 * Removes a cookie.
	 *
	 * @param cookie the name of the cookie to be removed.
	 *
	 * @return itself for method chaining.
	 */
	Response removeCookie(Cookie cookie);

	Map<String,Object> getAttributes();

	Response setAttribute(String name, Object object);

	/**
	 * Writes an HTML string into the response.
	 *
	 * @param output the output code to write in the response.
	 *
	 * @return itself for method chaining.
	 */
	Response write(String output);

	/**
	 * Writes an InputStream into the response.
	 *
	 * @param asset the {@link Asset} to write in the response.
	 *
	 * @return itself for method chaining.
	 */
	Response write(Asset asset);

	/**
	 * Renders the specified template with no additional attributes (besides those already in the response)
	 *
	 * @param templateName the name of the template to be rendered.
	 *
	 * @return itself for method chaining.
	 * @throws TemplateException
	 */
	Response render(String templateName) throws TemplateException;

	/**
	 * Renders the specified template with the specified attributes (and those already in the response). If an
	 * attribute with the same name already exists in the response it will be overriden.
	 *
	 * @param templateName the name of the template to be rendered.
	 * @param attributes a map of attributes to be passed to the view.
	 *
	 * @return itself for method chaining.
	 * @throws TemplateException
	 */
	Response render(String templateName, Map<String,Object> attributes) throws TemplateException;

	/**
	 * Sends a redirect.
	 *
	 * @param path the path to which the request is redirected.
	 */
	void redirect(String path);
	
	boolean isWritten();

} public class Route {

	public enum HttpMethod {
		GET,
		POST,
		PUT,
		DELETE,
		OPTIONS,
		HEAD,
		PATCH
	}

	private final HttpMethod httpMethod;

	private final String path;

	private final Object controller;

	private final Method action;

	public Route(HttpMethod httpMethod, String path, Object controller, Method action) {
		Preconditions.notNull(httpMethod, "no httpMethod provided");
		Preconditions.notNull(path, "no path provided");
		Preconditions.notNull(controller, "no controller provided");
		Preconditions.notNull(action, "no action provided");

		this.httpMethod = httpMethod;
		this.path = Path.fixPath(path);
		this.controller = controller;
		this.action = action;
	}

	public HttpMethod getHttpMethod() {
		return httpMethod;
	}

	public String getPath() {
		return path;
	}

	public Object getController() {
		return controller;
	}

	public Method getAction() {
		return action;
	}

} public interface RouteHandler {

	/**
	 * This method is called when an HTTP request matches the route for which this route handler is configured.
	 *
	 * @param request the Jogger HTTP request.
	 * @param response the Jogger HTTP response.
	 */
	void handle(Request request, Response response);

} public class RouterMiddleware implements Middleware {
	
	/**
	 * The list of routes.
	 */
	private List<Route> routes = new CopyOnWriteArrayList<Route>();

	/**
	 * The list of interceptors.
	 */
	private List<InterceptorEntry> interceptors = new CopyOnWriteArrayList<InterceptorEntry>();
	
	@Override
	public void handle(Request request, Response response, MiddlewareChain chain) throws Exception {
		Route route = getRoute(request.getMethod(), request.getPath());
		if (route == null) {
			chain.next();
			return;
		}
		
		request.setRoute(route);
		response.status(Response.OK);
		
		// load the interceptors of the request
		List<Interceptor> requestInterceptors = getInterceptors(request.getPath());

		// execute the controller
		ControllerExecutor controllerExecutor = new ControllerExecutor(route, request, response, requestInterceptors);
		controllerExecutor.proceed();
	}
	
	/**
	 * Returns a list of interceptors that match a path
	 *
	 * @param path
	 * @return a list of {@link Interceptor} objects.
	 */
	private List<Interceptor> getInterceptors(String path) {
		List<Interceptor> ret = new ArrayList<Interceptor>();

		for (InterceptorEntry entry : getInterceptors()) {
			if (matches(path, entry.getPaths())) {
				ret.add(entry.getInterceptor());
			}
		}

		return ret;
	}

	/**
	 * Helper method. Checks if the <code>path</code> is in the array of <code>paths</code>.
	 *
	 * @param path the path we want to check.
	 * @param paths the paths to match.
	 *
	 * @return true if the <code>path</code> matches the <code>paths</code> (at least one), false otherwise.
	 */
	private boolean matches(String path, String... paths) {
		if (paths.length == 0) {
			return true;
		}

		for (String p : paths) {
			/* TODO we should use the same mechanism servlets use to match paths */
			if (p.equalsIgnoreCase(path)) {
				return true;
			}
		}

		return false;
	}
	
	/**
	 * Retrieves the {@link Route} that matches the specified <code>httpMethod</code> and <code>path</code>.
	 *
	 * @param httpMethod the HTTP method to match. Should not be null or empty.
	 * @param path the path to match. Should not be null but can be empty (which is interpreted as /)
	 *
	 * @return a {@link Route} object that matches the arguments or null if no route matches.
	 */
	private Route getRoute(String httpMethod, String path) {
		Preconditions.notEmpty(httpMethod, "no httpMethod provided.");
		Preconditions.notNull(path, "no path provided.");

		String cleanPath = parsePath(path);

		for (Route route : routes) {
			if (matchesPath(route.getPath(), cleanPath) && route.getHttpMethod().toString().equalsIgnoreCase(httpMethod)) {
				return route;
			}
		}

		return null;
	}
	
	private String parsePath(String path) {
		path = Path.fixPath(path);

		try {
			URI uri = new URI(path);
			return uri.getPath();
		} catch (URISyntaxException e) {
			return null;
		}
	}

	/**
	 * Helper method. Tells if the the HTTP path matches the route path.
	 *
	 * @param routePath the path defined for the route.
	 * @param pathToMatch the path from the HTTP request.
	 *
	 * @return true if the path matches, false otherwise.
	 */
	private boolean matchesPath(String routePath, String pathToMatch) {
		routePath = routePath.replaceAll(Path.VAR_REGEXP, Path.VAR_REPLACE);
		return pathToMatch.matches("(?i)" + routePath);
	}
	
	public List<Route> getRoutes() {
		return routes;
	}

	public void setRoutes(List<Route> routes) {
		Preconditions.notNull(routes, "no routes provided");
		this.routes = routes;
	}
	
	/**
	 * Adds a route to the list of routes using a {@link Route} object.
	 *
	 * @param route the route to be added.
	 */
	public void addRoute(Route route) {
		Preconditions.notNull(route, "no route provided");
		this.routes.add(route);
	}
	
	/**
	 * Creates a {@link Route} object from the received arguments and adds it to the list of routes.
	 *
	 * @param httpMethod the HTTP method to which this route is going to respond.
	 * @param path the path to which this route is going to respond.
	 * @param controller the object that will be invoked when this route matches.
	 * @param methodName the name of the method in the <code>controller</code> object that will be invoked when this
	 * route matches.
	 *
	 * @throws NoSuchMethodException if the <code>methodName</code> is not found or doesn't have the right signature.
	 */
	public void addRoute(HttpMethod httpMethod, String path, Object controller, String methodName) throws NoSuchMethodException {
		Preconditions.notNull(controller, "no controller provided");
		Method method = controller.getClass().getMethod(methodName, Request.class, Response.class);
		addRoute(httpMethod, path, controller, method);
	}
	
	/**
	 * Creates a {@link Route} object from the received arguments and adds it to the list of routes.
	 *
	 * @param httpMethod the HTTP method to which this route is going to respond.
	 * @param path the path to which this route is going to respond.
	 * @param controller the object that will be invoked when this route matches.
	 * @param method the Method that will be invoked when this route matches.
	 */
	public void addRoute(HttpMethod httpMethod, String path, Object controller, Method method) {
		// validate signature
		Class<?>[] paramTypes = method.getParameterTypes();
		if (paramTypes.length != 2 || !paramTypes[0].equals(Request.class) || !paramTypes[1].equals(Response.class)) {
			throw new RoutesException("Expecting two params of type com.elibom.jogger.http.Request and com.elibom.jogger.http.Response "
					+ "respectively");
		}

		method.setAccessible(true); // to access methods from anonymous classes
		routes.add(new Route(httpMethod, path, controller, method));
	}
	
	/**
	 * Creates a {@link Route} object and adds it to the routes list. It will respond to the GET HTTP method and the
	 * specified <code>path</code> invoking the {@link RouteHandler} object.
	 *
	 * @param path the path to which this route will respond.
	 * @param handler the object that will be invoked when the route matches.
	 */
	public void get(String path, RouteHandler handler) {
		try {
			addRoute(HttpMethod.GET, path, handler, "handle");
		} catch (NoSuchMethodException e) {
			// shouldn't happen unless we change the name of the method in RouteHandler
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Creates a {@link Route} object and adds it to the routes list. It will respond to the POST HTTP method and the
	 * specified <code>path</code> invoking the {@link RouteHandler} object.
	 *
	 * @param path the path to which this route will respond.
	 * @param handler the object that will be invoked when the route matches.
	 */
	public void post(String path, RouteHandler handler) {
		try {
			addRoute(HttpMethod.POST, path, handler, "handle");
		} catch (NoSuchMethodException e) {
			// shouldn't happen unless we change the name of the method in RouteHandler
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Creates a {@link Route} object and adds it to the routes list. It will respond to the PUT HTTP method and the
	 * specified <code>path</code> invoking the {@link RouteHandler} object.
	 *
	 * @param path the path to which this route will respond.
	 * @param handler the object that will be invoked when the route matches.
	 */
	public void put(String path, RouteHandler handler) {
		try {
			addRoute(HttpMethod.PUT, path, handler, "handle");
		} catch (NoSuchMethodException e) {
			// shouldn't happen unless we change the name of the method in RouteHandler
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Creates a {@link Route} object and adds it to the routes list. It will respond to the DELETE HTTP method and the
	 * specified <code>path</code> invoking the {@link RouteHandler} object.
	 *
	 * @param path the path to which this route will respond.
	 * @param handler the object that will be invoked when the route matches.
	 */
	public void delete(String path, RouteHandler handler) {
		try {
			addRoute(HttpMethod.DELETE, path, handler, "handle");
		} catch (NoSuchMethodException e) {
			// shouldn't happen unless we change the name of the method in RouteHandler
			throw new RuntimeException(e);
		}
	}
	
	public List<InterceptorEntry> getInterceptors() {
		return interceptors;
	}

	public void setInterceptors(List<InterceptorEntry> interceptors) {
		Preconditions.notNull(interceptors, "no interceptors provided");
		this.interceptors = interceptors;
	}

	/**
	 * Adds the <code>interceptor</code> to the list of interceptors that will matches the specified
	 * <code>paths</code>.
	 *
	 * @param interceptor the interceptor object to be added.
	 * @param paths the paths in which this interceptor will be invoked, an empty array to respond to all paths.
	 */
	public void addInterceptor(Interceptor interceptor, String... paths) {
		Preconditions.notNull(interceptor, "no interceptor provided");
		interceptors.add(new InterceptorEntry(interceptor, paths));
	}
	
	/**
	 * This is a helper class that executes the interceptor chain and calls the controller. Notice that this class uses
	 * recursion to call each interceptor and the controller. It uses an index to keep track of the next interceptor to be
	 * executed. Finally, it calls the controller.
	 *
	 * @author German Escobar
	 */
	private class ControllerExecutor implements InterceptorExecution {

		private Route route;

		private Request request;

		private Response response;

		private List<Interceptor> interceptors;

		private int index = 0;

		/**
		 * Constructor. Initializes the object with the specified parameters.
		 *
		 * @param route
		 * @param request the Jogger HTTP request.
		 * @param response the Jogger HTTP response.
		 * @param interceptors a list of interceptors that we need to execute before calling the action.
		 */
		public ControllerExecutor(Route route, Request request, Response response, List<Interceptor> interceptors) {
			this.route = route;
			this.request = request;
			this.response = response;
			this.interceptors = interceptors;
		}

		@Override
		public void proceed() throws Exception {
			// if we finished executing all the interceptors, call the controller method
			if (index == interceptors.size()) {

				Object controller = route.getController();
				Method method = route.getAction();

				try {
					method.invoke(controller, request, response);
				} catch (InvocationTargetException e) {
					throw (Exception) e.getCause();
				}

				return;
			}

			// retrieve the interceptor and increase the index
			Interceptor interceptor = interceptors.get(index);
			index++;

			// execute the interceptor - notice that the interceptor can eventually call the proceed() method recursively.
			interceptor.intercept(request, response, this);
		}

		@Override
		public Controller getController() {
			// create and return a new instance of the Controller class
			return new Controller() {
				public <A extends Annotation> A getAnnotation(Class<A> annotation) {
					return route.getController().getClass().getAnnotation(annotation);
				}
			};
		}

		@Override
		public Action getAction() {
			// create and return a new instance of the Action class
			return new Action() {
				public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
					return findAnnotation(route.getAction(), annotationClass);
				}

				/**
				 * Helper method. Tries to find the annotation in the method or its super methods. Annotations on
				 * methods are not inherited by default, so we need to handle this explicitly.
				 *
				 * @param method the method from which we want to find the annotation.
				 * @param annotationClass the class of the annotation we are searching for.
				 *
				 * @return the annotation or null if not found.
				 */
				private <A extends Annotation> A findAnnotation(Method method, Class<A> annotationClass) {
					A annotation = method.getAnnotation(annotationClass);
					if (annotation != null) {
						return annotation;
					}

					Method superMethod = getSuperMethod(method);
					if (superMethod != null) {
						return findAnnotation(superMethod, annotationClass);
					}

					return null;
				}

				private Method getSuperMethod(Method method) {
					Class<?> superClass = method.getDeclaringClass().getSuperclass();
					try {
						return superClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
					} catch (NoSuchMethodException e) {
						return null;
					}
				}
			};
		}
	}
} public enum HttpMethod {
	GET,
	POST,
	PUT,
	DELETE,
	OPTIONS,
	HEAD,
	PATCH
} public interface Interceptor {

	/**
	 * This method is called when a matching request is received. Implementations of this method are responsible of
	 * calling the {@link InterceptorExecution#proceed()} method to continue with the execution of the request unless it
	 * actually wants to stop the execution (in which case it should set the response in the controller)
	 *
	 * @param request
	 * @param response
	 * @param execution
	 *
	 * @throws Exception
	 */
	void intercept(Request request, Response response, InterceptorExecution execution) throws Exception;

} public interface InterceptorExecution {

	/**
	 * Causes the next interceptor (or controller if there are no more interceptors) to be invoked.
	 *
	 * @throws Exception
	 */
	void proceed() throws Exception;

	/**
	 * Retrieves the controller. Useful to retrieve information from the controller.
	 *
	 * @return a {@link Controller} implementation.
	 */
	Controller getController();

	/**
	 * Retrieves the action. Useful to retrieve information from the action.
	 *
	 * @return a {@link Action} implementation.
	 */
	Action getAction();

}
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.elibom.jogger.AnnotatedActionMockController;
import com.elibom.jogger.AnnotatedMockController;
import com.elibom.jogger.MiddlewareChain;
import com.elibom.jogger.MockAnnotation;
import com.elibom.jogger.MockController;
import com.elibom.jogger.http.Request;
import com.elibom.jogger.http.Response;
import com.elibom.jogger.middleware.router.Route;
import com.elibom.jogger.middleware.router.RouteHandler;
import com.elibom.jogger.middleware.router.RouterMiddleware;
import com.elibom.jogger.middleware.router.Route.HttpMethod;
import com.elibom.jogger.middleware.router.interceptor.Interceptor;
import com.elibom.jogger.middleware.router.interceptor.InterceptorExecution;
import org.testng.Assert;
import org.testng.annotations.Test;
package com.elibom.jogger.middleware.router;




public class RouterMiddlewareTest {

	@Test
	public void shouldAddRoutes() throws Exception {
		RouterMiddleware jogger = new RouterMiddleware();

		MockController controller = new MockController();
		Method initMethod = MockController.class.getMethod("init", Request.class, Response.class);
		Method showMethod = MockController.class.getMethod("show", Request.class, Response.class);

		List<Route> routes = new ArrayList<Route>();
		routes.add( new Route(HttpMethod.GET, "/", controller, initMethod) );
		routes.add( new Route(HttpMethod.GET, "/", controller, showMethod) );

		jogger.setRoutes(routes);

		jogger.addRoute(HttpMethod.GET, "/test", controller, "init");
		jogger.addRoute(HttpMethod.GET, "/test", controller, showMethod);

		jogger.get("/another", mock(RouteHandler.class));
		jogger.post("/another", mock(RouteHandler.class));

		Assert.assertEquals(jogger.getRoutes().size(), 6);
	}
	
	@Test(dependsOnMethods="shouldAddRoutes")
	public void shouldMatchEmptyRoute() throws Exception {
		shouldMatchRoute(HttpMethod.GET, "", "");
		shouldMatchRoute(HttpMethod.GET, "", "/");
	}
	
	private void shouldMatchRoute(HttpMethod method, String routePath, String path) throws Exception {
		RouterMiddleware router = new RouterMiddleware();
		RouteHandler handler = mock(RouteHandler.class);
		router.addRoute(method, routePath, handler, "handle");
		
		Request request = mock(Request.class);
		when(request.getMethod()).thenReturn(method.toString().toUpperCase());
		when(request.getPath()).thenReturn(path);
		
		router.handle(request, mock(Response.class), mock(MiddlewareChain.class));
		verify(handler).handle(any(Request.class), any(Response.class));
		
		reset(handler);
		
		when(request.getMethod()).thenReturn(method.toString().toLowerCase());
		router.handle(request, mock(Response.class), mock(MiddlewareChain.class));
		verify(handler).handle(any(Request.class), any(Response.class));
	}
	
	@Test(dependsOnMethods="shouldAddRoutes")
	public void shouldMatchSimplePath() throws Exception {
		shouldMatchRoute(HttpMethod.GET, "/test", "/test");
		shouldMatchRoute(HttpMethod.GET, "/test", "/test/");
	}
	
	@Test(dependsOnMethods="shouldAddRoutes")
	public void shouldMatchPathWithVariables() throws Exception {
		shouldMatchRoute(HttpMethod.POST, "/test/{id}/", "/Test/1");
		shouldMatchRoute(HttpMethod.POST, "/test/{id}/", "/test/1-234234/");
	}
	
	@Test(dependsOnMethods="shouldAddRoutes")
	public void shouldMatchComplexPath() throws Exception {
		shouldMatchRoute(HttpMethod.POST, "/test/{id_test}/mocks/{id_mock}", "/test/1/mocks/2");
		shouldMatchRoute(HttpMethod.POST, "/test/{id_test}/mocks/{id_mock}", "/test/1/mocks/2/");
	}
	
	@Test(dependsOnMethods="shouldAddRoutes")
	public void shouldNotFindRouteWithWrongHttpMethod() throws Exception {
		RouterMiddleware router = new RouterMiddleware();
		RouteHandler handler = mock(RouteHandler.class);
		router.addRoute(HttpMethod.GET, "", handler, "handle");
		
		Request request = mock(Request.class);
		when(request.getMethod()).thenReturn("post");
		when(request.getPath()).thenReturn("/");
		Response response = mock(Response.class);
		
		router.handle(request, response, mock(MiddlewareChain.class));
		verify(handler, never()).handle(any(Request.class), any(Response.class));
		verify(response, never()).notFound();
		verify(response, never()).status(Response.NOT_FOUND);
	}
	
	@Test(dependsOnMethods="shouldAddRoutes")
	public void shouldNotFindRouteThatMatchesNoPath() throws Exception {
		RouterMiddleware router = new RouterMiddleware();
		RouteHandler handler = mock(RouteHandler.class);
		router.addRoute(HttpMethod.GET, "", handler, "handle");
		
		Request request = mock(Request.class);
		when(request.getMethod()).thenReturn("post");
		when(request.getPath()).thenReturn("/");
		Response response = mock(Response.class);
		
		router.handle(request, response, mock(MiddlewareChain.class));
		verify(handler, never()).handle(any(Request.class), any(Response.class));
		verify(response, never()).notFound();
		verify(response, never()).status(Response.NOT_FOUND);
	}
	
	@Test
	public void shouldCallInterceptors() throws Exception {
		RouterMiddleware router = new RouterMiddleware();
		RouteHandler handler = mock(RouteHandler.class);
		router.get("/", handler);
		
		ProceedInterceptor interceptor1 = new ProceedInterceptor();
		ProceedInterceptor interceptor2 = new ProceedInterceptor();
		router.addInterceptor(interceptor1);
		router.addInterceptor(interceptor2);
		
		router.handle(mockRequest("get", "/"), mock(Response.class), mock(MiddlewareChain.class));
		
		Assert.assertTrue(interceptor1.wasCalled());
		Assert.assertFalse(interceptor1.getControllerHasAnnotation());
		Assert.assertFalse(interceptor1.getActionHasAnnotation());
		Assert.assertTrue(interceptor2.wasCalled());

		verify(handler).handle(any(Request.class), any(Response.class));
	}
	
	private Request mockRequest(String httpMethod, String path) {
		Request request = mock(Request.class);
		when(request.getMethod()).thenReturn(httpMethod);
		when(request.getPath()).thenReturn(path);

		return request;
	}
	
	@Test
	public void shouldRetrieveControllerAnnotation() throws Exception {
		AnnotatedMockController controller = new AnnotatedMockController();