/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.everrest.core.impl;
import org.everrest.core.ApplicationContext;
import org.everrest.core.ObjectFactory;
import org.everrest.core.ResourceBinder;
import org.everrest.core.impl.async.AsynchronousJob;
import org.everrest.core.impl.header.AcceptMediaType;
import org.everrest.core.impl.uri.PathSegmentImpl;
import org.everrest.core.impl.uri.UriBuilderImpl;
import org.everrest.core.method.MethodInvoker;
import org.everrest.core.resource.GenericResourceMethod;
import org.everrest.core.resource.ResourceDescriptor;
import org.everrest.core.resource.ResourceMethodDescriptor;
import org.everrest.core.resource.SubResourceLocatorDescriptor;
import org.everrest.core.resource.SubResourceMethodDescriptor;
import org.everrest.core.uri.UriPattern;
import org.everrest.core.util.ResourceMethodComparator;
import org.everrest.core.util.UriPatternComparator;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static javax.ws.rs.core.HttpHeaders.ALLOW;
import static javax.ws.rs.core.HttpHeaders.LOCATION;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
import static javax.ws.rs.core.MediaType.APPLICATION_XML_TYPE;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
import static javax.ws.rs.core.MediaType.TEXT_XML_TYPE;
import static javax.ws.rs.core.MediaType.WILDCARD_TYPE;
import static javax.ws.rs.core.Response.Status.ACCEPTED;
import static javax.ws.rs.core.Response.Status.METHOD_NOT_ALLOWED;
import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.OK;
import static javax.ws.rs.core.Response.Status.UNSUPPORTED_MEDIA_TYPE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
public class RequestDispatcherTest {
@Rule public ExpectedException thrown = ExpectedException.none();
private ResourceBinder resources;
private ContainerRequest request;
private ContainerResponse response;
private ApplicationContext applicationContext;
private MethodInvoker methodInvoker;
private List<String> pathParameterValues;
private RequestDispatcher requestDispatcher;
@Before
public void setUp() throws Exception {
mockContainerRequest("POST");
response = mock(ContainerResponse.class);
resources = mock(ResourceBinder.class);
methodInvoker = mock(MethodInvoker.class);
mockApplicationContext();
requestDispatcher = new RequestDispatcher(resources);
}
private void mockContainerRequest(String httpMethod) {
request = mock(ContainerRequest.class);
when(request.getMethod()).thenReturn(httpMethod);
when(request.getAcceptMediaTypeList()).thenReturn(newArrayList(new AcceptMediaType()));
}
private void mockApplicationContext() {
applicationContext = mock(ApplicationContext.class, RETURNS_DEEP_STUBS);
when(applicationContext.getQueryParameters()).thenReturn(new MultivaluedHashMap<>());
when(applicationContext.getMethodInvoker(isA(GenericResourceMethod.class))).thenReturn(methodInvoker);
pathParameterValues = newArrayList();
when(applicationContext.getParameterValues()).thenReturn(pathParameterValues);
when(applicationContext.getAttributes().get("org.everrest.lifecycle.PerRequest")).thenReturn(newArrayList());
when(applicationContext.getBaseUriBuilder()).thenReturn(UriBuilderImpl.fromPath("http://localhost:8080/servlet"));
ApplicationContext.setCurrent(applicationContext);
}
@Test
public void returnsResponseWithStatus_NOT_FOUND_WhenRootResourceNotFound() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(NOT_FOUND, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void callsResourceMethod() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethod =
mockResourceMethod(Resource.class.getMethod("echo", String.class), "POST", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethod), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(resource), same(resourceMethod), same(applicationContext))).thenReturn("foo");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("foo", argumentCaptor.getValue().getEntity());
}
@Test
public void returnsResponseWithStatus_NO_CONTENT_WhenResourceMethodReturnsNull() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethod =
mockResourceMethod(Resource.class.getMethod("echo", String.class), "POST", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethod), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(resource), same(resourceMethod), same(applicationContext))).thenReturn(null);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(NO_CONTENT, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void returnsResponseWithStatus_ACCEPTED_WhenResourceMethodReturnsAsynchronousJob() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethod =
mockResourceMethod(Resource.class.getMethod("echo", String.class), "POST", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethod), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
AsynchronousJob asynchronousJob = mock(AsynchronousJob.class);
when(asynchronousJob.getJobURI()).thenReturn("async/1");
when(methodInvoker.invokeMethod(same(resource), same(resourceMethod), same(applicationContext))).thenReturn(asynchronousJob);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(ACCEPTED, argumentCaptor.getValue().getStatusInfo());
assertEquals("http://localhost:8080/servlet/async/1", argumentCaptor.getValue().getHeaderString(LOCATION));
}
@Test
public void returnsResponseProducedByResourceMethod() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethod =
mockResourceMethod(Resource.class.getMethod("echo", String.class), "POST", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethod), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
Response methodResponse = mock(Response.class);
when(methodResponse.getMetadata()).thenReturn(new MultivaluedHashMap<>());
when(methodInvoker.invokeMethod(same(resource), same(resourceMethod), same(applicationContext))).thenReturn(methodResponse);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertSame(methodResponse, argumentCaptor.getValue());
}
@Test
public void callsResourceMethodRespectsConsumedMediaTypes() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
when(request.getMediaType()).thenReturn(APPLICATION_XML_TYPE);
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethodOne = mockResourceMethod(Resource.class.getMethod("echo", String.class),
"POST",
newArrayList(TEXT_XML_TYPE, APPLICATION_XML_TYPE),
newArrayList(WILDCARD_TYPE));
ResourceMethodDescriptor resourceMethodTwo = mockResourceMethod(Resource.class.getMethod("echo", String.class),
"POST",
newArrayList(APPLICATION_JSON_TYPE),
newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethodOne, resourceMethodTwo), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(resource), same(resourceMethodOne), same(applicationContext))).thenReturn("foo");
when(methodInvoker.invokeMethod(same(resource), same(resourceMethodTwo), same(applicationContext))).thenReturn("bar");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("foo", argumentCaptor.getValue().getEntity());
}
@Test
public void returnsResponseWithStatus_NOT_FOUND_WhenResourceMethodNotFound() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
Resource resource = new Resource();
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(NOT_FOUND, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void returnsResponseWithStatus_METHOD_NOT_ALLOWED_WhenNotFoundResourceMethodWithRequestedHttpMethod() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethod =
mockResourceMethod(Resource.class.getMethod("echo", String.class), "GET", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethod), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(METHOD_NOT_ALLOWED, argumentCaptor.getValue().getStatusInfo());
String allowHeader = argumentCaptor.getValue().getHeaderString(ALLOW);
assertEquals("GET", allowHeader);
}
@Test
public void returnsResponseWithStatus_UNSUPPORTED_MEDIA_TYPE_WhenResourceMethodDoesNotSupportContentTypeOfRequest() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
when(request.getMediaType()).thenReturn(APPLICATION_JSON_TYPE);
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethod =
mockResourceMethod(Resource.class.getMethod("echo", String.class), "POST", newArrayList(TEXT_PLAIN_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethod), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(UNSUPPORTED_MEDIA_TYPE, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void callsResourceMethodRespectsProducedMediaTypes() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
when(request.getAcceptMediaTypeList()).thenReturn(newArrayList(new AcceptMediaType(TEXT_XML_TYPE, 0.7f),
new AcceptMediaType(TEXT_PLAIN_TYPE)));
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethodOne = mockResourceMethod(Resource.class.getMethod("echo", String.class),
"POST",
newArrayList(WILDCARD_TYPE),
newArrayList(TEXT_XML_TYPE, APPLICATION_XML_TYPE));
ResourceMethodDescriptor resourceMethodTwo = mockResourceMethod(Resource.class.getMethod("echo", String.class),
"POST",
newArrayList(WILDCARD_TYPE),
newArrayList(TEXT_PLAIN_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethodOne, resourceMethodTwo), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(resource), same(resourceMethodOne), same(applicationContext))).thenReturn("<bar/>");
when(methodInvoker.invokeMethod(same(resource), same(resourceMethodTwo), same(applicationContext))).thenReturn("foo");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("foo", argumentCaptor.getValue().getEntity());
}
@Test
public void returnsResponseWithStatus_NOT_ACCEPTABLE_WhenResourceMethodDoesNotProduceContentAcceptableByCaller() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b;x=y"));
when(request.getAcceptMediaTypeList()).thenReturn(newArrayList(new AcceptMediaType(TEXT_PLAIN_TYPE)));
Resource resource = new Resource();
ResourceMethodDescriptor resourceMethod = mockResourceMethod(Resource.class.getMethod("echo", String.class),
"POST",
newArrayList(WILDCARD_TYPE),
newArrayList(TEXT_XML_TYPE, APPLICATION_XML_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(resourceMethod), newArrayList(), newArrayList());
matchRequestPath();
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(NOT_ACCEPTABLE, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void callsSubResourceMethod() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
Resource resource = new Resource();
SubResourceMethodDescriptor subResourceMethod =
mockSubResourceMethod(Resource.class.getMethod("echo", String.class), "c/d", "POST", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(), newArrayList(subResourceMethod), newArrayList());
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(resource), same(subResourceMethod), same(applicationContext))).thenReturn("foo");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("foo", argumentCaptor.getValue().getEntity());
}
@Test
public void callsSubResourceMethodRespectsProducedMediaTypes() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
when(request.getAcceptMediaTypeList()).thenReturn(newArrayList(new AcceptMediaType(TEXT_XML_TYPE, 0.7f),
new AcceptMediaType(TEXT_PLAIN_TYPE)));
Resource resource = new Resource();
SubResourceMethodDescriptor subResourceMethodOne =
mockSubResourceMethod(Resource.class.getMethod("echo", String.class), "c/d", "POST", newArrayList(WILDCARD_TYPE), newArrayList(TEXT_PLAIN_TYPE));
SubResourceMethodDescriptor subResourceMethodTwo =
mockSubResourceMethod(Resource.class.getMethod("echo", String.class), "c/d", "POST", newArrayList(WILDCARD_TYPE), newArrayList(TEXT_XML_TYPE, APPLICATION_XML_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(), newArrayList(subResourceMethodOne, subResourceMethodTwo), newArrayList());
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(resource), same(subResourceMethodOne), same(applicationContext))).thenReturn("foo");
when(methodInvoker.invokeMethod(same(resource), same(subResourceMethodTwo), same(applicationContext))).thenReturn("<bar/>");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("foo", argumentCaptor.getValue().getEntity());
}
@Test
public void findsSubResourceWithRequestedHttpMethodWhenPathMatchesToMoreThanOneSubResourcesPaths() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b"));
when(request.getMethod()).thenReturn("DELETE");
when(request.getAcceptMediaTypeList()).thenReturn(newArrayList(new AcceptMediaType(TEXT_PLAIN_TYPE)));
Resource resource = new Resource();
SubResourceMethodDescriptor subResourceMethodOne =
mockSubResourceMethod(Resource.class.getMethod("echo", String.class),"(.*)", "GET", newArrayList(WILDCARD_TYPE), newArrayList(TEXT_PLAIN_TYPE));
SubResourceMethodDescriptor subResourceMethodTwo =
mockSubResourceMethod(Resource.class.getMethod("echo", String.class), ".*", "DELETE", newArrayList(WILDCARD_TYPE), newArrayList(TEXT_PLAIN_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(), newArrayList(subResourceMethodOne, subResourceMethodTwo), newArrayList());
matchRequestPath("b");
when(resources.getMatchedResource(eq("/a/b"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(resource), same(subResourceMethodOne), same(applicationContext))).thenReturn("get");
when(methodInvoker.invokeMethod(same(resource), same(subResourceMethodTwo), same(applicationContext))).thenReturn("delete");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("delete", argumentCaptor.getValue().getEntity());
}
@Test
public void returnsResponseWithStatus_METHOD_NOT_ALLOWED_WhenNotFoundSubResourceMethodWithRequestedHttpMethod() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
Resource resource = new Resource();
SubResourceMethodDescriptor subResourceMethod =
mockSubResourceMethod(Resource.class.getMethod("echo", String.class), "c/d", "GET", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(), newArrayList(subResourceMethod), newArrayList());
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response).setResponse(argumentCaptor.capture());
assertEquals(METHOD_NOT_ALLOWED, argumentCaptor.getValue().getStatusInfo());
String allowHeader = argumentCaptor.getValue().getHeaderString(ALLOW);
assertEquals("GET", allowHeader);
}
@Test
public void returnsResponseWithStatus_UNSUPPORTED_MEDIA_TYPE_WhenSubResourceMethodDoesNotSupportContentTypeOfRequest() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
when(request.getMediaType()).thenReturn(APPLICATION_JSON_TYPE);
Resource resource = new Resource();
SubResourceMethodDescriptor subResourceMethod =
mockSubResourceMethod(Resource.class.getMethod("echo", String.class), "c/d", "POST", newArrayList(TEXT_PLAIN_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(), newArrayList(subResourceMethod), newArrayList());
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(UNSUPPORTED_MEDIA_TYPE, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void returnsResponseWithStatus_NOT_ACCEPTABLE_WhenSubResourceMethodDoesNotProduceContentAcceptableByCaller() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
when(request.getAcceptMediaTypeList()).thenReturn(newArrayList(new AcceptMediaType(TEXT_PLAIN_TYPE)));
Resource resource = new Resource();
SubResourceMethodDescriptor subResourceMethod = mockSubResourceMethod(Resource.class.getMethod("echo", String.class), "c/d", "POST",
newArrayList(WILDCARD_TYPE), newArrayList(TEXT_XML_TYPE, APPLICATION_XML_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(resource, newArrayList(), newArrayList(subResourceMethod), newArrayList());
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(NOT_ACCEPTABLE, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void callsSubResourceLocator() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
LocatorResource locatorResource = new LocatorResource();
SubResourceLocatorDescriptor subResourceLocator = mockSubResourceLocator(LocatorResource.class.getMethod("resource"), "c/d");
ObjectFactory resourceFactory = mockResourceFactory(locatorResource, newArrayList(), newArrayList(), newArrayList(subResourceLocator));
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
SubResource subResource = new SubResource();
when(methodInvoker.invokeMethod(same(locatorResource), same(subResourceLocator), same(applicationContext))).thenReturn(subResource);
when(methodInvoker.invokeMethod(same(subResource), isA(ResourceMethodDescriptor.class), same(applicationContext))).thenReturn("foo");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("foo", argumentCaptor.getValue().getEntity());
}
@Test
public void returnsResponseWithStatus_METHOD_NOT_ALLOWED_WhenSubResourceLocatorProducesResourceThatDoesNotSupportRequestedHttpMethod() throws Exception {
when(request.getMethod()).thenReturn("GET");
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
LocatorResource locatorResource = new LocatorResource();
SubResourceLocatorDescriptor subResourceLocator = mockSubResourceLocator(LocatorResource.class.getMethod("resource"), "c/d");
ObjectFactory resourceFactory = mockResourceFactory(locatorResource, newArrayList(), newArrayList(), newArrayList(subResourceLocator));
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(locatorResource), same(subResourceLocator), same(applicationContext))).thenReturn(new SubResource());
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(METHOD_NOT_ALLOWED, argumentCaptor.getValue().getStatusInfo());
String allowHeader = argumentCaptor.getValue().getHeaderString(ALLOW);
assertEquals("POST,OPTIONS", allowHeader);
}
@Test
public void returnsResponseWithStatus_UNSUPPORTED_MEDIA_TYPE_WhenSubResourceLocatorProducesResourceThatDoesNotSupportContentTypeOfRequest() throws Exception {
when(request.getMediaType()).thenReturn(APPLICATION_JSON_TYPE);
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
LocatorResource locatorResource = new LocatorResource();
SubResourceLocatorDescriptor subResourceLocator = mockSubResourceLocator(LocatorResource.class.getMethod("resource"), "c/d");
ObjectFactory resourceFactory = mockResourceFactory(locatorResource, newArrayList(), newArrayList(), newArrayList(subResourceLocator));
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(locatorResource), same(subResourceLocator), same(applicationContext))).thenReturn(new SubResource());
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(UNSUPPORTED_MEDIA_TYPE, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void returnsResponseWithStatus_NOT_ACCEPTABLE_WhenSubResourceLocatorProducesResourceThatDoesNotProduceContentAcceptableByCaller() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
when(request.getAcceptMediaTypeList()).thenReturn(newArrayList(new AcceptMediaType(TEXT_PLAIN_TYPE)));
LocatorResource locatorResource = new LocatorResource();
SubResourceLocatorDescriptor subResourceLocator = mockSubResourceLocator(LocatorResource.class.getMethod("resource"), "c/d");
ObjectFactory resourceFactory = mockResourceFactory(locatorResource, newArrayList(), newArrayList(), newArrayList(subResourceLocator));
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
when(methodInvoker.invokeMethod(same(locatorResource), same(subResourceLocator), same(applicationContext))).thenReturn(new SubResource());
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(NOT_ACCEPTABLE, argumentCaptor.getValue().getStatusInfo());
}
@Test
public void subResourceMethodTakesPrecedenceOverSubResourceLocatorWithTheSamePath() throws Exception {
when(applicationContext.getPathSegments(false)).thenReturn(createPathSegments("a", "b", "c", "d;x=y"));
LocatorResource locatorResource = new LocatorResource();
SubResourceLocatorDescriptor subResourceLocator =
mockSubResourceLocator(LocatorResource.class.getMethod("resource"), "c/d");
SubResourceMethodDescriptor subResourceMethod =
mockSubResourceMethod(LocatorResource.class.getMethod("echo", String.class), "c/d", "POST", newArrayList(WILDCARD_TYPE), newArrayList(WILDCARD_TYPE));
ObjectFactory resourceFactory = mockResourceFactory(locatorResource,
newArrayList(),
newArrayList(subResourceMethod),
newArrayList(subResourceLocator));
matchRequestPath("c/d");
when(resources.getMatchedResource(eq("/a/b/c/d"), anyList())).thenReturn(resourceFactory);
SubResource subResource = new SubResource();
when(methodInvoker.invokeMethod(same(locatorResource), same(subResourceMethod), same(applicationContext))).thenReturn("foo");
when(methodInvoker.invokeMethod(same(locatorResource), same(subResourceLocator), same(applicationContext))).thenReturn(subResource);
when(methodInvoker.invokeMethod(same(subResource), isA(ResourceMethodDescriptor.class), same(applicationContext))).thenReturn("bar");
requestDispatcher.dispatch(request, response);
ArgumentCaptor<Response> argumentCaptor = ArgumentCaptor.forClass(Response.class);
verify(response, atLeastOnce()).setResponse(argumentCaptor.capture());
assertEquals(OK, argumentCaptor.getValue().getStatusInfo());
assertEquals("foo", argumentCaptor.getValue().getEntity());
}
private List<PathSegment> createPathSegments(String... segments) {
return Arrays.stream(segments).map(s -> PathSegmentImpl.fromString(s, false)).collect(toList());
}
private void matchRequestPath() {
pathParameterValues.clear();
pathParameterValues.add(null);
}
private void matchRequestPath(String subResourcePath) {
pathParameterValues.clear();
pathParameterValues.add(subResourcePath);
}
private ObjectFactory mockResourceFactory(Object resource,
List<ResourceMethodDescriptor> resourceMethods,
List<SubResourceMethodDescriptor> subResourceMethods,
List<SubResourceLocatorDescriptor> subResourceLocators) throws Exception {
ResourceDescriptor resourceDescriptor = mock(ResourceDescriptor.class, RETURNS_DEEP_STUBS);
Map<String, List<ResourceMethodDescriptor>> resourceMethodsMap = createResourceMethodsMap(resourceMethods);
when(resourceDescriptor.getResourceMethods()).thenReturn(resourceMethodsMap);
Map<UriPattern, Map<String, List<SubResourceMethodDescriptor>>> subResourceMethodsMap = createSubResourceMethodsMap(subResourceMethods);
when(resourceDescriptor.getSubResourceMethods()).thenReturn(subResourceMethodsMap);
Map<UriPattern, SubResourceLocatorDescriptor> subResourceLocatorsMap = createSubResourceLocatorsMap(subResourceLocators);
when(resourceDescriptor.getSubResourceLocators()).thenReturn(subResourceLocatorsMap);
ObjectFactory objectFactory = mock(ObjectFactory.class, RETURNS_DEEP_STUBS);
when(objectFactory.getObjectModel()).thenReturn(resourceDescriptor);
when(objectFactory.getInstance(isA(ApplicationContext.class))).thenReturn(resource);
return objectFactory;
}
private Map<String, List<ResourceMethodDescriptor>> createResourceMethodsMap(List<ResourceMethodDescriptor> resourceMethods) {
Map<String, List<ResourceMethodDescriptor>> resourceMethodsMap = resourceMethods.stream()
.collect(groupingBy(ResourceMethodDescriptor::getHttpMethod));
ResourceMethodComparator resourceMethodComparator = new ResourceMethodComparator();
resourceMethodsMap.values().forEach(_resourceMethods -> Collections.sort(_resourceMethods, resourceMethodComparator));
return resourceMethodsMap;
}
private Map<UriPattern, Map<String, List<SubResourceMethodDescriptor>>> createSubResourceMethodsMap(List<SubResourceMethodDescriptor> subResourceMethods) {
Map<UriPattern, Map<String, List<SubResourceMethodDescriptor>>> subResourceMethodsMap = new TreeMap<>(new UriPatternComparator());
for (SubResourceMethodDescriptor subResourceMethod : subResourceMethods) {
Map<String, List<SubResourceMethodDescriptor>> resourceMethodsMap = subResourceMethodsMap.get(subResourceMethod.getUriPattern());
if (resourceMethodsMap == null) {
subResourceMethodsMap.put(subResourceMethod.getUriPattern(), resourceMethodsMap = new HashMap<>());
}
List<SubResourceMethodDescriptor> resourceMethods = resourceMethodsMap.get(subResourceMethod.getHttpMethod());
if (resourceMethods == null) {
resourceMethodsMap.put(subResourceMethod.getHttpMethod(), resourceMethods = new ArrayList<>());
}
resourceMethods.add(subResourceMethod);
}
return subResourceMethodsMap;
}
private Map<UriPattern, SubResourceLocatorDescriptor> createSubResourceLocatorsMap(List<SubResourceLocatorDescriptor> subResourceLocators) {
Map<UriPattern, SubResourceLocatorDescriptor> subResourceLocatorsMap = new TreeMap<>(new UriPatternComparator());
for (SubResourceLocatorDescriptor subResourceLocator : subResourceLocators) {
subResourceLocatorsMap.put(subResourceLocator.getUriPattern(), subResourceLocator);
}
return subResourceLocatorsMap;
}
private ResourceMethodDescriptor mockResourceMethod(Method method, String httpMethod, List<MediaType> consumes, List<MediaType> produces) {
ResourceMethodDescriptor resourceMethod = mock(ResourceMethodDescriptor.class);
when(resourceMethod.getMethod()).thenReturn(method);
when(resourceMethod.getHttpMethod()).thenReturn(httpMethod);
when(resourceMethod.consumes()).thenReturn(consumes);
when(resourceMethod.produces()).thenReturn(produces);
return resourceMethod;
}
private SubResourceMethodDescriptor mockSubResourceMethod(Method method, String path, String httpMethod, List<MediaType> consumes, List<MediaType> produces) {
SubResourceMethodDescriptor subResourceMethod = mock(SubResourceMethodDescriptor.class);
UriPattern uriPattern = mockUriPattern(path);
when(subResourceMethod.getUriPattern()).thenReturn(uriPattern);
when(subResourceMethod.getMethod()).thenReturn(method);
when(subResourceMethod.getHttpMethod()).thenReturn(httpMethod);
when(subResourceMethod.consumes()).thenReturn(consumes);
when(subResourceMethod.produces()).thenReturn(produces);
return subResourceMethod;
}
private SubResourceLocatorDescriptor mockSubResourceLocator(Method method, String path) {
SubResourceLocatorDescriptor subResourceLocator = mock(SubResourceLocatorDescriptor.class);
UriPattern uriPattern = mockUriPattern(path);
when(subResourceLocator.getMethod()).thenReturn(method);
when(subResourceLocator.getUriPattern()).thenReturn(uriPattern);
when(subResourceLocator.getMethod()).thenReturn(method);
return subResourceLocator;
}
private UriPattern mockUriPattern(String path) {
UriPattern uriPattern = mock(UriPattern.class);
when(uriPattern.getTemplate()).thenReturn(path);
when(uriPattern.getRegex()).thenReturn(path);
when(uriPattern.match(anyString(), anyList())).thenAnswer(invocation -> {
String toMatch = (String)invocation.getArguments()[0];
boolean matches = Pattern.compile(path).matcher(toMatch).matches();
if (matches) {
matchRequestPath();
}
return matches;
});
return uriPattern;
}
public static class Resource {
public String echo(String phrase) {
return phrase;
}
}
public static class LocatorResource {
public SubResource resource() {
return null;
}
public String echo(String phrase) {
return phrase;
}
}
@Path("c/d")
public static class SubResource {
@Consumes("text/plain")
@Produces("text/xml")
@POST
public String echo(String phrase) {
return phrase;
}
}
}