Implement Custom Method Parameters in Injection Injection Jersey

Hello, I am creating an application using dropwizard using jersey 2.16 internally as a REST API framework.

For the entire application for all resource methods, I need some information to parse this information, I defined a user filter as shown below

@java.lang.annotation.Target(ElementType.PARAMETER) @java.lang.annotation.Retention(RetentionPolicy.RUNTIME) public @interface TenantParam { } 

Factory tenant is defined below

 public class TenantFactory implements Factory<Tenant> { private final HttpServletRequest request; private final ApiConfiguration apiConfiguration; @Inject public TenantFactory(HttpServletRequest request, @Named(ApiConfiguration.NAMED_BINDING) ApiConfiguration apiConfiguration) { this.request = request; this.apiConfiguration = apiConfiguration; } @Override public Tenant provide() { return null; } @Override public void dispose(Tenant tenant) { } } 

I really did not implement the method, but the structure is higher. There is also a TenantparamResolver

 public class TenantParamResolver implements InjectionResolver<TenantParam> { @Inject @Named(InjectionResolver.SYSTEM_RESOLVER_NAME) private InjectionResolver<Inject> systemInjectionResolver; @Override public Object resolve(Injectee injectee, ServiceHandle<?> serviceHandle) { if(Tenant.class == injectee.getRequiredType()) { return systemInjectionResolver.resolve(injectee, serviceHandle); } return null; } @Override public boolean isConstructorParameterIndicator() { return false; } @Override public boolean isMethodParameterIndicator() { return true; } } 

Now in my resource method I do as below

 @POST @Timed public ApiResponse create(User user, @TenantParam Tenant tenant) { System.out.println("resource method invoked. calling service method"); System.out.println("service class" + this.service.getClass().toString()); //DatabaseResult<User> result = this.service.insert(user, tenant); //return ApiResponse.buildWithPayload(new Payload<User>().addObjects(result.getResults())); return null; } 

This is how I configure the application

 @Override public void run(Configuration configuration, Environment environment) throws Exception { // bind auth and token param annotations environment.jersey().register(new AbstractBinder() { @Override protected void configure() { bindFactory(TenantFactory.class).to(Tenant.class); bind(TenantParamResolver.class) .to(new TypeLiteral<InjectionResolver<TenantParam>>() {}) .in(Singleton.class); } }); } 

Problem while starting the application. I am getting below the error.

 WARNING: No injection source found for a parameter of type public void com.proretention.commons.auth.resources.Users.create(com.proretention.commons.api.core.Tenant,com.proretention.commons.auth.model.User) at index 0. 

and there is a very long stack stack and description

Below is the signature of pojo's ad

public class User extends com.company.models.Model {

There are no annotations for the User class. A model is a class that defines only one property identifier of type long, as well as annotations in the model class

When I remove the User parameter from above, create a resource method, it works fine, and when I remove TenantParam, it also works fine. The problem only occurs when I use both User and TenantParam

  • What am I missing here? How to solve this error?

edited

I just tried with two special injections of method parameters that also don't work

 @POST @Path("/login") @Timed public void validateUser(@AuthParam AuthToken token, @TenantParam Tenant tenant) { } 
  1. What am I missing here? Is this a limitation in knitwear?
+5
source share
1 answer

Method parameters are handled a little differently for injection. The component we need to implement for this is ValueFactoryProvider . Once you implement this, you also need to link it in AbstractBinder .

The jersey has a template that follows to implement the ValueFactoryProvider . This is the template used to process parameters such as @PathParam and @QueryParam . Jersey has a ValueFactoryProvider for each of them, as well as for others.

The sample is as follows:

  • Instead of using ValueFactoryProvider we extend AbstractValueFactoryProvider

     public static class TenantValueProvider extends AbstractValueFactoryProvider { @Inject public TenantValueProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) { super(mpep, locator, Parameter.Source.UNKNOWN); } @Override protected Factory<?> createValueFactory(Parameter parameter) { if (!parameter.isAnnotationPresent(TenantParam.class) || !Tenant.class.equals(parameter.getRawType())) { return null; } return new Factory<Tenant>() { @Override public Tenant provide() { ... } }; } 

    In this component, it has a method that we need to implement, which returns Factory , which provides the value of the method parameter.

  • InjectionResolver is what is used to process custom annotations. Using this template, instead of directly implementing it, like the OP, we simply extend the ParamInjectionResolver , passing in our implementation class AbstractValueFactoryProvider to a super constructor

     public static class TenantParamInjectionResolver extends ParamInjectionResolver<TenantParam> { public TenantParamInjectionResolver() { super(TenantValueProvider.class); } } 

And indeed it is. Then just snap the two components

 public static class Binder extends AbstractBinder { @Override public void configure() { bind(TenantParamInjectionResolver.class) .to(new TypeLiteral<InjectionResolver<TenantParam>>(){}) .in(Singleton.class); bind(TenantValueProvider.class) .to(ValueFactoryProvider.class) .in(Singleton.class); } } 

Below is a complete test using the Jersey test structure . The required dependencies are listed in the javadoc comments. You can run the test like any other JUnit test

 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.Singleton; import javax.ws.rs.Consumes; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Response; import org.glassfish.hk2.api.Factory; import org.glassfish.hk2.api.InjectionResolver; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.api.TypeLiteral; import org.glassfish.hk2.utilities.binding.AbstractBinder; 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 org.junit.Test; import static org.junit.Assert.assertEquals; /** * Qaru https://stackoverflow.com/q/29145807/2587435 * * Run this like any other JUnit test. Dependencies required are as the following * * <dependency> * <groupId>org.glassfish.jersey.test-framework.providers</groupId> * <artifactId>jersey-test-framework-provider-grizzly2</artifactId> * <version>2.22</version> * <scope>test</scope> * </dependency> * <dependency> * <groupId>org.glassfish.jersey.media</groupId> * <artifactId>jersey-media-json-jackson</artifactId> * <version>2.22</version> * <scope>test</scope> * </dependency> * * @author Paul Samsotha */ public class TenantInjectTest extends JerseyTest { @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public static @interface TenantParam { } public static class User { public String name; } public static class Tenant { public String name; public Tenant(String name) { this.name = name; } } public static class TenantValueProvider extends AbstractValueFactoryProvider { @Inject public TenantValueProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator) { super(mpep, locator, Parameter.Source.UNKNOWN); } @Override protected Factory<?> createValueFactory(Parameter parameter) { if (!parameter.isAnnotationPresent(TenantParam.class) || !Tenant.class.equals(parameter.getRawType())) { return null; } return new AbstractContainerRequestValueFactory<Tenant>() { // You can @Inject things here if needed. Jersey will inject it. // for example @Context HttpServletRequest @Override public Tenant provide() { final ContainerRequest request = getContainerRequest(); final String name = request.getUriInfo().getQueryParameters().getFirst("tenent"); return new Tenant(name); } }; } public static class TenantParamInjectionResolver extends ParamInjectionResolver<TenantParam> { public TenantParamInjectionResolver() { super(TenantValueProvider.class); } } public static class Binder extends AbstractBinder { @Override public void configure() { bind(TenantParamInjectionResolver.class) .to(new TypeLiteral<InjectionResolver<TenantParam>>(){}) .in(Singleton.class); bind(TenantValueProvider.class) .to(ValueFactoryProvider.class) .in(Singleton.class); } } } @Path("test") @Produces("text/plain") @Consumes("application/json") public static class TestResource { @POST public String post(User user, @TenantParam Tenant tenent) { return user.name + ":" + tenent.name; } } @Override public ResourceConfig configure() { return new ResourceConfig(TestResource.class) .register(new TenantValueProvider.Binder()) .register(new LoggingFilter(Logger.getAnonymousLogger(), true)); } @Test public void shouldReturnTenantAndUserName() { final User user = new User(); user.name = "peeskillet"; final Response response = target("test") .queryParam("tenent", "testing") .request() .post(Entity.json(user)); assertEquals(200, response.getStatus()); assertEquals("peeskillet:testing", response.readEntity(String.class)); } } 

See also:

+1
source

Source: https://habr.com/ru/post/1215684/


All Articles