Last active
February 8, 2018 17:40
-
-
Save psamsotha/3f7e1a1b31e0611f37ec to your computer and use it in GitHub Desktop.
Custom method parameter injection with Jersey 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
import java.util.logging.Logger; | |
import javax.inject.Inject; | |
import javax.inject.Named; | |
import javax.inject.Singleton; | |
import javax.ws.rs.GET; | |
import javax.ws.rs.POST; | |
import javax.ws.rs.Path; | |
import javax.ws.rs.client.Entity; | |
import javax.ws.rs.core.Context; | |
import javax.ws.rs.core.Response; | |
import javax.ws.rs.ext.ExceptionMapper; | |
import javax.ws.rs.ext.Provider; | |
import org.glassfish.hk2.api.Factory; | |
import org.glassfish.hk2.api.Injectee; | |
import org.glassfish.hk2.api.InjectionResolver; | |
import org.glassfish.hk2.api.ServiceHandle; | |
import org.glassfish.hk2.api.ServiceLocator; | |
import org.glassfish.hk2.api.TypeLiteral; | |
import org.glassfish.hk2.utilities.binding.AbstractBinder; | |
import org.glassfish.jersey.client.ClientConfig; | |
import org.glassfish.jersey.filter.LoggingFilter; | |
import org.glassfish.jersey.server.ContainerRequest; | |
import org.glassfish.jersey.server.ResourceConfig; | |
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory; | |
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider; | |
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider; | |
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver; | |
import org.glassfish.jersey.server.model.Parameter; | |
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider; | |
import org.glassfish.jersey.test.JerseyTest; | |
import static org.junit.Assert.assertEquals; | |
import org.junit.Test; | |
/* | |
* Run this example like any other JUnit test. | |
* | |
* This is the only required Maven dependency to run the test. | |
* | |
* <dependency> | |
* <groupId>org.glassfish.jersey.test-framework.providers</groupId> | |
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId> | |
* <version>2.19</version> | |
* <scope>test</scope> | |
* </dependency> | |
* | |
* @author Paul Samsotha | |
*/ | |
public class MethodParamsInjectionTest extends JerseyTest { | |
private static final String TENANT_NAME = "Some Tenant"; | |
private static final String MESSAGE_BODY = "Message"; | |
private static final String NAME_AND_MESSAGE = TENANT_NAME + ":" + MESSAGE_BODY; | |
public static class Tenant { | |
public String name; | |
public Tenant(String name) { this.name = name; } | |
} | |
public static class TenantFactory implements Factory<Tenant> { | |
@Override | |
public Tenant provide() { return new Tenant(TENANT_NAME); } | |
@Override | |
public void dispose(Tenant t) { } | |
} | |
@Provider | |
public static class DebugMapper implements ExceptionMapper<Throwable> { | |
@Override | |
public Response toResponse(Throwable exception) { | |
exception.printStackTrace(); | |
return Response.serverError() | |
.entity(exception.getMessage()) | |
.build(); | |
} | |
} | |
@Target(ElementType.PARAMETER) | |
@Retention(RetentionPolicy.RUNTIME) | |
public static @interface TenantParam {} | |
public static class TenantParamResolver implements InjectionResolver<TenantParam> { | |
@Inject | |
@Named(InjectionResolver.SYSTEM_RESOLVER_NAME) | |
InjectionResolver<Inject> systemInjectionResolver; | |
@Override | |
public Object resolve(Injectee injectee, ServiceHandle<?> root) { | |
if (Tenant.class == injectee.getRequiredType()) { | |
return systemInjectionResolver.resolve(injectee, root); | |
} | |
return null; | |
} | |
@Override | |
public boolean isConstructorParameterIndicator() { return false; } | |
@Override | |
public boolean isMethodParameterIndicator() { return false; } | |
} | |
public static class TenantParamValueProvider implements ValueFactoryProvider { | |
@Override | |
public Factory<?> getValueFactory(Parameter parameter) { | |
if (parameter.getRawType() == Tenant.class | |
&& parameter.isAnnotationPresent(TenantParam.class)) { | |
return new TenantFactory(); | |
} | |
return null; | |
} | |
@Override | |
public ValueFactoryProvider.PriorityType getPriority() { | |
return Priority.NORMAL; | |
} | |
} | |
public static class TenantBinder extends AbstractBinder { | |
@Override | |
protected void configure() { | |
bindFactory(TenantFactory.class).to(Tenant.class); | |
//bind(TenantParamResolver.class) | |
// .to(new TypeLiteral<InjectionResolver<TenantParam>>(){}) | |
// .in(Singleton.class); | |
//bind(TenantParamValueProvider.class) | |
// .to(ValueFactoryProvider.class) | |
// .in(Singleton.class); | |
} | |
} | |
@Path("with-context") | |
public static class WithContextNoOther { | |
@GET | |
public String get(@Context Tenant tenant) { | |
return tenant.name; | |
} | |
} | |
@Path("with-context-and-entity") | |
public static class WithContextAndEntity { | |
@POST | |
public String post(@Context Tenant tenant, String body) { | |
return tenant.name + ":" + body; | |
} | |
} | |
@Path("with-custom") | |
public static class WithCustomNoOther { | |
@GET | |
public String get(@TenantParam Tenant tenant) { | |
return tenant.name; | |
} | |
} | |
@Path("with-custom-and-entity") | |
public static class WithCustomAndEntity { | |
@POST | |
public String post(@TenantParam Tenant tenant, String body) { | |
return tenant.name + ":" + body; | |
} | |
} | |
@Override | |
public ResourceConfig configure() { | |
return new ResourceConfig() | |
.register(new TenantBinder()) | |
// see below for following class. You can use this instead | |
// of the above registered TenantBinder | |
//.register(new TenantParamValueFactoryProvider.Binder()) | |
.register(WithContextNoOther.class) | |
//.register(WithContextAndEntity.class) | |
//.register(WithCustomNoOther.class) | |
//.register(WithCustomAndEntity.class) | |
.register(DebugMapper.class); | |
} | |
@Override | |
public void configureClient(ClientConfig config) { | |
config.register(new LoggingFilter(Logger.getAnonymousLogger(), true)); | |
} | |
@Test | |
public void should_return_tenant_with_Context_injection() { | |
Response response = target("with-context").request().get(); | |
assertEquals(200, response.getStatus()); | |
assertEquals(TENANT_NAME, response.readEntity(String.class)); | |
} | |
//@Test | |
public void should_return_tenant_with_Context_injection_and_entity() { | |
Response response = target("with-context-and-entity").request() | |
.post(Entity.text(MESSAGE_BODY)); | |
assertEquals(200, response.getStatus()); | |
assertEquals(NAME_AND_MESSAGE, response.readEntity(String.class)); | |
} | |
//@Test | |
public void should_return_tenant_with_Custom_injection() { | |
Response response = target("with-custom").request().get(); | |
assertEquals(200, response.getStatus()); | |
assertEquals(TENANT_NAME, response.readEntity(String.class)); | |
} | |
//@Test | |
public void should_return_tenant_with_Custom_injection_and_entity() { | |
Response response = target("with-custom-and-entity").request() | |
.post(Entity.text(MESSAGE_BODY)); | |
assertEquals(200, response.getStatus()); | |
assertEquals(NAME_AND_MESSAGE, response.readEntity(String.class)); | |
} | |
// ========================================================================== | |
/** | |
* You can register this class with HK2 instead of the above `FactoryBinder` | |
*/ | |
public static class TenantParamValueFactoryProvider extends AbstractValueFactoryProvider { | |
@Inject | |
public TenantParamValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, | |
ServiceLocator locator) { | |
super(mpep, locator, Parameter.Source.UNKNOWN); | |
} | |
public static class TenantFactory extends AbstractContainerRequestValueFactory<Tenant> { | |
@Override | |
public Tenant provide() { | |
ContainerRequest request = getContainerRequest(); | |
// do something with request. | |
return new Tenant(TENANT_NAME); | |
} | |
} | |
@Override | |
protected Factory<?> createValueFactory(Parameter parameter) { | |
if (parameter.getRawType() == Tenant.class | |
&& parameter.isAnnotationPresent(TenantParam.class)) { | |
return new TenantFactory(); | |
} | |
return null; | |
} | |
public static class TenantParamInjectionResolver extends ParamInjectionResolver<TenantParam> { | |
public TenantParamInjectionResolver() { | |
super(TenantParamValueFactoryProvider.class); | |
} | |
} | |
public static class Binder extends AbstractBinder { | |
@Override | |
protected void configure() { | |
bind(TenantParamValueFactoryProvider.class) | |
.to(ValueFactoryProvider.class) | |
.in(Singleton.class); | |
bind(TenantParamInjectionResolver.class) | |
.to(new TypeLiteral<InjectionResolver<TenantParam>>(){}) | |
.in(Singleton.class); | |
bindFactory(TenantFactory.class) | |
.to(Tenant.class) | |
.in(Singleton.class); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment