/*******************************************************************************
* 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.method;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import org.everrest.core.ApplicationContext;
import org.everrest.core.Parameter;
import org.everrest.core.Property;
import org.everrest.core.impl.InternalException;
import org.everrest.core.method.MethodInvokerFilter;
import org.everrest.core.resource.GenericResourceMethod;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import javax.ws.rs.CookieParam;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.MessageBodyReader;
import java.io.IOException;
import java.lang.annotation.Annotation;
import static com.google.common.collect.Lists.newArrayList;
import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
import static javax.ws.rs.core.Response.Status.UNSUPPORTED_MEDIA_TYPE;
import static org.junit.Assert.assertEquals;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
@RunWith(DataProviderRunner.class)
public class DefaultMethodInvokerTest {
private static final String ARGUMENT_VALUE = "to be or not to be";
private static final Class ARGUMENT_CLASS = String.class;
private static final String RESOURCE_PATH = "/a/b";
public static class EchoResource {
private Exception thrownException;
public EchoResource(Exception thrownException) {
this.thrownException = thrownException;
}
public EchoResource() {
this(null);
}
@SuppressWarnings("unused")
public String echo(String phrase) throws Exception {
if (thrownException != null) {
throw thrownException;
}
return phrase;
}
}
@Rule
public ExpectedException thrown = ExpectedException.none();
private ParameterResolverFactory parameterResolverFactory;
private GenericResourceMethod resourceMethod;
private Parameter entityParameter;
private Annotation parameterAnnotation;
private Parameter annotatedParameter;
private ParameterResolver parameterResolver;
private ApplicationContext applicationContext;
private MessageBodyReader<String> messageBodyReader;
private DefaultMethodInvoker methodInvoker;
@Before
public void setUp() throws Exception {
mockEntityParameter();
mockParameterAnnotation();
mockAnnotatedParameter();
mockResourceMethod();
mockApplicationContext();
mockParameterResolverFactory();
mockEntityReader();
methodInvoker = new DefaultMethodInvoker(parameterResolverFactory);
}
@Test
public void notifiesMethodInvokerFiltersBeforeMethodInvocation() throws Exception {
MethodInvokerFilter methodInvokerFilter = mock(MethodInvokerFilter.class);
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
when(applicationContext.getProviders().getMethodInvokerFilters(RESOURCE_PATH)).thenReturn(newArrayList(methodInvokerFilter));
methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
verify(methodInvokerFilter).accept(eq(resourceMethod), aryEq(new Object[]{ARGUMENT_VALUE}));
}
@Test
public void invokesMethodWithEntityParameter() throws Exception {
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
Object invocationResult = methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
assertEquals(ARGUMENT_VALUE, invocationResult);
}
@Test
public void invokesMethodWithNullEntityParameterWhenEntityStreamDoesNotPresent() throws Exception {
when(applicationContext.getContainerRequest().getEntityStream()).thenReturn(null);
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
Object invocationResult = methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
assertEquals(null, invocationResult);
}
@Test
public void throwsWebApplicationExceptionWhenMessageBodyReaderForEntityParameterIsNotAvailable() throws Exception {
when(applicationContext.getProviders()
.getMessageBodyReader((Class)entityParameter.getParameterClass(),
entityParameter.getGenericType(),
entityParameter.getAnnotations(),
applicationContext.getContainerRequest().getMediaType()))
.thenReturn(null);
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
thrown.expect(webApplicationExceptionWithStatusMatcher(UNSUPPORTED_MEDIA_TYPE));
methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
}
@Test
public void invokesMethodWithNullEntityParameterWhenMessageBodyReaderIsNotAvailableAndBothContentTypeAndContentLengthAreNotSet() throws Exception {
when(applicationContext.getContainerRequest().getMediaType()).thenReturn(null);
when(applicationContext.getProviders()
.getMessageBodyReader((Class)entityParameter.getParameterClass(),
entityParameter.getGenericType(),
entityParameter.getAnnotations(),
applicationContext.getContainerRequest().getMediaType()))
.thenReturn(null);
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
MultivaluedMap<String, String> headers = new MultivaluedHashMap<>();
headers.putSingle(CONTENT_LENGTH, "0");
when(applicationContext.getContainerRequest().getRequestHeaders()).thenReturn(headers);
Object invocationResult = methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
assertEquals(null, invocationResult);
}
@Test
public void rethrowWebApplicationExceptionThatOccursWhenReadEntityParameter() throws Exception {
WebApplicationException thrownByMessageBodyReader = new WebApplicationException();
when(messageBodyReader.readFrom((Class)entityParameter.getParameterClass(),
entityParameter.getGenericType(),
entityParameter.getAnnotations(),
applicationContext.getContainerRequest().getMediaType(),
applicationContext.getContainerRequest().getRequestHeaders(),
applicationContext.getContainerRequest().getEntityStream())).thenThrow(thrownByMessageBodyReader);
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
thrown.expect(exceptionSameInstanceMatcher(thrownByMessageBodyReader));
methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
}
@Test
public void rethrowInternalExceptionThatOccursWhenReadEntityParameter() throws Exception {
InternalException thrownByMessageBodyReader = new InternalException(new Exception());
when(messageBodyReader.readFrom((Class)entityParameter.getParameterClass(),
entityParameter.getGenericType(),
entityParameter.getAnnotations(),
applicationContext.getContainerRequest().getMediaType(),
applicationContext.getContainerRequest().getRequestHeaders(),
applicationContext.getContainerRequest().getEntityStream())).thenThrow(thrownByMessageBodyReader);
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
thrown.expect(exceptionSameInstanceMatcher(thrownByMessageBodyReader));
methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
}
@Test
public void wrapsExceptionThatOccursWhenReadEntityParameterWithInternalException() throws Exception {
Exception thrownByMessageBodyReader = new IOException();
when(messageBodyReader.readFrom((Class)entityParameter.getParameterClass(),
entityParameter.getGenericType(),
entityParameter.getAnnotations(),
applicationContext.getContainerRequest().getMediaType(),
applicationContext.getContainerRequest().getRequestHeaders(),
applicationContext.getContainerRequest().getEntityStream())).thenThrow(thrownByMessageBodyReader);
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
thrown.expect(InternalException.class);
thrown.expectCause(exceptionSameInstanceMatcher(thrownByMessageBodyReader));
methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
}
@Test
public void invokesMethodWithAnnotatedParameter() throws Exception {
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(annotatedParameter));
Object invocationResult = methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
assertEquals(ARGUMENT_VALUE, invocationResult);
}
@DataProvider
public static Object[][] annotationTypesWhenNeedToThrowWebApplicationExceptionWithStatusNotFound() {
return new Object[][]{
{MatrixParam.class},
{QueryParam.class},
{PathParam.class}
};
}
@UseDataProvider("annotationTypesWhenNeedToThrowWebApplicationExceptionWithStatusNotFound")
@Test
public <A extends Annotation> void throwsWebApplicationExceptionWithStatusNotFoundWhenFailResolveParameterAnnotatedWith(Class<A> annotationType) throws Exception {
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(annotatedParameter));
when(parameterAnnotation.annotationType()).thenReturn((Class)annotationType);
Exception thrownByParameterResolver = new Exception();
when(parameterResolver.resolve(annotatedParameter, applicationContext)).thenThrow(thrownByParameterResolver);
thrown.expect(webApplicationExceptionWithStatusMatcher(NOT_FOUND));
methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
}
@DataProvider
public static Object[][] annotationTypesWhenNeedToThrowWebApplicationExceptionWithStatusBadRequest() {
return new Object[][]{
{CookieParam.class},
{Context.class},
{FormParam.class},
{HeaderParam.class},
{Property.class}
};
}
@UseDataProvider("annotationTypesWhenNeedToThrowWebApplicationExceptionWithStatusBadRequest")
@Test
public <A extends Annotation> void throwsWebApplicationExceptionWithStatusBadRequestWhenFailResolveParameterAnnotatedWith(Class<A> annotationType) throws Exception {
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(annotatedParameter));
when(parameterAnnotation.annotationType()).thenReturn((Class)annotationType);
Exception thrownByParameterResolver = new Exception();
when(parameterResolver.resolve(annotatedParameter, applicationContext)).thenThrow(thrownByParameterResolver);
thrown.expect(webApplicationExceptionWithStatusMatcher(BAD_REQUEST));
methodInvoker.invokeMethod(new EchoResource(), resourceMethod, applicationContext);
}
@Test
public void rethrowWebApplicationExceptionThatThrownByInvocatedMethod() throws Exception {
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
WebApplicationException thrownByMethod = new WebApplicationException();
thrown.expect(exceptionSameInstanceMatcher(thrownByMethod));
methodInvoker.invokeMethod(new EchoResource(thrownByMethod), resourceMethod, applicationContext);
}
@Test
public void rethrowInternalExceptionThatThrownByInvocatedMethod() throws Exception {
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
InternalException thrownByMethod = new InternalException(new Exception());
thrown.expect(exceptionSameInstanceMatcher(thrownByMethod));
methodInvoker.invokeMethod(new EchoResource(thrownByMethod), resourceMethod, applicationContext);
}
@Test
public void wrapsExceptionThatThrownByInvocatedMethodWithInternalException() throws Exception {
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList(entityParameter));
Exception thrownByMethod = new Exception();
thrown.expect(InternalException.class);
thrown.expectCause(exceptionSameInstanceMatcher(thrownByMethod));
methodInvoker.invokeMethod(new EchoResource(thrownByMethod), resourceMethod, applicationContext);
}
private BaseMatcher<Throwable> webApplicationExceptionWithStatusMatcher(Status status) {
return new BaseMatcher<Throwable>() {
@Override
public boolean matches(Object item) {
return item instanceof WebApplicationException
&& status.equals(((WebApplicationException)item).getResponse().getStatusInfo());
}
@Override
public void describeTo(Description description) {
description.appendText(String.format("WebApplicationException with status %d \"%s\"", status.getStatusCode(), status.getReasonPhrase()));
}
};
}
private BaseMatcher<Throwable> exceptionSameInstanceMatcher(Exception expectedException) {
return new BaseMatcher<Throwable>() {
@Override
public boolean matches(Object item) {
return item == expectedException;
}
@Override
public void describeTo(Description description) {
description.appendText(String.format("Expected exception: %s", expectedException));
}
};
}
private void mockEntityParameter() {
entityParameter = mock(Parameter.class);
when(entityParameter.getParameterClass()).thenReturn(ARGUMENT_CLASS);
}
private void mockAnnotatedParameter() {
annotatedParameter = mock(Parameter.class);
when(annotatedParameter.getParameterClass()).thenReturn(ARGUMENT_CLASS);
when(annotatedParameter.getAnnotation()).thenReturn(parameterAnnotation);
}
private void mockParameterAnnotation() {
PathParam parameterAnnotation = mock(PathParam.class);
when(parameterAnnotation.annotationType()).thenReturn((Class)PathParam.class);
this.parameterAnnotation = parameterAnnotation;
}
private void mockParameterResolverFactory() throws Exception {
parameterResolver = mock(ParameterResolver.class);
when(parameterResolver.resolve(annotatedParameter, applicationContext)).thenReturn(ARGUMENT_VALUE);
parameterResolverFactory = mock(ParameterResolverFactory.class);
when(parameterResolverFactory.createParameterResolver(parameterAnnotation)).thenReturn(parameterResolver);
}
private void mockResourceMethod() throws Exception {
resourceMethod = mock(GenericResourceMethod.class);
when(resourceMethod.getMethod()).thenReturn(EchoResource.class.getMethod("echo", String.class));
when(resourceMethod.getMethodParameters()).thenReturn(newArrayList());
}
private void mockApplicationContext() {
applicationContext = mock(ApplicationContext.class, RETURNS_DEEP_STUBS);
when(applicationContext.getPath()).thenReturn(RESOURCE_PATH);
when(applicationContext.getProviders().getMethodInvokerFilters(anyString())).thenReturn(newArrayList());
when(applicationContext.getQueryParameters()).thenReturn(new MultivaluedHashMap<>());
when(applicationContext.getContainerRequest().getRequestHeaders()).thenReturn(new MultivaluedHashMap<>());
when(applicationContext.getContainerRequest().getMediaType()).thenReturn(TEXT_PLAIN_TYPE);
ApplicationContext.setCurrent(applicationContext);
}
private void mockEntityReader() throws java.io.IOException {
messageBodyReader = mock(MessageBodyReader.class);
when(messageBodyReader.readFrom((Class)entityParameter.getParameterClass(),
entityParameter.getGenericType(),
entityParameter.getAnnotations(),
applicationContext.getContainerRequest().getMediaType(),
applicationContext.getContainerRequest().getRequestHeaders(),
applicationContext.getContainerRequest().getEntityStream())).thenReturn(ARGUMENT_VALUE);
when(applicationContext.getProviders()
.getMessageBodyReader((Class)entityParameter.getParameterClass(),
entityParameter.getGenericType(),
entityParameter.getAnnotations(),
applicationContext.getContainerRequest().getMediaType()))
.thenReturn(messageBodyReader);
}
}