not getting all the properties from Inherited object during REST POST call - java

I am facing some issue with REST POST call. I am consuming JSON object.
Lets suppose I have 2 classes below
Class Parent extends Serializable
{
private static final long serialVersionUID = 1L;
#Key
protected String Market;
#Key
protected String Symbol;
}
Class Child extends Parent
{
private static final long serialVersionUID = -4252878751127065794L;
private Double strikePrice;
private String optionType;
}
#POST
#Path("/addProduct")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response addProduct(Child child) {
--somecode--
}
Now I am passing JSON through Postman with all the properties which include all the properties from Parent class also. But when I am debugging this child object it is not giving parent properties. Do I need to use GSON or any other lib for this ?
JSON
{
"Market": "BSE",
"Symbol": "Infosys",
"strikePrice": 100,
"optionType": "Put"
}
I can see only strikePrice and optionType with data.

I found one solution. We need to add MessageBodyReader for Custom Response.
I dont know the best practice but it will work without issue.
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Scanner;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import common.SerializeUtil;
#Provider
public class CustomMessageBodyReader implements MessageBodyReader<Child> {
#Context
private Providers providers;
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
// TODO Auto-generated method stub
return Parent.class.isAssignableFrom(type);
}
#Override
public Child readFrom(Class<Child> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
String entity = toString(entityStream);
Child child = SerializeUtil.fromString(entity, Child.class);
return child;
}
public static String toString(InputStream inputStream) {
return new Scanner(inputStream, "UTF-8")
.useDelimiter("\\A").next();
}
}
You also need to register this to your service config like below
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(CustomMessageBodyReader.class);
Please feel free to provide best solution.

Related

Jersey ContextResolver GetContext() called only once

I have the following ContextResolver<ObjectMapper> implementation, which based on the query params should return the corresponding JSON mapper (pretty/DateToUtc/Both):
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JsonMapper implements ContextResolver<ObjectMapper> {
private ObjectMapper prettyPrintObjectMapper;
private ObjectMapper dateToUtcMapper;
private ObjectMapper bothMapper;
private UriInfo uriInfoContext;
public JsonMapper(#Context UriInfo uriInfoContext) throws Exception {
this.uriInfoContext = uriInfoContext;
this.prettyPrintObjectMapper = new ObjectMapper();
this.prettyPrintObjectMapper.enable(SerializationFeature.INDENT_OUTPUT);
this.dateToUtcMapper = new ObjectMapper();
this.dateToUtcMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
this.bothMapper = new ObjectMapper();
this.bothMapper.enable(SerializationFeature.INDENT_OUTPUT);
this.bothMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
#Override
public ObjectMapper getContext(Class<?> objectType) {
System.out.println("hi");
try {
MultivaluedMap<String, String> queryParameters = uriInfoContext.getQueryParameters();
Boolean containsPretty = queryParameters.containsKey("pretty");
Boolean containsDate = queryParameters.containsKey("date_to_utc");
Boolean containsBoth = containsPretty && containsDate;
if (containsBoth) {
System.out.println("Returning containsBoth");
return bothMapper;
}
if (containsDate) {
System.out.println("Returning containsDate");
return dateToUtcMapper;
}
if (containsPretty) {
System.out.println("Returning pretty");
return prettyPrintObjectMapper;
}
} catch(Exception e) {
// protect from invalid access to uriInfoContext.getQueryParameters()
}
System.out.println("Returning null");
return null; // use default mapper
}
}
And the following Main Application:
private Server configureServer() {
ObjectMapper mapper = new ObjectMapper();
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.packages(Calculator.class.getPackage().getName());
resourceConfig.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
// #ValidateOnExecution annotations on subclasses won't cause errors.
resourceConfig.property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
resourceConfig.register(JacksonFeature.class);
resourceConfig.register(JsonMapper.class);
resourceConfig.register(AuthFilter.class);
ServletContainer servletContainer = new ServletContainer(resourceConfig);
ServletHolder sh = new ServletHolder(servletContainer);
Server server = new Server(serverPort);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.addServlet(sh, "/*");
server.setHandler(context);
return server;
}
However, getContext() function is only called once for the entire server lifetime, only on the first request. The whole idea of this class is to determine on runtime what is the mapper based on the url parameters.
UPDATE
getContext() is called once for each uri path. For example, http://server/path1?pretty=true will yield pretty output for all request to /path1, regardless of thier future pretty queryParam. A call to path2 will call getContext again, but not to future path2 calls.
UPDATE2
Well, it seems like the GetContext is called for each class once, and caches it for that specific class. This is why it expects a class as parameter. So it seems like #LouisF is right, and the objectMapper isn't suited for conditional serialization. However, the ContainerResponseFilter alternative is partially working, but not exposing ObjectMapper features, such as converting dates to UTC. So I'm quite puzzled right now on what is the most appropriate solution for conditional serialization.
SOLVED
With the help of #LoisF, I've managed to have conditional serialization, using ContainerResponseFilter. I havn't use ContextResolver. Below is the working example:
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.jaxrs.cfg.EndpointConfigBase;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterInjector;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterModifier;
/**
* Created by matt on 17/01/2016.
*/
#Provider
public class ResultTransformer implements ContainerResponseFilter {
public static final String OUTPUT_FORMAT_HEADER = "X-Output-Format";
public static final ObjectMapper MAPPER = new ObjectMapper();
public static class OutputFormat {
Boolean pretty = true;
Boolean dateAsTimestamp = false;
public Boolean getPretty() {
return pretty;
}
public void setPretty(Boolean pretty) {
this.pretty = pretty;
}
#JsonProperty("date_as_timestamp")
public Boolean getDateAsTimestamp() {
return dateAsTimestamp;
}
public void setDateAsTimestamp(Boolean dateAsTimestamp) {
this.dateAsTimestamp = dateAsTimestamp;
}
}
#Override
public void filter(ContainerRequestContext reqCtx, ContainerResponseContext respCtx) throws IOException {
String outputFormatStr = reqCtx.getHeaderString(OUTPUT_FORMAT_HEADER);
OutputFormat outputFormat;
if (outputFormatStr == null) {
outputFormat = new OutputFormat();
} else {
try {
outputFormat = MAPPER.readValue(outputFormatStr, OutputFormat.class);
ObjectWriterInjector.set(new IndentingModifier(outputFormat));
} catch (Exception e) {
e.printStackTrace();
ObjectWriterInjector.set(new IndentingModifier(new OutputFormat()));
}
}
}
public static class IndentingModifier extends ObjectWriterModifier {
private OutputFormat outputFormat;
public IndentingModifier(OutputFormat outputFormat) {
this.outputFormat = outputFormat;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpointConfigBase, MultivaluedMap<String, Object> multivaluedMap, Object o, ObjectWriter objectWriter, JsonGenerator jsonGenerator) throws IOException {
if(outputFormat.getPretty()) jsonGenerator.useDefaultPrettyPrinter();
if (outputFormat.dateAsTimestamp) {
objectWriter = objectWriter.with(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
} else {
objectWriter = objectWriter.without(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
return objectWriter;
}
}
}
You should consider performance.
With your solution, you are creating a new ObjectMapper instance with each request. This is quite heavy!!! I found ObjectMapper creation as main performance stopper during a JProfile measurement.
Not sure if just having 2 static members for pretty / non-pretty is a sufficient solution regarding thread-safety. You need to take care of the mechanism used by the JAX-RS framework in order to cache the ObjectMapper, in order to not have any side-effects.
If you want it by request, you need it to be evaluated for each call. What I would suggest here is to move this logic in a dedicated component and do something as follow :
#GET
public Response demo(#Context final UriInfo uriInfoContext, final String requestBody) {
final ObjectMapper objectMapper = objectMapperResolver.resolve(uriInfoContext.getQueryParameters());
objectMapper.readValue(requestBody, MyClass.class);
...
}
where objectMapperResolver encapsulates the logic of choosing the right ObjectMapper depending on the query parameters

Customize Json Output with Jersey and Jaxb

I am trying to create a simple web service which outputs using json, but am not getting the desired Json output.
POJO:
package com.rest.resource;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Track implements Serializable
{
#XmlElement
String singer = "ABC";
#XmlElement
String title = "XYZ";
}
Service:
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBException;
import com.rest.resource.Track;
#Path("/json/metallica")
public class JSONService
{
#POST
#Path("/post")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Track createTrackInJSON(final Track track)
{
return track;
}
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final Track track = new Track();
return Response.status(201).entity(track).build();
}
}
On /get I get
{"singer":"ABC","title":"XYZ"}
but I want "track": {"singer":"ABC","title":"XYZ"}
I am unable yo print the root element.
I tried using a CustomJAXBContextResolver class but did not work for me? Can anyone give an example of the same?
If you want to use the ContextResolver, you'd need to use the JSONConfiguration and switch the JSON Notation. You could do that by adding a class like this:
#Provider
public class MyJAXBContextProvider implements ContextResolver<JAXBContext> {
private JSONJAXBContext trackCtx;
public MyJAXBContextProvider() throws JAXBException {
trackCtx = new JSONJAXBContext(JSONConfiguration.mappedJettison().build(), Track.class);
}
public JAXBContext getContext(Class<?> type) {
if(type == Track.class) {
return trackCtx;
}
return null;
}
}
Adding that class produced this for me:
{"track":{"singer":"ABC","title":"XYZ"}}
For more info check out the Jersey Docs
You'd have to wrap Track with another object:
public class TrackWrapper {
Track track;
TrackWrapper(Track track) {
this.track=track;
}
}
and return an instance of TrackWrapper,
#GET
#Path("/get")
#Produces(MediaType.APPLICATION_JSON)
public Response getTrackInJSON() throws JAXBException
{
final TrackWrapper trackWrapper = new TrackWrapper(new Track());
return Response.status(201).entity(trackWrapper).build();
}
}
and just in case, if you're gonna use JSON only you don't need the JAXB annotations.

Jax-rs json pretty output

in Java when i use the
#Produces("application/json")
annotation the output is not formated into human readable form. How do i achive that?
Just for the record, if you want to enable the pretty output only for some resources you can use the #JacksonFeatures annotation on a resource method.
Here is example:
#Produces(MediaType.APPLICATION_JSON)
#JacksonFeatures(serializationEnable = { SerializationFeature.INDENT_OUTPUT })
public Bean resource() {
return new Bean();
}
This is how you can properly do conditional pretty/non-pretty json output based on presence of "pretty" in query string.
Create a PrettyFilter that implements ContainerResponseFilter, that will be executed on every request:
#Provider
public class PrettyFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext reqCtx, ContainerResponseContext respCtx) throws IOException {
UriInfo uriInfo = reqCtx.getUriInfo();
//log.info("prettyFilter: "+uriInfo.getPath());
MultivaluedMap<String, String> queryParameters = uriInfo.getQueryParameters();
if(queryParameters.containsKey("pretty")) {
ObjectWriterInjector.set(new IndentingModifier(true));
}
}
public static class IndentingModifier extends ObjectWriterModifier {
private final boolean indent;
public IndentingModifier(boolean indent) {
this.indent = indent;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpointConfigBase, MultivaluedMap<String, Object> multivaluedMap, Object o, ObjectWriter objectWriter, JsonGenerator jsonGenerator) throws IOException {
if(indent) jsonGenerator.useDefaultPrettyPrinter();
return objectWriter;
}
}
}
And pretty much that's it!
You will need to ensure that this class gets used by Jersey by either automated package scanning or registered manually.
Spent few hours trying to achieve that and found that no-one has published a ready-to-use solution before.
Create this class anywhere in your project. It will be loaded on deployment. Notice the .configure(SerializationConfig.Feature.INDENT_OUTPUT, true); which configures the mapper to format the output.
For Jackson 2.0 and later, replace the two .configure() lines with these:
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(SerializationFeature.INDENT_OUTPUT, true);
And change your imports accordingly.
package com.secret;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
/**
*
* #author secret
*/
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper;
public JacksonContextResolver() throws Exception {
this.objectMapper = new ObjectMapper();
this.objectMapper
.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
}
#Override
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}
Bear in mind that formatting has a negative effect on performance.
If you are using Spring, then you can globally set the property
spring.jackson.serialization.INDENT_OUTPUT=true
More info at https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html
Building on helpful DaTroop's answer, here is another version which allows choosing between optimized json and formatted json based on the absence or presence of a "pretty" parameter :
package test;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper prettyPrintObjectMapper;
private UriInfo uriInfoContext;
public JacksonContextResolver(#Context UriInfo uriInfoContext) throws Exception {
this.uriInfoContext = uriInfoContext;
this.prettyPrintObjectMapper = new ObjectMapper();
this.prettyPrintObjectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
}
#Override
public ObjectMapper getContext(Class<?> objectType) {
try {
MultivaluedMap<String, String> queryParameters = uriInfoContext.getQueryParameters();
if(queryParameters.containsKey("pretty")) {
return prettyPrintObjectMapper;
}
} catch(Exception e) {
// protect from invalid access to uriInfoContext.getQueryParameters()
}
return null; // use default mapper
}
}
If you are using the jersey-media-json-binding dependency, which uses Yasson (the official RI of JSR-367) and JAVAX-JSON, you can introduce pretty printing as follows:
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
public class RandomConfig implements ContextResolver<Jsonb> {
private final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withFormatting(true));
public RandomConfig() { }
#Override
public Jsonb getContext(Class<?> objectType) {
return jsonb;
}
}
Alternative for Jersey 1.x:
org.codehaus.jackson.map.ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);

rendering JSON String as JSON Using JAXB within jersey

Given a class like this:
#XmlRootElement
public class MyClass {
private Boolean flag1;
private String json;
...
}
Can I add an annotation that will cause the JSON to become part of the rendered JSON without being escaped as a string?
For example, if the object has flag1=true and json="{"a":5}" I want to get:
{"flag":true,"json":{"a":5}}
instead of:
{"flag":true,"json":"{\"a\":5}"}
You can write your own marshaller to do so.
You can use a Provider to change default behaviour of JSONJAXBContext. See http://jersey.java.net/nonav/documentation/latest/user-guide.html#d4e865
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
#Provider
public class CustomWoodwingOutputJSONContextProvider implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = { MyClass.class };
public CustomWoodwingOutputJSONContextProvider() throws JAXBException {
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (int i = 0; i < this.types.length; i++)
if (this.types[i].equals(objectType))
return context;
return null;
}
}

Injecting into a Jersey Resource class

I did try going through the following links
How to wire in a collaborator into a Jersey resource?
and
Access external objects in Jersey Resource class
But still i am unable to find a working sample which shows how to inject into a Resource class.
I am not using Spring or a web container.
My Resource is
package resource;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/something")
public class Resource
{
#MyResource
Integer foo = null;
private static String response = "SampleData from Resource";
public Resource()
{
System.out.println("...constructor called :" + foo);
}
#Path("/that")
#GET
#Produces("text/plain")
public String sendResponse()
{
return response + "\n";
}
}
My Provider is
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Integer>
{
#Override
public ComponentScope getScope()
{
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Integer arg2)
{
return new Injectable<Object>()
{
#Override
public Object getValue()
{
return new Integer(99);
}
};
}
}
My EndpointPublisher is
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
class EndpointPublisher
{
public static void main(final String[] args)
{
final String address = "http://localhost:8080/";
final Map<String, String> config = new HashMap<String, String>();
config.put("com.sun.jersey.config.property.packages", "resource");
try
{
GrizzlyWebContainerFactory.create(address, config);
System.out.println("server started ....." + address);
callGet();
}
catch (final Exception e)
{
e.printStackTrace();
}
}
public static void callGet()
{
Client client = null;
ClientResponse response = null;
client = Client.create();
final WebResource resource =
client.resource("http://localhost:8080/something");
response = resource.path("that")
.accept(MediaType.TEXT_XML_TYPE, MediaType.APPLICATION_XML_TYPE)
.type(MediaType.TEXT_XML)
.get(ClientResponse.class);
System.out.println(">>>> " + response.getResponseDate());
}
}
My annotation being
#Retention(RetentionPolicy.RUNTIME)
public #interface MyResource
{}
But when i execute my EndpointPublisher i am unable to inject foo!!
Your InjectableProvider is not implemented correctly. The second type parameter should not be the type of the field you are trying to inject - instead it should be the context - either java.lang.reflect.Type class or com.sun.jersey.api.model.Parameter class. In your case, you would use Type. So, your InjectableProvider implementation should look as follows:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import java.lang.reflect.Type;
#Provider
public class MyResourceProvider implements InjectableProvider<MyResource, Type> {
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public Injectable getInjectable(final ComponentContext arg0, final MyResource arg1, final Type arg2) {
if (Integer.class.equals(arg2)) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
} else {
return null;
}
}
}
There is a helper class for per-request injectable providers (PerRequestTypeInjectableProvider) as well as singleton injectable providers (SingletonTypeInjectableProvider), so you can further simplify it by inheriting from that:
package resource;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;
#Provider
public class MyResourceProvider extends PerRequestTypeInjectableProvider<MyResource, Integer> {
public MyResourceProvider() {
super(Integer.class);
}
#Override
public Injectable<Integer> getInjectable(ComponentContext ic, MyResource a) {
return new Injectable<Integer>() {
#Override
public Integer getValue() {
return new Integer(99);
}
};
}
}
Note that for these helper classes the second type parameter is the type of the field.
And one more thing - the injection happens after the constructor is called, so the constructor of your resource will still print out ...constructor called :null, but if you change your resource method to return foo, you'll see the response you'll get will be 99.
This solution works well and I wanted to share what I found to enable CDI on jersey resources.
Here is the simplest bean ever :
package fr.test;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
#RequestScoped
public class Test {
private int i;
#PostConstruct
public void create() {
i = 6;
}
public int getI() {
return i;
}
}
In your resource class, we just inject this bean, as we would do in a any normal context :
package fr.test;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
#Path("/login")
public class LoginApi {
#Inject
private Test test;
#GET
#Produces("text/plain")
public String getIt() {
return "Hi there!" + test;
}
}
And here is the key. We define a Jersey "InjectionProvider" which will be responsible of beans' resolution :
package fr.test;
import javax.inject.Inject;
import java.lang.reflect.Type;
import javax.ws.rs.ext.Provider;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import fr.xxxxxxxxxx.ApplicationBeans;
#Provider
public class InjectionProvider implements InjectableProvider<Inject, Type> {
public ComponentScope getScope() {
// CDI will handle scopes for us
return ComponentScope.Singleton;
}
#Override
public Injectable<?> getInjectable(ComponentContext context,
Inject injectAnno, Type t) {
if (!(t instanceof Class))
throw new RuntimeException("not injecting a class type ?");
Class<?> clazz = (Class<?>) t;
final Object instance = ApplicationBeans.get(clazz);
return new Injectable<Object>() {
public Object getValue() {
return instance;
}
};
}
}
InjectableProvider is typed with the kind of annotation we are handling, and the context type (here, normal java type)
ApplicationBeans is just a simple helper for bean resolution. Here is its content :
package fr.xxxxxxxxxx;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import fr.xxxxxxxxxxxxx.UnexpectedException;
/**
* Gives direct access to managed beans - Designed to be used from unmanaged code
*
* #author lgrignon
*
*/
#ApplicationScoped
public class ApplicationBeans
{
protected static ApplicationBeans instance;
#Inject
private BeanManager beanManager;
/**
* Gets instance
*
* #return Instance from managed environment
*/
public static ApplicationBeans instance()
{
if (instance == null)
{
BeanManager beanManager;
InitialContext ctx = null;
try
{
ctx = new InitialContext();
beanManager = (BeanManager)ctx.lookup("java:comp/BeanManager");
}catch(NamingException e)
{
try
{
beanManager = (BeanManager)ctx.lookup("java:app/BeanManager");
}catch(NamingException ne)
{
throw new UnexpectedException("Unable to obtain BeanManager.", ne);
}
}
instance = getBeanFromManager(beanManager, ApplicationBeans.class);
}
return instance;
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public static <T> T get(final Class<T> beanType, Annotation... annotations)
{
return instance().getBean(beanType, annotations);
}
/**
* Gets bean instance from context
*
* #param <T>
* Bean's type
* #param beanType
* Bean's type
* #param annotations
* Bean's annotations
* #return Bean instance or null if no
*/
public <T> T getBean(final Class<T> beanType, Annotation... annotations)
{
return getBeanFromManager(beanManager, beanType, annotations);
}
#SuppressWarnings("unchecked")
private static <T> T getBeanFromManager(BeanManager beanManager, final Class<T> beanType, Annotation... annotations)
{
Set<Bean<?>> beans = beanManager.getBeans(beanType, annotations);
if (beans.size() > 1)
{
throw new UnexpectedException("Many bean declarations found for type %s (%s)", beanType.getSimpleName(), beansToString(beans));
}
if (beans.isEmpty())
{
throw new UnexpectedException("No bean declaration found for type %s", beanType.getSimpleName());
}
final Bean<T> bean = (Bean<T>)beans.iterator().next();
final CreationalContext<T> context = beanManager.createCreationalContext(bean);
return (T)beanManager.getReference(bean, beanType, context);
}
private static String beansToString(Collection<Bean<?>> beans)
{
String[] beansLabels = new String[beans.size()];
int i = 0;
for (final Bean<?> bean : beans)
{
beansLabels[i++] = bean.getName();
}
return Arrays.toString(beansLabels);
}
}
Hope this will help those who want to enable CDI injection in their Jersey resources.
Bye !

Categories

Resources