/******************************************************************************* * 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 ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; 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.Filter; import org.everrest.core.GenericContainerRequest; import org.everrest.core.GenericContainerResponse; import org.everrest.core.ObjectFactory; import org.everrest.core.RequestFilter; import org.everrest.core.ResponseFilter; import org.everrest.core.impl.provider.ByteEntityProvider; import org.everrest.core.impl.provider.DefaultExceptionMapper; import org.everrest.core.impl.provider.DuplicateProviderException; import org.everrest.core.impl.provider.StringEntityProvider; import org.everrest.core.method.MethodInvokerFilter; import org.everrest.core.provider.ProviderDescriptor; import org.everrest.core.resource.GenericResourceMethod; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.slf4j.LoggerFactory; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.security.Permission; import java.util.List; import static com.google.common.collect.Lists.newArrayList; import static java.util.stream.Collectors.toList; import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; import static javax.ws.rs.core.MediaType.WILDCARD_TYPE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(DataProviderRunner.class) public class ProviderBinderTest { @Provider @Produces("text/plain") public static class ContextResolverText implements ContextResolver<String> { public String getContext(Class<?> type) { return null; } } @Provider public static class ContextResolverWildcard implements ContextResolver<String> { public String getContext(Class<?> type) { return null; } } @Provider @Produces("text/xml") public static class ContextResolverXml implements ContextResolver<String> { public String getContext(Class<?> type) { return null; } } @Provider @Produces("text/*") public static class ContextResolverAnyText implements ContextResolver<String> { public String getContext(Class<?> type) { return null; } } @Provider public static class RuntimeExceptionMapper implements ExceptionMapper<RuntimeException> { @Override public Response toResponse(RuntimeException exception) { return null; } } @Filter public static class AllMatchesRequestFilter implements RequestFilter { @Override public void doFilter(GenericContainerRequest request) { } } @Filter @Path("/a/b") public static class PathMatchesRequestFilter implements RequestFilter { @Override public void doFilter(GenericContainerRequest request) { } } @Filter public static class AllMatchesResponseFilter implements ResponseFilter { @Override public void doFilter(GenericContainerResponse response) { } } @Filter @Path("/a/b") public static class PathMatchesResponseFilter implements ResponseFilter { @Override public void doFilter(GenericContainerResponse response) { } } @Filter public static class AllMatchesMethodInvokerFilter implements MethodInvokerFilter { @Override public void accept(GenericResourceMethod genericResourceMethod, Object[] params) { } } @Filter @Path("/a/b") public static class PathMatchesMethodInvokerFilter implements MethodInvokerFilter { @Override public void accept(GenericResourceMethod genericResourceMethod, Object[] params) { } } @Rule public ExpectedException thrown = ExpectedException.none(); private ProviderBinder providers; private static ContextResolverText contextResolverText = new ContextResolverText(); private static ContextResolverWildcard contextResolverWildcard = new ContextResolverWildcard(); private static ContextResolverXml contextResolverXml = new ContextResolverXml(); private static ContextResolverAnyText contextResolverAnyText = new ContextResolverAnyText(); private static RuntimeExceptionMapper runtimeExceptionMapper = new RuntimeExceptionMapper(); private static DefaultExceptionMapper defaultExceptionMapper = new DefaultExceptionMapper(); private static MessageBodyReader<String> stringMessageBodyReader = new StringEntityProvider(); private static MessageBodyReader<byte[]> byteMessageBodyReader = new ByteEntityProvider(); private static MessageBodyWriter<String> stringMessageBodyWriter = new StringEntityProvider(); private static MessageBodyWriter<byte[]> byteMessageBodyWriter = new ByteEntityProvider(); private static RequestFilter allMatchesRequestFilter = new AllMatchesRequestFilter(); private static RequestFilter pathMatchesRequestFilter = new PathMatchesRequestFilter(); private static ResponseFilter allMatchesResponseFilter = new AllMatchesResponseFilter(); private static ResponseFilter pathMatchesResponseFilter = new PathMatchesResponseFilter(); private static MethodInvokerFilter allMatchesMethodInvokerFilter = new AllMatchesMethodInvokerFilter(); private static MethodInvokerFilter pathMatchesMethodInvokerFilter = new PathMatchesMethodInvokerFilter(); private static final boolean SINGLETON = true; private static final boolean PER_REQUEST = false; private Appender<ILoggingEvent> mockLogbackAppender; private ApplicationContext context; @Before public void setUp() throws Exception { context = mock(ApplicationContext.class); ApplicationContext.setCurrent(context); providers = new ProviderBinder(); setUpLogbackAppender(); } private void setUpLogbackAppender() { ch.qos.logback.classic.Logger providerBinderLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ProviderBinder.class); mockLogbackAppender = mockLogbackAppender(); providerBinderLogger.addAppender(mockLogbackAppender); } private Appender mockLogbackAppender() { Appender mockAppender = mock(Appender.class); when(mockAppender.getName()).thenReturn("MockAppender"); return mockAppender; } @After public void tearDown() { ch.qos.logback.classic.Logger providerBinderLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ProviderBinder.class); providerBinderLogger.detachAppender(mockLogbackAppender); ProviderBinder.setInstance(null); } @DataProvider public static Object[][] contextResolverByClassAndMediaTypeData() { return new Object[][] { {PER_REQUEST, String.class, new MediaType("text", "plain"), ContextResolverText.class}, {PER_REQUEST, String.class, new MediaType("text", "xml"), ContextResolverXml.class}, {PER_REQUEST, String.class, new MediaType("text", "xxx"), ContextResolverAnyText.class}, {PER_REQUEST, String.class, new MediaType("xxx", "xxx"), ContextResolverWildcard.class}, {PER_REQUEST, Object.class, new MediaType("xxx", "xxx"), null}, {SINGLETON, String.class, new MediaType("text", "plain"), contextResolverText}, {SINGLETON, String.class, new MediaType("text", "xml"), contextResolverXml}, {SINGLETON, String.class, new MediaType("text", "xxx"), contextResolverAnyText}, {SINGLETON, String.class, new MediaType("xxx", "xxx"), contextResolverWildcard}, {SINGLETON, Object.class, new MediaType("xxx", "xxx"), null} }; } @Test @UseDataProvider("contextResolverByClassAndMediaTypeData") public void retrievesContextResolverByClassAndMediaType(boolean singletonOrPerRequest, Class<?> aClass, MediaType mediaType, Object expectedContextResolverClassOrInstance) throws Exception { if (singletonOrPerRequest == SINGLETON) { registerSingletonContextResolvers(); } else { registerPerRequestContextResolvers(); } ContextResolver<?> contextResolver = providers.getContextResolver(aClass, mediaType); if (singletonOrPerRequest == SINGLETON) { assertSame(expectedContextResolverClassOrInstance, contextResolver); } else { if (expectedContextResolverClassOrInstance == null) { assertNull(contextResolver); } else { assertNotNull(contextResolver); assertEquals(expectedContextResolverClassOrInstance, contextResolver.getClass()); } } } private void registerPerRequestContextResolvers() { providers.addContextResolver(ContextResolverText.class); providers.addContextResolver(ContextResolverWildcard.class); providers.addContextResolver(ContextResolverXml.class); providers.addContextResolver(ContextResolverAnyText.class); } private void registerSingletonContextResolvers() { providers.addContextResolver(contextResolverText); providers.addContextResolver(contextResolverWildcard); providers.addContextResolver(contextResolverXml); providers.addContextResolver(contextResolverAnyText); } @DataProvider public static Object[][] exceptionMapperByExceptionType() { return new Object[][] { {PER_REQUEST, Exception.class, DefaultExceptionMapper.class}, {PER_REQUEST, RuntimeException.class, RuntimeExceptionMapper.class}, {PER_REQUEST, IOException.class, DefaultExceptionMapper.class}, {SINGLETON, Exception.class, defaultExceptionMapper}, {SINGLETON, RuntimeException.class, runtimeExceptionMapper}, {SINGLETON, IOException.class, defaultExceptionMapper} }; } @Test @UseDataProvider("exceptionMapperByExceptionType") public <T extends Throwable> void retrievesExceptionMapperByExceptionType(boolean singletonOrPerRequest, Class<T> errorClass, Object expectedExceptionMapperClassOrInstance) throws Exception { if (singletonOrPerRequest == SINGLETON) { registerSingletonExceptionMappers(); } else { registerPerRequestExceptionMappers(); } ExceptionMapper<T> exceptionMapper = providers.getExceptionMapper(errorClass); if (singletonOrPerRequest == SINGLETON) { assertSame(expectedExceptionMapperClassOrInstance, exceptionMapper); } else { assertNotNull(exceptionMapper); assertEquals(expectedExceptionMapperClassOrInstance, exceptionMapper.getClass()); } } private void registerPerRequestExceptionMappers() { providers.addExceptionMapper(DefaultExceptionMapper.class); providers.addExceptionMapper(RuntimeExceptionMapper.class); } private void registerSingletonExceptionMappers() { providers.addExceptionMapper(defaultExceptionMapper); providers.addExceptionMapper(runtimeExceptionMapper); } @DataProvider public static Object[][] messageBodyReaderByTypeAndMediaType() { return new Object[][] { {SINGLETON, String.class, null, TEXT_PLAIN_TYPE, stringMessageBodyReader}, {SINGLETON, byte[].class, null, TEXT_PLAIN_TYPE, byteMessageBodyReader}, {SINGLETON, Object.class, null, TEXT_PLAIN_TYPE, null}, {PER_REQUEST, String.class, null, TEXT_PLAIN_TYPE, StringEntityProvider.class}, {PER_REQUEST, byte[].class, null, TEXT_PLAIN_TYPE, ByteEntityProvider.class}, {PER_REQUEST, Object.class, null, TEXT_PLAIN_TYPE, null} }; } @Test @UseDataProvider("messageBodyReaderByTypeAndMediaType") public void retrievesMessageBodyReaderByTypeAndMediaType(boolean singletonOrPerRequest, Class<?> readObjectType, Type readObjectGenericType, MediaType mediaType, Object expectedMessageBodyReaderClassOrInstance) throws Exception { if (singletonOrPerRequest == SINGLETON) { registerSingletonMessageBodyReaders(); } else { registerPerRequestMessageBodyReaders(); } MessageBodyReader messageBodyReader = providers.getMessageBodyReader(readObjectType, readObjectGenericType, null, mediaType); if (singletonOrPerRequest == SINGLETON) { assertSame(expectedMessageBodyReaderClassOrInstance, messageBodyReader); } else { if (expectedMessageBodyReaderClassOrInstance == null) { assertNull(messageBodyReader); } else { assertNotNull(messageBodyReader); assertEquals(expectedMessageBodyReaderClassOrInstance, messageBodyReader.getClass()); } } } private void registerPerRequestMessageBodyReaders() { providers.addMessageBodyReader(StringEntityProvider.class); providers.addMessageBodyReader(ByteEntityProvider.class); } private void registerSingletonMessageBodyReaders() { providers.addMessageBodyReader(stringMessageBodyReader); providers.addMessageBodyReader(byteMessageBodyReader); } @DataProvider public static Object[][] messageBodyWriterByTypeAndMediaType() { return new Object[][] { {SINGLETON, String.class, null, TEXT_PLAIN_TYPE, stringMessageBodyWriter}, {SINGLETON, byte[].class, null, TEXT_PLAIN_TYPE, byteMessageBodyWriter}, {SINGLETON, Object.class, null, TEXT_PLAIN_TYPE, null}, {PER_REQUEST, String.class, null, TEXT_PLAIN_TYPE, StringEntityProvider.class}, {PER_REQUEST, byte[].class, null, TEXT_PLAIN_TYPE, ByteEntityProvider.class}, {PER_REQUEST, Object.class, null, TEXT_PLAIN_TYPE, null} }; } @Test @UseDataProvider("messageBodyWriterByTypeAndMediaType") public void retrievesMessageBodyWriterByTypeAndMediaType(boolean singletonOrPerRequest, Class<?> writeObjectType, Type writeObjectGenericType, MediaType mediaType, Object expectedMessageBodyWriterClassOrInstance) throws Exception { if (singletonOrPerRequest == SINGLETON) { registerSingletonMessageBodyWriters(); } else { registerPerRequestMessageBodyWriters(); } MessageBodyWriter messageBodyWriter = providers.getMessageBodyWriter(writeObjectType, writeObjectGenericType, null, mediaType); if (singletonOrPerRequest == SINGLETON) { assertSame(expectedMessageBodyWriterClassOrInstance, messageBodyWriter); } else { if (expectedMessageBodyWriterClassOrInstance == null) { assertNull(messageBodyWriter); } else { assertNotNull(messageBodyWriter); assertEquals(expectedMessageBodyWriterClassOrInstance, messageBodyWriter.getClass()); } } } private void registerPerRequestMessageBodyWriters() { providers.addMessageBodyWriter(StringEntityProvider.class); providers.addMessageBodyWriter(ByteEntityProvider.class); } private void registerSingletonMessageBodyWriters() { providers.addMessageBodyWriter(stringMessageBodyWriter); providers.addMessageBodyWriter(byteMessageBodyWriter); } @Test public void logsErrorWhenTryRegisterPerRequestDuplicateContextResolver() throws Exception { providers.addContextResolver(ContextResolverAnyText.class); providers.addContextResolver(ContextResolverAnyText.class); assertThatErrorLoggingEventWithThrowableAppended(DuplicateProviderException.class); } @Test public void logsErrorWhenTryRegisterSingletonDuplicateContextResolver() throws Exception { providers.addContextResolver(contextResolverAnyText); providers.addContextResolver(contextResolverAnyText); assertThatErrorLoggingEventWithThrowableAppended(DuplicateProviderException.class); } @Test public void logsErrorWhenTryRegisterDuplicateContextResolver() throws Exception { providers.addContextResolver(ContextResolverAnyText.class); providers.addContextResolver(contextResolverAnyText); assertThatErrorLoggingEventWithThrowableAppended(DuplicateProviderException.class); } @Test public void logsErrorWhenTryRegisterPerRequestDuplicateExceptionMapper() throws Exception { providers.addExceptionMapper(RuntimeExceptionMapper.class); providers.addExceptionMapper(RuntimeExceptionMapper.class); assertThatErrorLoggingEventWithThrowableAppended(DuplicateProviderException.class); } @Test public void logsErrorWhenTryRegisterSingletonDuplicateExceptionMapper() throws Exception { providers.addExceptionMapper(runtimeExceptionMapper); providers.addExceptionMapper(runtimeExceptionMapper); assertThatErrorLoggingEventWithThrowableAppended(DuplicateProviderException.class); } @Test public void logsErrorWhenTryRegisterDuplicateExceptionMapper() throws Exception { providers.addExceptionMapper(RuntimeExceptionMapper.class); providers.addExceptionMapper(runtimeExceptionMapper); assertThatErrorLoggingEventWithThrowableAppended(DuplicateProviderException.class); } private void assertThatErrorLoggingEventWithThrowableAppended(Class<? extends Throwable> throwable) { for (ILoggingEvent loggingEvent : retrieveLoggingEvents()) { if (loggingEvent.getLevel() == Level.ERROR && loggingEvent.getThrowableProxy() != null && loggingEvent.getThrowableProxy().getClassName().equals(throwable.getName())) { return; } } fail(String.format("Error event with error type %s was not logged", throwable.getName())); } private List<ILoggingEvent> retrieveLoggingEvents() { ArgumentCaptor<ILoggingEvent> logEventCaptor = ArgumentCaptor.forClass(ILoggingEvent.class); verify(mockLogbackAppender, atLeastOnce()).doAppend(logEventCaptor.capture()); return logEventCaptor.getAllValues(); } @DataProvider public static Object[][] requestFiltersByPath() { return new Object[][] { {SINGLETON, "/a/b", newArrayList(allMatchesRequestFilter, pathMatchesRequestFilter)}, {SINGLETON, "/a", newArrayList(allMatchesRequestFilter)}, {SINGLETON, "/", newArrayList(allMatchesRequestFilter)}, {PER_REQUEST, "/a/b", newArrayList(AllMatchesRequestFilter.class, PathMatchesRequestFilter.class)}, {PER_REQUEST, "/a", newArrayList(AllMatchesRequestFilter.class)}, {PER_REQUEST, "/", newArrayList(AllMatchesRequestFilter.class)} }; } @Test @UseDataProvider("requestFiltersByPath") public void retrievesRequestFiltersByPath(boolean singletonOrPerRequest, String path, List<Object> expectedRequestFilterClassesOrInstances) throws Exception { if (singletonOrPerRequest == SINGLETON) { registerSingletonRequestFilters(); } else { registerPerRequestRequestFilters(); } List<RequestFilter> requestFilters = providers.getRequestFilters(path); if (singletonOrPerRequest == SINGLETON) { expectedRequestFilterClassesOrInstances.removeAll(requestFilters); assertTrue(String.format("Request filters %s expected but not found", expectedRequestFilterClassesOrInstances), expectedRequestFilterClassesOrInstances.isEmpty()); } else { List<Class> methodInvokerFiltersClasses = requestFilters.stream().map(Object::getClass).collect(toList()); expectedRequestFilterClassesOrInstances.removeAll(methodInvokerFiltersClasses); assertTrue(String.format("Request filters %s expected but not found", expectedRequestFilterClassesOrInstances), expectedRequestFilterClassesOrInstances.isEmpty()); } } private void registerPerRequestRequestFilters() { providers.addRequestFilter(PathMatchesRequestFilter.class); providers.addRequestFilter(AllMatchesRequestFilter.class); } private void registerSingletonRequestFilters() { providers.addRequestFilter(pathMatchesRequestFilter); providers.addRequestFilter(allMatchesRequestFilter); } @DataProvider public static Object[][] responseFiltersByPath() { return new Object[][] { {SINGLETON, "/a/b", newArrayList(allMatchesResponseFilter, pathMatchesResponseFilter)}, {SINGLETON, "/a", newArrayList(allMatchesResponseFilter)}, {SINGLETON, "/", newArrayList(allMatchesResponseFilter)}, {PER_REQUEST, "/a/b", newArrayList(AllMatchesResponseFilter.class, PathMatchesResponseFilter.class)}, {PER_REQUEST, "/a", newArrayList(AllMatchesResponseFilter.class)}, {PER_REQUEST, "/", newArrayList(AllMatchesResponseFilter.class)}, }; } @Test @UseDataProvider("responseFiltersByPath") public void retrievesResponseFiltersByPath(boolean singletonOrPerRequest, String path, List<Object> expectedResponseFilterClassesOrInstances) throws Exception { if (singletonOrPerRequest == SINGLETON) { registerSingletonResponseFilters(); } else { registerPerRequestResponseFilters(); } List<ResponseFilter> responseFilters = providers.getResponseFilters(path); if (singletonOrPerRequest == SINGLETON) { expectedResponseFilterClassesOrInstances.removeAll(responseFilters); assertTrue(String.format("Response filters %s expected but not found", expectedResponseFilterClassesOrInstances), expectedResponseFilterClassesOrInstances.isEmpty()); } else { List<Class> methodInvokerFiltersClasses = responseFilters.stream().map(Object::getClass).collect(toList()); expectedResponseFilterClassesOrInstances.removeAll(methodInvokerFiltersClasses); assertTrue(String.format("Response filters %s expected but not found", expectedResponseFilterClassesOrInstances), expectedResponseFilterClassesOrInstances.isEmpty()); } } private void registerPerRequestResponseFilters() { providers.addResponseFilter(PathMatchesResponseFilter.class); providers.addResponseFilter(AllMatchesResponseFilter.class); } private void registerSingletonResponseFilters() { providers.addResponseFilter(pathMatchesResponseFilter); providers.addResponseFilter(allMatchesResponseFilter); } @DataProvider public static Object[][] methodInvokerFiltersByPath() { return new Object[][] { {SINGLETON, "/a/b", newArrayList(allMatchesMethodInvokerFilter, pathMatchesMethodInvokerFilter)}, {SINGLETON, "/a", newArrayList(allMatchesMethodInvokerFilter)}, {SINGLETON, "/", newArrayList(allMatchesMethodInvokerFilter)}, {PER_REQUEST, "/a/b", newArrayList(AllMatchesMethodInvokerFilter.class, PathMatchesMethodInvokerFilter.class)}, {PER_REQUEST, "/a", newArrayList(AllMatchesMethodInvokerFilter.class)}, {PER_REQUEST, "/", newArrayList(AllMatchesMethodInvokerFilter.class)}, }; } @Test @UseDataProvider("methodInvokerFiltersByPath") public void retrievesMethodInvokerFiltersByPath(boolean singletonOrPerRequest, String path, List<Object> expectedMethodInvokerFilterClassesOrInstances) throws Exception { if (singletonOrPerRequest == SINGLETON) { registerSingletonMethodInvokerFilters(); } else { registerPerRequestMethodInvokerFilters(); } List<MethodInvokerFilter> methodInvokerFilters = providers.getMethodInvokerFilters(path); if (singletonOrPerRequest == SINGLETON) { expectedMethodInvokerFilterClassesOrInstances.removeAll(methodInvokerFilters); assertTrue(String.format("MethodInvoker filters %s expected but not found", expectedMethodInvokerFilterClassesOrInstances), expectedMethodInvokerFilterClassesOrInstances.isEmpty()); } else { List<Class> methodInvokerFiltersClasses = methodInvokerFilters.stream().map(Object::getClass).collect(toList()); expectedMethodInvokerFilterClassesOrInstances.removeAll(methodInvokerFiltersClasses); assertTrue(String.format("MethodInvoker filters %s expected but not found", expectedMethodInvokerFilterClassesOrInstances), expectedMethodInvokerFilterClassesOrInstances.isEmpty()); } } private void registerPerRequestMethodInvokerFilters() { providers.addMethodInvokerFilter(PathMatchesMethodInvokerFilter.class); providers.addMethodInvokerFilter(AllMatchesMethodInvokerFilter.class); } private void registerSingletonMethodInvokerFilters() { providers.addMethodInvokerFilter(pathMatchesMethodInvokerFilter); providers.addMethodInvokerFilter(allMatchesMethodInvokerFilter); } @SuppressWarnings("unchecked") @Test public void retrievesAcceptableWriterMediaTypes() throws Exception { ObjectFactory<ProviderDescriptor> writerFactory = mock(ObjectFactory.class); ProviderDescriptor providerDescriptor = mock(ProviderDescriptor.class); when(providerDescriptor.produces()).thenReturn(newArrayList(new MediaType("text", "*"), new MediaType("text", "plain"))); when(providerDescriptor.getObjectClass()).thenReturn((Class)StringEntityProvider.class); when(writerFactory.getObjectModel()).thenReturn(providerDescriptor); MessageBodyWriter<String> writer = mock(MessageBodyWriter.class); when(writer.isWriteable(eq(String.class), (Type)isNull(), any(Annotation[].class), eq(WILDCARD_TYPE))).thenReturn(true); when(writerFactory.getInstance(context)).thenReturn(writer); providers.addMessageBodyWriter(writerFactory); assertEquals(newArrayList(new MediaType("text", "plain"), new MediaType("text", "*")), providers.getAcceptableWriterMediaTypes(String.class, null, null)); } @Test public void setsInstanceOfProviderBinder() throws Exception { ProviderBinder providerBinder = mock(ProviderBinder.class); ProviderBinder.setInstance(providerBinder); assertSame(providerBinder, ProviderBinder.getInstance()); } @Test public void failsSetInstanceOfProviderBinderWhenDoNotHaveProvidersManagePermission() throws Exception { try { setupSecurityManager(); ProviderBinder providerBinder = mock(ProviderBinder.class); thrown.expect(SecurityException.class); ProviderBinder.setInstance(providerBinder); } finally { restoreSecurityManager(); } } @Test public void findsMessageBodyReaderThatSupportsTypeNearestToDeserializedTypeInClassesHierarchy() { EntityReader entityReader = new EntityReader(); ExtendedEntityReader extendedEntityReader = new ExtendedEntityReader(); ExtendedExtendedEntityReader extendedExtendedEntityReader = new ExtendedExtendedEntityReader(); providers.addMessageBodyReader(entityReader); providers.addMessageBodyReader(extendedEntityReader); assertSame(entityReader, providers.getMessageBodyReader(Entity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedEntityReader, providers.getMessageBodyReader(ExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedEntityReader, providers.getMessageBodyReader(ExtendedExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); providers.addMessageBodyReader(extendedExtendedEntityReader); assertSame(entityReader, providers.getMessageBodyReader(Entity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedEntityReader, providers.getMessageBodyReader(ExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedExtendedEntityReader, providers.getMessageBodyReader(ExtendedExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); } @Test public void findsMessageBodyWriterThatSupportsTypeNearestToSerializedTypeInClassesHierarchy() { EntityWriter entityWriter = new EntityWriter(); ExtendedEntityWriter extendedEntityWriter = new ExtendedEntityWriter(); ExtendedExtendedEntityWriter extendedExtendedEntityWriter = new ExtendedExtendedEntityWriter(); providers.addMessageBodyWriter(entityWriter); providers.addMessageBodyWriter(extendedEntityWriter); assertSame(entityWriter, providers.getMessageBodyWriter(Entity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedEntityWriter, providers.getMessageBodyWriter(ExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedEntityWriter, providers.getMessageBodyWriter(ExtendedExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); providers.addMessageBodyWriter(extendedExtendedEntityWriter); assertSame(entityWriter, providers.getMessageBodyWriter(Entity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedEntityWriter, providers.getMessageBodyWriter(ExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); assertSame(extendedExtendedEntityWriter, providers.getMessageBodyWriter(ExtendedExtendedEntity.class, null, null, TEXT_PLAIN_TYPE)); } static class Entity { } static class ExtendedEntity extends Entity { } static class ExtendedExtendedEntity extends ExtendedEntity { } @Provider static class EntityReader implements MessageBodyReader<Entity> { @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Entity.class.isAssignableFrom(type); } @Override public Entity readFrom(Class<Entity> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { return null; } } @Provider static class ExtendedEntityReader implements MessageBodyReader<ExtendedEntity> { @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Entity.class.isAssignableFrom(type); } @Override public ExtendedEntity readFrom(Class<ExtendedEntity> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { return null; } } @Provider static class ExtendedExtendedEntityReader implements MessageBodyReader<ExtendedExtendedEntity> { @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Entity.class.isAssignableFrom(type); } @Override public ExtendedExtendedEntity readFrom(Class<ExtendedExtendedEntity> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { return null; } } @Provider static class EntityWriter implements MessageBodyWriter<Entity> { @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Entity.class.isAssignableFrom(type); } @Override public long getSize(Entity entity, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public void writeTo(Entity entity, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { } } @Provider static class ExtendedEntityWriter implements MessageBodyWriter<ExtendedEntity> { @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Entity.class.isAssignableFrom(type); } @Override public long getSize(ExtendedEntity extendedEntity, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public void writeTo(ExtendedEntity extendedEntity, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { } } @Provider static class ExtendedExtendedEntityWriter implements MessageBodyWriter<ExtendedExtendedEntity> { @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Entity.class.isAssignableFrom(type); } @Override public long getSize(ExtendedExtendedEntity extendedExtendedEntity, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public void writeTo(ExtendedExtendedEntity extendedExtendedEntity, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { } } private SecurityManager defaultSecurityManager; private void setupSecurityManager() { defaultSecurityManager = System.getSecurityManager(); final RuntimePermission providersManagePermission = new RuntimePermission("providersManagePermission"); SecurityManager securityManager = new SecurityManager() { @Override public void checkPermission(Permission perm) { if (perm.equals(providersManagePermission)) { throw new SecurityException(); } } }; System.setSecurityManager(securityManager); } private void restoreSecurityManager() { System.setSecurityManager(defaultSecurityManager); } @Test public void initializesProviderBinderOnFirstCallAndReusesSameInstance() throws Exception { ProviderBinder providerBinder = ProviderBinder.getInstance(); assertSame(providerBinder, ProviderBinder.getInstance()); } }