Jersey custom method parameter injection with inbuild injection - java

Hello I am building an application using dropwizard, that is using jersey 2.16 internally as REST API framework.
For the whole application on all resource methods I need some information so to parse that information I defined a custom filter like below
#java.lang.annotation.Target(ElementType.PARAMETER)
#java.lang.annotation.Retention(RetentionPolicy.RUNTIME)
public #interface TenantParam {
}
The tenant factory 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 haven't actually implemented the method but structure is above. 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 am doing like 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;
}
Here is how I am configuring 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);
}
});
}
The problem is during application start I am getting below 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 very long stack error stack and description
Below is the declaration signature of user pojo
public class User extends com.company.models.Model {
No annotations on User class. Model is a class that defines only single property id of type long and also no annotations on model class
When I remove the User parameter from above create resource method it works fine and when I removed TenantParam it also works fine. The problem only occurs when I use both User and TenantParam
What I am missing here ? how to resolve this error ?
EDITED
I just tried with two custom method param injection, that is also not working
#POST
#Path("/login")
#Timed
public void validateUser(#AuthParam AuthToken token, #TenantParam Tenant tenant) {
}
What I am missing here ? Is this a restriction in jersey ?

Method parameters are handled a little differently for injection. The component we need to implement for this, is the ValueFactoryProvider. Once you implement that, you also need to bind it in your AbstractBinder.
Jersey has a pattern that it follows for implementing the ValueFactoryProvider. This is the pattern used to handle parameters like #PathParam and #QueryParam. Jersey has a ValueFactoryProvider for each one of those, as well as others.
The pattern is as follows:
Instead of implementing the ValueFactoryProvider directly, 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 we need to implement that returns the Factory that provides the method parameter value.
The InjectionResolver is what is used to handle the custom annotation. With this pattern, instead of directly implementing it, as the OP has, we just extend ParamInjectionResolver passing in our AbstractValueFactoryProvider implementation class to super constructor
public static class TenantParamInjectionResolver
extends ParamInjectionResolver<TenantParam> {
public TenantParamInjectionResolver() {
super(TenantValueProvider.class);
}
}
And that's really it. Then just bind 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 Jersey Test Framework. 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;
/**
* Stack Overflow 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:
Jersey 2.x Custom Injection Annotation With Attributes
My Comment in the Dropwizard issue: "No injection source found for a parameter"

Related

Jersey HK2 Dependency Injection

I'm writing a simple microservices that exposes REST API. So I started working with Jersey and of course I need to Inject my object into jersey resources. Basically I have 2 classes that defines a set of resources and some of them need to use another service.
So basically I have:
public interface MyService {
String getServiceName();
void doService();
}
2 implementations of this interface (MyServiceBean and MyAlternativeServiceBean)
and, as far as I understood reading jersey docs, I defined an hk2 Binder:
public class MyBinder implements Binder{
#Override
public void bind(DynamicConfiguration config) {
DescriptorImpl descriptor = BuilderHelper.link(MyServiceBean.class).named("MyServiceBean").to(MyService.class).build();
config.bind(descriptor);
config.bind(BuilderHelper.link(MyAlternativeServiceBean.class).named("MyAlternativeServiceBean").to(MyService.class).build());
}
I registered this binder to the ApplicationConfig class
public class ApplicationConfig extends ResourceConfig{
public ApplicationConfig(){
property("property.value", "MyAlternativeServiceImplementation");
registerInstances(new MyBinder());
}
}
And annotated properly into the resources
#Path("first")
public class First {
#Inject #Named(value = "MyServiceBean")
private MyService myService;
//...
}
#Path("second")
public class Second {
#Inject #Named(value = "MyAlternativeServiceBean")
private MyService myService;
//...
}
All works until MyService implementation have no args constructor. But in 1 case I need to provide a dependency also to MyAlternativeServiceBean.
Here is the constructor
#Inject #Named("property.value")
public MyAlternativeServiceBean(String property){
this.property = property;
}
But I get an exception:
javax.servlet.ServletException: A MultiException has 5 exceptions. They are:|1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=String,parent=MyAlternativeServiceBean,qualifiers={}),position=0,optional=false,self=false,unqualified=null,2080509613)|2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.hpe.services.MyAlternativeServiceBean errors were found|3. java.lang.IllegalStateException: Unable to perform operation: resolve on com.hpe.services.MyAlternativeServiceBean|4. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.hpe.tests.SecondEntryPoint errors were found|5. java.lang.IllegalStateException: Unable to perform operation: resolve on com.hpe.tests.SecondEntryPoint|
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:392)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:219)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:684)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:501)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:229)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1086)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:427)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:193)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1020)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:135)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)
at org.eclipse.jetty.server.Server.handle(Server.java:370)
at org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:494)
at org.eclipse.jetty.server.AbstractHttpConnection.headerComplete(AbstractHttpConnection.java:973)
at org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.headerComplete(AbstractHttpConnection.java:1035)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:641)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:231)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:696)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:53)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
at java.lang.Thread.run(Thread.java:745)
Basically I don't konw how to inject properties/constants (that I can read from a configuration file for instance) in hk2
Thanks
Regards
What you can do is create a custom annotation
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface Config {
String value();
}
Then create an InjectionResolver for it (which allows for injection using custom annotations)
public static class ConfigInjectionResolver implements InjectionResolver<Config> {
private static final Map<String, String> properties = new HashMap<>();
public ConfigInjectionResolver() {
properties.put("greeting.message", "Hello World");
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (String.class == injectee.getRequiredType()) {
AnnotatedElement elem = injectee.getParent();
if (elem instanceof Constructor) {
Constructor ctor = (Constructor) elem;
Config config = (Config) ctor.getParameterAnnotations()[injectee.getPosition()][0];
return properties.get(config.value());
} else {
Config config = elem.getAnnotation(Config.class);
return properties.get(config.value());
}
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() { return true; }
#Override
public boolean isMethodParameterIndicator() { return false; }
}
This example just uses a Map, but I'm sure you can figure out how to make it use Properties. Once you register the InjectionResolver, you can now just do
public SomeService(#Config("some.property") String property) {}
Here is a complete test case
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import static org.junit.Assert.*;
/**
* Run like any other JUnit Test. Only one required dependency
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*
* #author Paul Samsotha
*/
public class ConfigExample extends JerseyTest {
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public static #interface Config {
String value();
}
public static class ConfigInjectionResolver implements InjectionResolver<Config> {
private static final Map<String, String> properties = new HashMap<>();
public ConfigInjectionResolver() {
properties.put("greeting.message", "Hello World");
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (String.class == injectee.getRequiredType()) {
AnnotatedElement elem = injectee.getParent();
if (elem instanceof Constructor) {
Constructor ctor = (Constructor) elem;
Config config = (Config) ctor.getParameterAnnotations()[injectee.getPosition()][0];
return properties.get(config.value());
} else {
Config config = elem.getAnnotation(Config.class);
return properties.get(config.value());
}
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() { return true; }
#Override
public boolean isMethodParameterIndicator() { return false; }
}
private static interface GreetingService {
String getGreeting();
}
private static class ConfiguredGreetingService implements GreetingService {
private String message;
public ConfiguredGreetingService(#Config("greeting.message") String message) {
this.message = message;
}
#Override
public String getGreeting() {
return this.message;
}
}
#Path("greeting")
public static class GreetingResource {
#Inject
private GreetingService greetingService;
#GET
public String getConfigProp() {
return greetingService.getGreeting();
}
}
#Override
public ResourceConfig configure() {
ResourceConfig config = new ResourceConfig(GreetingResource.class);
config.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
config.register(new AbstractBinder(){
#Override
protected void configure() {
bind(ConfiguredGreetingService.class).to(GreetingService.class).in(Singleton.class);
bind(ConfigInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Config>>(){})
.in(Singleton.class);
}
});
return config;
}
#Test
public void should_get_configured_greeting() {
final Response response = target("greeting")
.request().get();
assertEquals("Hello World", response.readEntity(String.class));
}
}

Passing #Context argument to method in class

I have an existing class I'm trying to hook into to get some header parameters to SSO a user into our system. The class is as follows.
import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import org.springframework.stereotype.Component;
#Component
#Path("/http")
public class HttpResource {
#GET
#Path("/getHeaders")
#Produces(MediaType.APPLICATION_JSON)
public Map<String, String> getAllHeaders(#Context HttpHeaders headers) {
Map<String, String> headerList = new HashMap<String, String>();
for (String key : headers.getRequestHeaders().keySet()) {
String value = headers.getRequestHeader(key).get(0);
System.out.println(key + " : " + value);
headerList.put(key, value);
}
return headerList;
}
}
What I'm trying to figure out is how do I call getAllHeaders() with the #Context argument? I've found a ton of examples of the class I have, but nothing that shows how to call it.
I've also tried putting the annotation inside the class instead of as an argument.
#Context
HttpHeaders httpHeaders;
but when I try to access httpHeaders.getAllHeaders() it returns null. I assume because it's not actually created because the javax documents say it will never return null.
I'm trying to call this within my SSOAuthorizationFilter.java, but have also tried accessing it via a controller as well.
Write an Annotation first.
#Retention(RUNTIME)
#Target({ PARAMETER })
#Documented
public #interface SSOAuthorization {}
And then a Resolver for that.
public class SSOAuthorizationResolver {
public static class SSOAuthorizationInjectionResolver extends
ParamInjectionResolver<SSOAuthorization> {
public SSOAuthorizationInjectionResolver() {
super(SSOAuthorizationValueFactoryProvider.class);
}
}
#Singleton
public static class SSOAuthorizationValueFactoryProvider extends
AbstractValueFactoryProvider {
#Context
private HttpHeaders httpHeaders;
#Inject
public SSOAuthorizationValueFactoryProvider(
final MultivaluedParameterExtractorProvider mpep,
final ServiceLocator injector) {
super(mpep, injector, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
final Class<?> classType = parameter.getRawType();
if (!Language.class.equals(classType)
|| parameter.getAnnotation(SSOAuthorization.class) == null) {
return null;
}
return new AbstractContainerRequestValueFactory<String>() {
#Override
public String provide() {
// Can use httpHeader to get your header here.
return httpHeader.getHeaderString("SSOAuthorization");
}
};
}
}
// Binder implementation
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(SSOAuthorizationValueFactoryProvider.class).to(
ValueFactoryProvider.class).in(Singleton.class);
bind(SSOAuthorizationInjectionResolver.class).to(
new TypeLiteral<InjectionResolver<SSOAuthorization>>() {
}).in(Singleton.class);
}
}
}
And in the ResourceConfig just register the Resolver
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig(Class... classes) {
super(classes);
register(new SSOAuthorizationResolver.Binder());
}
}
And finally use it in your controller with the #SSOAuthorization annotation.
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public String someMethod(#SSOAuthorization String auth) {
return auth;
}

Jaxrs Filter at Resource Locator

I'm trying to intercept requests to my jaxrs apis basead on annotations, my filter is simple:
#Provider
public class Filter implements ContainerRequestFilter {
#Context
private ResourceInfo info;
#Override
public void filter(ContainerRequestContext crc) throws IOException {
// here I'm trying to get the annotate resource class or method.
info.getResourceClass().isAnnotationPresent(MyCustomAnnotation.class);
}
}
this works fine with a simple resource like this: (works both in class and method)
#Path("/")
public class SimpleResource {
#GET
#MyCustomAnnotation
public String test() {
return "test";
}
}
But in my real application, I have scenarios like this:
#Path("/")
public class RootResource {
#Inject
ChildResource childResource;
#Path("child")
public ChildResource child () {
return childResource;
}
}
So, I wanna put my custom annotation only on ResourceLocator and on the fly verify that the final resource contains the annotation.
#Path("/")
#CustomAnnotation
public class RootResource {
#Inject
ChildResource childResource;
#Path("child")
public ChildResource child () {
return childResource;
}
}
is it possible? or i can only get information about the matched resource?
"In jersey how would be this?"
With Jersey you have access to the resource model, and ways to traverse the model. You can see jersey server introspectionmodeller not public in v2.0? for some explanation and examples of how to traverse the model and Resource and ResourceMethod. Other than that, there is not much documentation these APIs.
Below is a complete example Using Jersey Test Framework. You can run the class like any other JUnit test. You just need this one dependency to run it
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.19</version>
<scope>test</scope>
</dependency>
And here's the test.
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import static junit.framework.Assert.assertEquals;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ExtendedUriInfo;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
public class ResourceModelTest extends JerseyTest {
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public static #interface ResourceAnnotation {
String value();
}
#Path("root")
#ResourceAnnotation("SomeValue")
public static class ParentResource {
#Path("sub")
public ChildResource getChild() {
return new ChildResource();
}
#GET
public String get() {
return "ROOT";
}
}
public static class ChildResource {
#GET
public String get() {
return "CHILD";
}
}
#Provider
public static class ResourceFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
ExtendedUriInfo info = (ExtendedUriInfo) requestContext.getUriInfo();
List<ResourceMethod> resourceLocators = info.getMatchedResourceLocators();
if (!resourceLocators.isEmpty()) {
Resource parent = resourceLocators.get(0).getParent();
Class<?> parentClass = parent.getHandlerClasses().iterator().next();
ResourceAnnotation anno = parentClass.getAnnotation(ResourceAnnotation.class);
if (anno != null) {
responseContext.getHeaders().putSingle("X-SubResource-Header", anno.value());
}
}
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(ParentResource.class)
.register(ResourceFilter.class);
}
#Override
public void configureClient(ClientConfig config) {
config.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
#Test
public void get_child_resource() {
Response response = target("root/sub").request().get();
assertEquals(200, response.getStatus());
assertEquals("SomeValue", response.getHeaderString("X-SubResource-Header"));
}
}

Jersey - How to mock service

I am using "Jersey Test Framework" for unit testing my webservice.
Here is my resource class :
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
// The Java class will be hosted at the URI path "/helloworld"
#Path("/helloworld")
public class class HelloWorldResource {
private SomeService service;
#GET
#Produces("text/plain")
public String getClichedMessage() {
// Return some cliched textual content
String responseFromSomeService = service.getSomething();
return responseFromSomeService;
}
}
How can I mock SomeService in unit tests ?
See Update below: You don't need a Factory
If you are using Jersey 2, one solution would be to use Custom Injection and Lifecycle Management feature (with HK2 - which comes with the Jersey dist). Also required would be a Mocking framework of course. I'm going to use Mockito.
First create a Factory with mocked instance:
public static interface GreetingService {
public String getGreeting(String name);
}
public static class MockGreetingServiceFactory
implements Factory<GreetingService> {
#Override
public GreetingService provide() {
final GreetingService mockedService
= Mockito.mock(GreetingService.class);
Mockito.when(mockedService.getGreeting(Mockito.anyString()))
.thenAnswer(new Answer<String>() {
#Override
public String answer(InvocationOnMock invocation)
throws Throwable {
String name = (String)invocation.getArguments()[0];
return "Hello " + name;
}
});
return mockedService;
}
#Override
public void dispose(GreetingService t) {}
}
Then use the AbstractBinder to bind the factory to the interface/service class, and register the binder. (It's all described in the link above):
#Override
public Application configure() {
AbstractBinder binder = new AbstractBinder() {
#Override
protected void configure() {
bindFactory(MockGreetingServiceFactory.class)
.to(GreetingService.class);
}
};
ResourceConfig config = new ResourceConfig(GreetingResource.class);
config.register(binder);
return config;
}
Seems like a lot, but it's just an option. I'm not too familiar with the test framework, or if it has an mocking capabilities for injection.
Here is the full test:
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
public class ServiceMockingTest extends JerseyTest {
#Path("/greeting")
public static class GreetingResource {
#Inject
private GreetingService greetingService;
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getGreeting(#QueryParam("name") String name) {
return greetingService.getGreeting(name);
}
}
public static interface GreetingService {
public String getGreeting(String name);
}
public static class MockGreetingServiceFactory
implements Factory<GreetingService> {
#Override
public GreetingService provide() {
final GreetingService mockedService
= Mockito.mock(GreetingService.class);
Mockito.when(mockedService.getGreeting(Mockito.anyString()))
.thenAnswer(new Answer<String>() {
#Override
public String answer(InvocationOnMock invocation)
throws Throwable {
String name = (String)invocation.getArguments()[0];
return "Hello " + name;
}
});
return mockedService;
}
#Override
public void dispose(GreetingService t) {}
}
#Override
public Application configure() {
AbstractBinder binder = new AbstractBinder() {
#Override
protected void configure() {
bindFactory(MockGreetingServiceFactory.class)
.to(GreetingService.class);
}
};
ResourceConfig config = new ResourceConfig(GreetingResource.class);
config.register(binder);
return config;
}
#Test
public void testMockedGreetingService() {
Client client = ClientBuilder.newClient();
Response response = client.target("http://localhost:9998/greeting")
.queryParam("name", "peeskillet")
.request(MediaType.TEXT_PLAIN).get();
Assert.assertEquals(200, response.getStatus());
String msg = response.readEntity(String.class);
Assert.assertEquals("Hello peeskillet", msg);
System.out.println("Message: " + msg);
response.close();
client.close();
}
}
Dependencies for this test:
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.13</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.0</version>
</dependency>
UPDATE
So in most cases, you really don't need a Factory. You can simply bind the mock instance with its contract:
#Mock
private Service service;
#Override
public ResourceConfig configure() {
MockitoAnnotations.initMocks(this);
return new ResourceConfig()
.register(MyResource.class)
.register(new AbstractBinder() {
#Override
protected configure() {
bind(service).to(Service.class);
}
});
}
#Test
public void test() {
when(service.getSomething()).thenReturn("Something");
// test
}
Much simpler!
Here is how I did it with Jersey 2.20, Spring 4.1.4 RELEASE, Mockito 1.10.8, and TestNG 6.8.8.
#Test
public class CasesResourceTest extends JerseyTestNg.ContainerPerMethodTest {
#Mock
private CaseService caseService;
#Mock
private CaseConverter caseConverter;
#Mock
private CaseRepository caseRepository;
private CasesResource casesResource;
#Override
protected Application configure() {
MockitoAnnotations.initMocks(this);
casesResource = new CasesResource();
AbstractBinder binder = new AbstractBinder() {
#Override
protected void configure() {
bindFactory(new InstanceFactory<CaseConverter>(caseConverter)).to(CaseConverter.class);
bindFactory(new InstanceFactory<CaseService>(caseService)).to(CaseService.class);
}
};
return new ResourceConfig()
.register(binder)
.register(casesResource)
.property("contextConfigLocation", "solve-scm-rest/test-context.xml");
}
public void getAllCases() throws Exception {
when(caseService.getAll()).thenReturn(Lists.newArrayList(new solve.scm.domain.Case()));
when(caseConverter.convertToApi(any(solve.scm.domain.Case.class))).thenReturn(new Case());
Collection<Case> cases = target("/cases").request().get(new GenericType<Collection<Case>>(){});
verify(caseService, times(1)).getAll();
verify(caseConverter, times(1)).convertToApi(any(solve.scm.domain.Case.class));
assertThat(cases).hasSize(1);
}
}
You also need this class which makes the binding code above a bit easier:
public class InstanceFactory<T> implements Factory<T> {
private T instance;
public InstanceFactory(T instance) {
this.instance = instance;
}
#Override
public void dispose(T t) {
}
#Override
public T provide() {
return instance;
}
}
Edited as pr. request. This is the contents of my test-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
It turns out that my test-context.xml does not instantiate any beans nor scan any packages, in fact, it does not do anything at all. I guess I just put it there in case I might need it.

Servlet context injection fail while using jersey test framework

I'm beginning with jersey and trying to get freemarker working with it using TDD. I want to make a ViewProcessor for my templates, but fail to inject the servlet context in the class.
Here is the class code :
#Provider
public class myProcessor implements ViewProcessor<Template> {
[...]
#Context
public ServletContext myContext;
[...]
freemarkerConfiguration.setTemplateLoader(
new WebappTemplateLoader(myContext,
myContext.getInitParameter("freemarker.template.path")));
[...]
}
And here is the test code :
public class myProcessorTest extends JerseyTest {
public static myProcessor mp;
public myProcessorTest() throws Exception{
super(new WebAppDescriptor.Builder("com.domain").build());
}
#Test
public void firstTest(){
mp = new myProcessor();
String path = new String("test.ftl");
Template template = mp.resolve(path);
assertNotNull(template);
}
}
I use maven with dependencies as follow:
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly</artifactId>
<version>1.5-SNAPSHOT</version>
<scope>test</scope>
</dependency>
My code runs fine when I deploy to my local jetty server. But if I want to test the code in my IDE, it failed to inject the servlet context (#Context) : myContext is null when I run the test :/
I think I'm missing something, but I'm a complete beginner with servlet world.
Here's a technique for testing a specific resource class, using Jersey Test Framework, with servlet support. Also demonstrates how to customize the ServletContext.
import javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.TestProperties;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerException;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import static org.mockito.Mockito.mock;
/**
* A base class for testing web resources.
*/
public abstract class WebResourceTest extends JerseyTest {
/**
* Creates a JAX-RS resource configuration for test purposes.
*/
#Override
protected abstract ResourceConfig configure();
/**
* Creates a test container factory with servlet support.
*/
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new GrizzlyWebTestContainerFactory();
}
/**
* Configures a deployment context for JAX-RS.
*/
#Override
protected DeploymentContext configureDeployment() {
ResourceConfig app = configure();
app.register(new Feature() {
#Context
ServletContext servletContext;
#Override
public boolean configure(FeatureContext context) {
servletContext.setAttribute("example", new Object());
return true;
}
});
return ServletDeploymentContext.forServlet(new ServletContainer(app)).build();
}
}
A usage example:
import org.glassfish.jersey.server.ResourceConfig;
import javax.ws.rs.core.Context;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.core.Response;
import static org.mockito.Mockito.spy;
import static org.testng.Assert.assertEquals;
import static org.junit.Assert.*;
public class MyResourceTest extends WebResourceTest {
private MyResource resource;
#Override
protected ResourceConfig configure() {
resource = spy(new MyResource());
return new ResourceConfig().register(resource);
}
#Test
public void testSomething() {
Response r = target("/myresource").request().get();
assertEquals(200, r.getStatus());
assertEquals(1, resource.count);
}
}
#Path("/myresource")
public class MyResource {
int count = 0;
#Context
protected ServletContext servletContext;
#GET
public void get() {
Object attr = servletContext.getAttribute("example");
count++;
}
}
There's a couple of ways to do it. Remove the constructor and implement a configure() method like this:
public class myProcessorTest extends JerseyTest {
public static myProcessor mp;
#Override
protected AppDescriptor configure() {
return new WebAppDescriptor.Builder("com.domain")
.contextParam("contextConfigLocation", "classpath:/applicationContext.xml")
.contextPath("/").servletClass(SpringServlet.class)
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class)
.build();
}
or alternatively you can annotate your test with the spring context:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:applicationContext.xml")
public class MyProcessorTest extends JerseyTest {
public static myProcessor mp;
There is a solution to this problem that does not require spring, assuming you are using the default/standard Grizzy2 test framework provider. According to this answer the jersey-test-framework-provider-grizzly2 framework provider does not utilize a servlet environment in constructing the application context. Your symptoms result from there being no ServletContext instance to inject.
The workaround is to provide the test container for the unit tests yourself. First, modify your dependencies:
<!--<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.25</version>
<scope>test</scope>
</dependency>-->
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>2.25</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-servlet</artifactId>
<version>2.25</version>
</dependency>
Then, modify your test to provide a Grizzy servlet container:
#Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return (final URI baseUri, final DeploymentContext deploymentContext) ->
new TestContainer() {
private HttpServer server = null;
#Override
public ClientConfig getClientConfig() {
return null;
}
#Override
public URI getBaseUri() {
return baseUri;
}
#Override
public void start() {
try {
this.server = GrizzlyWebContainerFactory.create(baseUri, Collections
.singletonMap("jersey.config.server.provider.packages", "<your-package-name>"));
} catch (final ProcessingException | IOException cause) {
throw new TestContainerException(cause);
}
}
#Override
public void stop() {
this.server.shutdownNow();
}
};
}
I assume that you are going to use this in multiple unit tests, so it may be wise to extend JerseyTest so this common configuration may be performed automatically. Additionally, it may be worth reviewing org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory to see if there is any functionality provided by the test container that you wish to emulate/preserve. The example provided should be able to be dropped into your test to at least confirm this is a fix.
EDIT: In my own implementation, I required the ability to still supply a ResourceConfig when generating the server. I suspect that this is likely to be the common case for other Jersey Test Framework users. A working example of the proposed TestContainerFactory follows.
import java.io.IOException;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.servlet.WebappContext;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.spi.TestContainer;
import org.glassfish.jersey.test.spi.TestContainerException;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.glassfish.jersey.test.spi.TestHelper;
public class RestTestContainerFactory implements TestContainerFactory {
public static class RestTestContainer implements TestContainer {
private static final Logger LOGGER = Logger.getLogger(RestTestContainer.class.getName());
private URI baseUri = null;
private final HttpServer server;
public RestTestContainer(final URI baseUri, final DeploymentContext context) {
this.baseUri = UriBuilder.fromUri(baseUri).path(context.getContextPath()).build();
if(LOGGER.isLoggable(Level.INFO)) {
LOGGER.info("Creating RestRestContainer configured at the base URI "+TestHelper.zeroPortToAvailablePort(baseUri));
}
try {
final WebappContext webContext = new WebappContext("TestContext", context.getContextPath());
context.getResourceConfig()
.register(new AbstractBinder() {
#Override
protected void configure() {
bind(webContext).to(ServletContext.class);
}
});
this.server = GrizzlyHttpServerFactory.createHttpServer(this.baseUri, context.getResourceConfig(), false);
webContext.deploy(this.server);
} catch (final ProcessingException cause) {
throw new TestContainerException(cause);
}
}
#Override
public ClientConfig getClientConfig() {
return null;
}
#Override
public URI getBaseUri() {
return baseUri;
}
#Override
public void start() {
if(server.isStarted()) {
LOGGER.warning("Ignoring start request - RestTestContainer is already started");
} else {
LOGGER.fine("Starting RestTestContainer...");
try {
server.start();
if(baseUri.getPort() == 0) {
baseUri = UriBuilder.fromUri(baseUri)
.port(server.getListener("grizzly").getPort())
.build();
LOGGER.info("Started GrizzlyTestContainer at the base URI "+baseUri);
}
}
catch(final ProcessingException | IOException cause) {
throw new TestContainerException(cause);
}
}
}
#Override
public void stop() {
if(server.isStarted()) {
LOGGER.fine("Stopping RestTestContainer...");
server.shutdownNow();
} else {
LOGGER.warning("Ignoring stop request - RestTestContainer is already stopped");
}
}
}
#Override
public TestContainer create(final URI baseUri, final DeploymentContext context) {
return new RestTestContainer(baseUri,context);
}
}
Frustratingly, grizzly's GrizzlyWebContainerFactory will provide a servlet context, but not configure with a resource config. Inversely, GrizzlyHttpServerFactory will configure an application with a ResourceConfig, but will not provide a web context.
We can work around this by creating the WebappContext (extends ServletContext) manually, configuring it, and then injecting it into the resource config by means of an AbstractBinder.

Categories