I am upgrading a legacy project from Jersey 2.22 to the latest version, and I have some problem migrating our dependency injection.
We were using the custom annotation to inject the user / token in the method parameter :
#GET
public Response getAll(#SdnBasicAuth final UserManagement.User user) {
// ... Check user rights return Response.accepted().build();
}
And now I cannot get a working solution with the new Jersey decoupled dependency injection.
The system behavior is the following :
At the startup of the application the getAll method is called twice with my dummy user (that's not the expected behavior, we want only this call in response to a request).
Both supplier and resolver are called at the startup but not when I make a "real" request.
Note : In the Resolver the parameter final ServiceHandle<?> root is null.
How can I reproduce the behavior of the Jersey 2.22 (see code at the bottom of the post)
Thanks for helping.
The new code version with jersey 2.29 :
Dependencies list :
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-core</artifactId>
<version>${hk2.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-api</artifactId>
<version>${hk2.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-locator</artifactId>
<version>${hk2.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-server</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
<version>${jersey.version}</version>
<scope>provided</scope>
</dependency>
The Binder :
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Singleton;
import javax.ws.rs.core.GenericType;
public class BasicAuthBinder extends AbstractBinder {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthBinder.class);
#Override
protected void configure() {
bindFactory(BasicAuthSupplier.class)
.to(UserManagement.User.class)
.proxyForSameScope(false)
.in(Singleton.class);
// I have try to change to this :
// .in(RequestScoped.class);
// But after that I have the following exception at startup.
// org.glassfish.hk2.api.MultiException: A MultiException has 1 exceptions.
// They are:
// java.lang.IllegalStateException: Not inside a request scope.
bind(BasicAuthResolver.class)
.to(new GenericType<InjectionResolver<SdnBasicAuth>>() {})
.in(Singleton.class);
}
}
The resolver
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import javax.inject.Inject;
import javax.inject.Named;
public class BasicAuthResolver implements org.glassfish.hk2.api.InjectionResolver<SdnBasicAuth> {
#Inject
#Named (InjectionResolver.SYSTEM_RESOLVER_NAME)
org.glassfish.hk2.api.InjectionResolver<Inject> systemInjectionResolver;
#Override
public Object resolve(final Injectee injectee, final ServiceHandle<?> root) {
if (UserManagement.User.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, root);
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return true;
}
}
The supplier:
import java.util.function.Supplier;
public class BasicAuthSupplier implements Supplier<UserManagement.User> {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthSupplier.class);
public BasicAuthSupplier() {
}
#Override
public UserManagement.User get() {
// Dummy code to create the user.
return UserManagement.User.newUser().build();
}
}
And the API :
#Path ("/my-path")
#Consumes (APPLICATION_JSON)
#Produces (APPLICATION_JSON)
public class {
#GET
public Response getAll(#SdnBasicAuth final UserManagement.User user) {
// ... Check user rights
return Response.accepted().build();
}
}
Below was the working code on jersey 2.22
Annotation:
#Target ({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention (RetentionPolicy.RUNTIME)
#Documented
public #interface SdnBasicAuth {
}
Binder :
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Singleton;
public class BasicAuthBinder extends AbstractBinder {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthBinder.class);
private final UserManagement userManagement;
public BasicAuthBinder(final UserManagement userManagement) {
this.userManagement = userManagement;
}
#Override
protected void configure() {
bind(userManagement).to(UserManagement.class);
bind(BasicAuthFactory.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(BasicAuthResolver.class)
.to(new TypeLiteral<InjectionResolver<SdnBasicAuth>>() {
})
.in(Singleton.class);
}
}
Factory :
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.ServiceLocator;
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.model.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
#Singleton
public class BasicAuthFactory extends AbstractValueFactoryProvider {
// SLF4J Logger
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthFactory.class);
#Inject
private UserManagement userManagement;
#Inject
public BasicAuthFactory(MultivaluedParameterExtractorProvider mpep, ServiceLocator injector) {
super(mpep, injector, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> classType = parameter.getRawType();
if (parameter.getAnnotation(SdnBasicAuth.class) == null) {
LOG.debug("Not injecting user management provider.");
return null;
}
if (classType == null || (!classType.equals(UserManagement.User.class))) {
LOG.warn("IdentityParam annotation was not placed on correct object type; Injection might not work correctly!");
return null;
}
return new IdentityParamValueFactory(userManagement);
}
private static final class IdentityParamValueFactory extends AbstractContainerRequestValueFactory<UserManagement.User> {
private final UserManagement userManagement;
#Context
private ResourceContext context;
public IdentityParamValueFactory(final UserManagement userManagement) {
this.userManagement = userManagement;
}
public UserManagement.User provide() {
final HttpServletRequest request = context.getResource(HttpServletRequest.class);
// Dumb code to do the authorization stuff not related of our problem.
return UserManagement.User.newUser().build();
}
}
}
The resolver:
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
import javax.inject.Singleton;
#Singleton
public class BasicAuthResolver extends ParamInjectionResolver<SdnBasicAuth> {
public BasicAuthResolver() {
super(BasicAuthFactory.class);
}
}
Related
Am new to springboot and am trying to integrate Integration Tests using Rest-assured to test my Rest-Api.
Am getting NPE when injecting #Steps into SpringBoot Test.I'm introducing a step class to improve on re-usability code.This test runs well if the step method is in the IT-class.I tried #Component annotation but it didn't work
Step class
import net.thucydides.core.annotations.Step;
import org.apache.http.HttpStatus;
import static com.jayway.restassured.RestAssured.when;
public class StaffSteps {
protected static String BASE_STAFF_URL = "/api/v1a/staff/";
protected static Staff staff;
#Step
public StaffSteps getStaffMemberById(String id){
staff = when().get(BASE_STAFF_URL+id)
.then().assertThat()
.statusCode(HttpStatus.SC_OK)
.extract()
.as(Staff.class);
return this;
}
#Step
public Staff getStaff(){return staff;}
}
import net.thucydides.core.annotations.Steps;
import org.apache.http.HttpStatus;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays;
import java.util.List;
import static com.jayway.restassured.RestAssured.when;
#RunWith(SpringJUnit4ClassRunner.class)
public class StaffControllerIT extends BaseTest {
#Steps
private StaffSteps staffSteps;
#Before
public void setUp(){
}
#Test
public void getStaffMemberById(){
String id ="ff8081817049a34e017049a379320000";
Staff staff = staffSteps.getStaffMemberById(id).getStaff();
System.err.println(staff);
}
When i run this test, staffSteps is null.
Here is my dependency i used
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-core</artifactId>
<version>1.9.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
Please let me know if you need more information on this. Thanks
Solution using SpringRunner:
Annotate the steps with #Bean and #StepScope, and as a result, this object will share its lifetime with StepExecution.
public class StaffStepsConfig {
protected static String BASE_STAFF_URL = "/api/v1a/staff/";
protected static Staff staff;
#Bean
#StepScope
public StaffSteps getStaffMemberById(String id){
staff = when().get(BASE_STAFF_URL+id)
.then().assertThat()
.statusCode(HttpStatus.SC_OK)
.extract()
.as(Staff.class);
return this;
}
#Bean
#StepScope
public Staff getStaff(){return staff;}
}
In the Test class, the spring-batch-test dependency provides a set of useful helper methods and listeners that can be used to configure the Spring Batch context during testing.
#RunWith(SpringRunner.class)
//#SpringBatchTest
//#SpringBootTest
#EnableAutoConfiguration
#ContextConfiguration(classes = { StaffStepsConfig.class })
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class StaffControllerIT extends BaseTest {
#Autowired
private StaffSteps staffSteps;
#Before
public void setUp(){
}
#Test
public void getStaffMemberById(){
String id ="ff8081817049a34e017049a379320000";
Staff staff = staffSteps.getStaffMemberById(id).getStaff();
System.err.println(staff);
}
}
Note: This runner recognizes #SpringBootTest . I think the problem is in the way the test outcomes are generated. The steps are not being read by Serenity. Spring will inject #Autowired classes, and #serenity will inject #steps classes. I assume this happens because serenity and spring are creating components in different contexts .
required dependencies in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
<version>2.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.9.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<version>4.2.0.RELEASE</version>
<scope>test</scope>
</dependency>
I'm trying to mock a controller/resource including the jax-rs layer. The class has dependencies that need to be injected.
It however also has some String values that are injected using a qualifier interface.
Basically, I'm using JerseyTest to run a single controller and use HK2 for dependency injection. I created a ResourceConfig and registered a AbstractBinder to bind the injected classes.
This works fine for regular injected dependencies, but when the the additional #SomeQualifierInterface annotation is added, it crashes with the following error:
MultiException stack 1 of 3
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=String,parent=ThingsController,qualifiers={#com.company.SomeQualifierInterface()},position=-1,optional=false,self=false,unqualified=null,10035302)
...
MultiException stack 2 of 3
java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.company.ThingsController errors were found
...
MultiException stack 3 of 3
java.lang.IllegalStateException: Unable to perform operation: resolve on com.company.ThingsController
...
See the simplified full code example below:
Controller / Resource
import org.slf4j.Logger;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
#Path("/things")
public class ThingsController {
#Inject
private Logger log;
#Inject
#SomeQualifierInterface
private String injectedQualifierValue;
#GET
public Response getThings() {
log.info("getting things");
System.out.println("Injected value: " + injectedQualifierValue);
return Response.status(200).entity("hello world!").build();
}
}
Qualifier interface
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
#Qualifier
#Retention(RUNTIME)
#Target({ TYPE, METHOD, FIELD, PARAMETER })
public #interface SomeQualifierInterface { }
Producing service
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
#ApplicationScoped
public class SomeProducerService {
#Produces
#Dependent
#SomeQualifierInterface
public String getQualifierValue() {
return "some value!";
}
}
Test
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.slf4j.Logger;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MockedThingsControllerTest extends JerseyTest {
private Logger logMock = mock(Logger.class);
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(ThingsController.class);
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure() {
bind(logMock).to(Logger.class);
bind("some mocked value").to(String.class); // Doesn't work
bind(new SomeProducerService()).to(SomeProducerService.class); // Doesn't work
}
});
return resourceConfig;
}
#Test
public void doSomething() {
Response response = target("/things").request().get();
assertEquals(200, response.getStatus());
verify(logMock).info("getting things");
}
}
POM
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.27.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.28</version>
<scope>test</scope>
</dependency>
Solved!
First, use the AbstractBinder from org.glassfish.hk2.utilities.binding.AbstractBinder instead of org.glassfish.jersey.internal.inject.AbstractBinder.
Second, create a class that extends AnnotationLiteral and implements the interface.
Last, bind the value to a TypeLiteral and set the qualifiedBy to the AnnotationLiteral.
Full code:
import org.glassfish.hk2.api.AnnotationLiteral;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.slf4j.Logger;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import static junit.framework.TestCase.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MockedThingsControllerTest extends JerseyTest {
private Logger logMock = mock(Logger.class);
#Override
protected Application configure() {
ResourceConfig resourceConfig = new ResourceConfig(ThingsController.class);
resourceConfig.register(new AbstractBinder() {
#Override
protected void configure() {
bind(logMock).to(Logger.class);
bind("some mocked value").to(new TypeLiteral<String>() {}).qualifiedBy(new SomeQualifierLiteral());
}
});
return resourceConfig;
}
#Test
public void doSomething() {
Response response = target("/things").request().get();
assertEquals(200, response.getStatus());
verify(logMock).info("getting things");
}
static class SomeQualifierLiteral extends AnnotationLiteral<SomeQualifierInterface> implements SomeQualifierInterface {}
}
I'm trying to use Cucumber with TestFX but cant get any nodes from the application.
I've another class of TestFX that works fine and another class of Cucumber which also works fine. But I'm getting
org.loadui.testfx.exceptions.NoNodesFoundException: No nodes matched 'TextInputControl has text "Can you find this label"'.
TestFXBase :
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.stage.Stage;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.testfx.api.FxToolkit;
import org.testfx.framework.junit.ApplicationTest;
import java.util.concurrent.TimeoutException;
public class TestFXBase extends ApplicationTest {
private static boolean isHeadless = false;
#BeforeClass
public static void setupHeadlessMode() {
if(isHeadless){
System.setProperty("testfx.robot", "glass");
System.setProperty("testfx.headless", "true");
System.setProperty("prism.order", "sw");
System.setProperty("prism.text", "t2k");
System.setProperty("java.awt.headless", "true");
}
}
#Before
public void setUpClass() throws Exception {
ApplicationTest.launch(Main.class);
}
#Override
public void start(Stage stage) throws Exception {
stage.show();
}
#After
public void afterEachTest() throws TimeoutException {
FxToolkit.hideStage();
release(new KeyCode[]{});
release(new MouseButton[]{});
}
/* Helper method to retrieve Java FX GUI Components */
public <T extends Node> T find (final String query){
return (T) lookup(query).queryAll().iterator().next();
}
}
This is my testfx base class and my cucumber runner and stepdefs extends this.
StepDefs:
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import org.junit.Test;
public class MyStepdefs extends TestFXBase {
#Test
#Given("^That \"([^\"]*)\" Exists$")
public void thatExists(String arg0) throws Throwable {
rightClickOn("#rect");
}
#Test
#Then("^Which is \"([^\"]*)\"$")
public void whichIs(String arg0) throws Throwable {
System.out.println(arg0);
}
}
Runner:
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
#RunWith(Cucumber.class)
#CucumberOptions(plugin = { "pretty" })
public class MyRunner extends TestFXBase{}
Feature:
Feature: Do label texts exist?
Scenario: Can you find this label text
Given That "Can you find this label" Exists
Then Which is "great"
So, the parameter is passed but TestFX dont start application in my cucumber runner, just tries to find nodes. There is a class that extends TestFXBase and works perfectly. How can I solve this issue?
Edit: My dependencies are
<dependency>
<groupId>org.loadui</groupId>
<artifactId>testFx</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>org.jfxtras</groupId>
<artifactId>openjfx-monocle</artifactId>
<version>1.8.0_20</version>
</dependency>
<dependency>
<groupId>org.testfx</groupId>
<artifactId>testfx-core</artifactId>
<version>4.0.6-alpha</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.testfx</groupId>
<artifactId>testfx-junit</artifactId>
<version>4.0.6-alpha</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
Solution here is to move content of setupHeadlessMode and setUpClass methods to TestFXBase class initializer:
static {
if (isHeadless) {
System.setProperty("testfx.robot", "glass");
System.setProperty("testfx.headless", "true");
System.setProperty("prism.order", "sw");
System.setProperty("prism.text", "t2k");
System.setProperty("java.awt.headless", "true");
}
try {
ApplicationTest.launch(Main.class);
} catch (Exception e) {
// oh no
}
}
I try to use mock to verify method for serveral times.But I meet this problem.
org.mockito.exceptions.verification.TooLittleActualInvocations:
personDao.update(isA(com.zhaolu08.Person));
Wanted 3 times:
-> at com.zhaolu08.PersonServiceTest.testUpdate(PersonServiceTest.java:32)
But was 1 time:
while my code is:
package com.zhaolu08;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.eq;
public class PersonServiceTest {
private PersonDao mockDao;
private PersonService personService;
#Before
public void setUp() throws Exception {
//模拟PersonDao对象
mockDao = mock(PersonDao.class);
when(mockDao.getPerson(1)).thenReturn(new Person(1, "Person1"));
when(mockDao.update(isA(Person.class))).thenReturn(true);
personService = new PersonService(mockDao);
}
#Test
public void testUpdate() throws Exception {
boolean result = personService.update(1, "new name");
Assert.assertTrue("must true", result);
verify(mockDao, times(2)).getPerson(eq(1));
verify(mockDao, times(3)).update(isA(Person.class));
}
}
I can't find out the problem. I try some methods. They did not work.
My IDE is idea.
Maven pom is:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
</dependency>
I can't find out why it doesn't work. It is just a simple demo. It's too wired.
It seems due to the fact that you are expecting personDao.update to be invoked 3 times and actually in your method personService.update(1, "new name"); it is getting invoked only 1 time
I created a Jersey filter and I need it assigned to some resources (not all). Therefore, I'm using dynamic binding to achieve that.
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) {
Path resourcePath = resourceInfo.getResourceClass().getAnnotation(Path.class);
if (resourcePath != null && resourcePath.value().contains("/v2/"))
{
featureContext.register(MyFilter.class);
}
}
}
So I want this filter to be applied to all methods in those resources that contain a certain string in their paths. Some of those resources use sub-resource locators to define sub-resources. E.g.,
#Path("/v2/resource_path")
#Consumes({ ... })
#Produces({ ... })
class MyResource
{
#Path("/subresource_path")
public MySubResource getSubResource(#Context ResourceContext rc)
{
return rc.getResource(MySubResource.class);
}
}
Even though Jersey documentation claims
The configure method will be executed once for each resource method that is defined in the application.
the configure method in MyDynamicFeature shown above doesn't get called for getSubResource method of MyResource class at all. It does get called for all the rest of the methods in MyResource class though (which I omitted in the example).
Is there a way to make this work for sub-resources? I need my filter to be applied to MySubResource as well.
We use Jersey 2.21.
Check out this issue. I'm not sure that it is currently possible. If you add some logging in your feature to log the method and class, you will see that subresource methods are never traversed. As explained by Marek in the issue, it's because in order to handle this, the sub-resource locator method would need to be invoked, which it never is.
The only workaround is to use Name Binding instead. I've tested this and it works (see below). The idea is to make a custom annotation, and annotate the filter, the resource class, and sub-resource class you want filtered. For example
#NameBinding
#Target({METHOD, TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface SomeAnno {}
#SomeAnno
public class Filter implements ContainerRequestFilter {}
#SomeAnno
#Path("v2")
public class V2Resource {
#Path("sub")
public V2SubResource get() {
return new V2SubResource();
}
#SomeAnno
public static class V2SubResource {
#GET
public String get() { return "get"; }
}
}
The above would bind all the resource methods in V2Resource as well as V2SubResource.
Below is a complete example using Jersey Test Framework. Run it like any other JUnit test
UPDATE: Note that with the below tests, with the current (2.26) version of Jersey, the tests for the version 2 resource hangs because of the 1000 status code being returned in the filter. I guess Jersey doesn't like this. To fix this just change the status code in the filter to 500 and fix the test assertions accordingly to test for a 500 status code.
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.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.NameBinding;
import javax.ws.rs.Path;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Stack Overflow question http://stackoverflow.com/q/36878817/2587435
*
* Run this like any other JUnit test. Only one required test dependency
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*
* #author Paul Samsotha
*/
public class DynamicSubresourceTest extends JerseyTest {
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public static #interface Status1000 {}
#Provider
#Status1000
public static class Status1000Filter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context) throws IOException {
context.abortWith(Response.status(500).build());
}
}
#Path("v1")
public static class V1Resource {
#GET
public String get() {
return "v1";
}
#Path("sub")
public V1SubResource getSub() {
return new V1SubResource();
}
public static class V1SubResource {
#GET
public String get() {
return "v1subresource";
}
}
}
#Path("v2")
#Status1000
public static class V2Resource {
#GET
public String get() {
return "v2";
}
#Path("sub")
public V2SubResource getSub() {
return new V2SubResource();
}
#Status1000
public static class V2SubResource {
#GET
public String get() {
return "v2subresource";
}
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(V1Resource.class, V2Resource.class)
.property(ServerProperties.WADL_FEATURE_DISABLE, true)
.register(Status1000Filter.class)
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
#Test
public void should_return_1000_for_v2_resource_method() {
final Response response = target("v2").request().get();
assertThat(response.getStatus(), is(500));
}
#Test
public void should_return_1000_for_v2_subresource_locator() {
final Response response = target("v2/sub").request().get();
assertThat(response.getStatus(), is(500));
}
#Test
public void should_return_data_for_v1_resource_method() {
final Response response = target("v1").request().get();
assertThat(response.getStatus(), is(200));
assertThat(response.readEntity(String.class), is("v1"));
}
#Test
public void should_return_data_for_v1_subresource_locator() {
final Response response = target("v1/sub").request().get();
assertThat(response.getStatus(), is(200));
assertThat(response.readEntity(String.class), is("v1subresource"));
}
}