Skip to content

Instantly share code, notes, and snippets.

@psamsotha
Last active February 8, 2018 17:40
Show Gist options
  • Save psamsotha/3f7e1a1b31e0611f37ec to your computer and use it in GitHub Desktop.
Save psamsotha/3f7e1a1b31e0611f37ec to your computer and use it in GitHub Desktop.
Custom method parameter injection with Jersey 2
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