I have a rest API (PUT verb) which accepts both request body and path params:
Ex:
curl --data {a:1, b:2} -X PUT "https://example.com/users/{username}/address/{addressname}"
I am trying to fetch both request body and path param in one POJO
Response myAPI(@BeanParam Users user){
system.out.println(user.username);
system.out.println(user.a);
Users class
public class Users{
@PathParam(username)
private String userName;
......
private String a;
......
}
But I am getting value of user.a as null.
How to parse both request body and param in same class?
You can do this with a custom annotation and an InjectionResolver
. What the InjectionResolver
does is allow you to create a custom injection point with your own annotation. So you could do something like
public class Users {
@PathParam(username)
private String userName;
@Body
private String a;
}
When implementing the InjectionResolver
, you would grab the actual body from the ContainerRequest
using the ContainerRequest#readEntity(Class)
method. You would determine the Class
to pass by doing some reflection on the Field
that you can obtain inside the InjectionResolver
. Below is a complete example using Jersey Test Framework. Run it 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.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
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.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
/**
* Only one required dependency to run this test. Note this is using 2.25.1.
* If you are using 2.26 or later, the implementation will be different,
* and this will not work. You need to use the Jersey packaged `InjectionResolver`,
* not the HK2 one. And you will also need to bind that `InjectionResolver`
* to `GenericType` instead of `TypeLiteral` in the `AbstractBinder#configure()`.
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>2.25.1</version>
* <scope>test</scope>
* </dependency>
*/
public class BeanParamTest extends JerseyTest {
@Path("test")
@Consumes("application/json")
@Produces("application/json")
public static class TestResource {
@POST
@Path("{username}")
public String post(@BeanParam ModelBean bean) {
return bean.toString();
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(new AbstractBinder() {
@Override
protected void configure() {
bind(BodyInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Body>>() {})
.in(Singleton.class);
}
});
}
@Test
public void testIt() {
final Response res = target("test/peeskillet")
.request()
.post(Entity.json("{\"foo\":\"bar\"}"));
System.out.println(res.readEntity(String.class));
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Body {}
public static class ModelBean {
@PathParam("username")
private String username;
@Body
private String body;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
@Override
public String toString() {
return "ModelBean{" +
"username='" + username + '\'' +
", body='" + body + '\'' +
'}';
}
}
public static class BodyInjectionResolver implements InjectionResolver<Body> {
@Inject
private javax.inject.Provider<ContainerRequest> requestProvider;
@Override
public Object resolve(Injectee injectee, ServiceHandle<?> serviceHandle) {
if (injectee.getParent().isAnnotationPresent(Body.class)) {
AnnotatedElement parent = injectee.getParent();
if (parent instanceof Field) {
Class<?> entityType = ((Field) parent).getType();
return requestProvider.get().readEntity(entityType);
}
}
return null;
}
@Override
public boolean isConstructorParameterIndicator() {
return false;
}
@Override
public boolean isMethodParameterIndicator() {
return false;
}
}
}