jaxrs override ContextResolver Provider - java

I'm using openapi generator to generate some template code for me, the generated code also include a default Jackson ObjectMapper.
here is the code of the generated provider
package xxx.gen.invoker;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() throws Exception {
objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.setDateFormat(new RFC3339DateFormat());
}
public ObjectMapper getContext(Class<?> arg0) {
return objectMapper;
}
}
Basically, I don't want to modify the generated code, is there any way I can create my own one with higher priority that can override the generated one?
I tried to write my customized one, but it seems that the generated one was picked up during runtime, so what's the resolution progress looks like when JAX RS pick up providers when there're multiple?
here is the code for my own customized provider
package xxx.service.util;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
this.mapper = createObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
private ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}
}

Related

Spring part of the Service will not be dynamic proxy, leading to the use of #Cacheable

I am using the version of SpringBooot 1.8. Using #Cacheable annotations in the Service layer does not cache the data. Through debugging, it is found that some of the Service objects in the Controller layer are proxied by cglib, but some of them are not. The wordings are similar in nature. Why does this happen and how should it be solved ?
ApplicationBoot:
#SpringBootApplication
#EnableCaching
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
Service:
#Service
#Slf4j
public class MovieServiceImpl extends ServiceImpl<MovieMapper,Movie> implements MovieService {
#Resource
private PlayService playService;
#Resource
private DownloadService downloadService;
#Resource
private PlayApiService playApiService;
#Resource
private AreaService areaService;
#Resource
private CategoryService categoryService;
#Resource
private IncrementRecordsService inService;
#Override
#Cacheable(value = "nowIsAndFuck")
public Movie nowIsAndFuck() {
log.debug("movie-test 执行查询:"+1);
return this.selectById(1);
}
}
Cache Config:
package com.aikanyingshi.web.core.config.cache;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
#Configuration
#Component
public class RedisConfig extends CachingConfigurerSupport {
#Resource
private RedisConfigProperties redisConfigProperties;
#Bean
public RedisConnectionFactory redisConnectionFactory(){
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisConfigProperties.getHost());
factory.setPort(redisConfigProperties.getPort());
if(redisConfigProperties.getPassword()!=null){
factory.setPassword(redisConfigProperties.getPassword());
}
return factory;
}
#Bean
public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,String> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
setSerializer(template);
return template;
}
#Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(10);
return cacheManager;
}
private void setSerializer(RedisTemplate<String, String> template) {
Jackson2JsonRedisSerializer serializer
= new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(om);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
}
#Override
public KeyGenerator keyGenerator() {
return (o, method, objects) -> {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(":");
sb.append(method);
for (Object object : objects) {
sb.append(":");
sb.append(object.toString());
}
return sb.toString();
};
}
}

How to serialize nested ObjectId to String with Jackson?

There are many questions concerning conversion from ObjectId to String with jackson. All answers suggest either creating own JsonSerializer<ObjectId> or annotating the ObjectId field with #JsonSerialize(using = ToStringSerializer.class).
However, I have a map that sometimes contains ObjectIds, i.e.:
class Whatever {
private Map<String, Object> parameters = new HashMap<>();
Whatever() {
parameters.put("tom", "Cat");
parameters.put("jerry", new ObjectId());
}
}
I want jackson to convert it to:
{
"parameters": {
"tom": "cat",
"jerry": "57076a6ed1c5d61930a238c5"
}
}
But I get:
{
"parameters": {
"tom": "cat",
"jerry": {
"date": 1460103790000,
"machineIdentifier": 13747670,
"processIdentifier": 6448,
"counter": 10631365,
"time": 1460103790000,
"timestamp": 1460103790,
"timeSecond": 1460103790
}
}
}
I have registered the conversion (in Spring) with
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder
.serializerByType(ObjectId.class, new ToStringSerializer());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
converters.add(converter);
}
}
And the first-level ObjectIds are converted correctly. How to make jackson convert also the nested ones? Do I have to write custom converter for this map?
Keep in mind that this Map can be nested multiple times (i.e. contain another maps). I just want to convert ObjectId to String whenever jackson sees it.
I suppose that you are taking about org.bson.types.ObjectId from org.springframework.boot:spring-boot-starter-data-mongodb. Your code works perfectly fine for me. 1 thing i can see is that you don't show #Configuration annotation above WebappConfig.
Here is my demo project, can you test it on yours setup?
Application.java
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.bson.types.ObjectId;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Configuration
public static class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder
.serializerByType(ObjectId.class, new ToStringSerializer());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
converters.add(converter);
}
}
#RestController
public static class MyRestController {
#ResponseBody
#RequestMapping("/")
public Whatever method() {
return new Whatever();
}
}
public static class Whatever {
private Map<String, Object> parameters = new HashMap<>();
public Whatever() {
parameters.put("tom", "Cat");
parameters.put("jerry", new ObjectId());
}
public Map<String, Object> getParameters() {
return parameters;
}
public void setParameters(Map<String, Object> parameters) {
this.parameters = parameters;
}
}
}
Responce from 127.0.0.1:8080
{
"parameters": {
"tom": "Cat",
"jerry": "5709df1cf0d9550b4de619d2"
}
}
Gradle:
dependencies {
compile("org.springframework.boot:spring-boot-starter-data-mongodb")
compile("org.springframework.boot:spring-boot-starter-web")
}
thanks varren's answer, it works fine in springMvc's older version.
but since 5.0, WebMvcConfigurerAdapter was deprecated.
solution:
may not work solution: we can implements WebMvcConfigurer directly for mvc config. but some config may not work, because WebMvcConfigurationSupport's priority is higher.
suggest solution: we can extends WebMvcConfigurationSupport directly. imply configureMessageConverters method can add all kinds of custom HttpMessageConverters we need, and it can works fine before default converters.
spring framework is a amazing framework, I need to look it deeper after I got time.(●'◡'●)

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

Override Jackson Object Mapper properties on Websphere 8.5.5 using Apache Wink

We are using IBM(s) bundled Apache Wink to offer JAXRS endpoints for our application. We are coding towards Websphere 8.5.5. Since we are servlet 3.0 compliant we use the 'programmatic' way of configuring the JaxRS application, meaning no entries in web.xml and we rely on class scanning for annotated jax rs resources. In general it works fine.
#ApplicationPath("/api/v1/")
public class MyApplication extends Application{
This version of Websphere along with Apache Wink, uses Jackson 1.6.x for JSON de/serialization and in general it works well. We would like though to change some of the default values of the Object Mapper
So we have defined a customer context resolver, where just alter some of the se/deserialzation properties.
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class CustomJackssonConverter implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public AibasJackssonConverter() {
defaultObjectMapper = createDefaultMapper();
}
...
mapper.getSerializationConfig().set(SerializationConfig.Feature.INDENT_OUTPUT, true);
During JAX-RS calls we can see that the container registers the new Provider, with no errors
The problem is that , the Configuration is not 'followed', from the logs I can see that the Wink Engine is looking up a WinkJacksonProvider, which in turn..returns a JacksonProvider that is following the Jackson(s) default values?
Is there a way to just change this default value?
I have tried to change the implementation of the Application object as indicated here, in order to configure Providers programmatically, but it did not work.
http://www.ibm.com/developerworks/java/library/wa-aj-jackson/index.html
Any hints or tips?
Many thanks
I solved this problem by just implementing a MessageBodyWriter class, like this:
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
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 DefaultMessageBodyWriter implements MessageBodyWriter<Object> {
#Override
public long getSize(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
#Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true;
}
#Override
public void writeTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
mapper.writeValue(entityStream, object);
}
}
Every time a JSON serialization is requested, this class comes into action and finally its writeTo method is invoked.
Here SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS is turned off, as requested by WebSphere.
I found working solution with ContextResource.
You need Jackson JAX-RS provider dependencies. Maven example:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.9.7</version>
</dependency>
Next you can implement ContextResolver
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = createObjectMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
private ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
// some mapper configurations
return mapper;
}
}
And finaly you must register JacksonJaxbJsonProvider and your ContextResolver in your Application class.
public class RestApplicationConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
resources.add(JacksonJaxbJsonProvider.class);
resources.add(JacksonConfig.class);
// Add other resources
return resources;
}
}
I was using liberty profile server 20.0.0.0 and eclipselink 2.7.6. The MoxyJsonProvider from eclipselink is used by JAXRS by default and it was throwing "A Cycle is detected" error. I wanted to override the default JAXB Moxy provider with my ObjectMapper or Jackson Json Provider.
public Set<Class<?>> getClasses() did not work. I had to create another class file to create public class JaxbJsonProvider extends JacksonJaxbJsonProvider and annotate it as #Provider. This solution worked for me.
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class JaxbJsonProvider extends JacksonJaxbJsonProvider {
}
and the other file is
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Provider
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public JacksonObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
public static ObjectMapper createDefaultMapper() {
log.info(String.format("Registering ObjectMapper modules"));
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNRESOLVED_OBJECT_IDS);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.configure(MapperFeature.USE_GETTERS_AS_SETTERS, true);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return mapper;
}
}
Restart you liberty profile server and REST APIs would work fine. Note that #Slf4j is merely used for logging statement and mapper config options are something that you can skip. Liberty recommends to use mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);. All others are as per your project settings.
I usse MOXy instead of Jackson in WAS v8.0.0.x.
To override Jackson, I implement my Application class as so:
#Named
#ApplicationScoped
#ApplicationPath("/resources/")
public class WinkApplication extends Application implements Serializable {
private static final long serialVersionUID = 1L;
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(WinkResource.class);
classes.add(WinkMOXyJsonProvider.class);
classes.add(WinkResponseException.class);
classes.add(WinkResponseExceptionMapper.class);
return classes;
}
}
However, I've noticed that WAS seems to ignore following annotation:
#ApplicationPath("/resources/")
So, I've resorted to using web.xml:
<!-- Wink Servlet -->
<servlet>
<description>JAX-RS Tools Generated - Do not modify</description>
<servlet-name>JAX-RS Servlet</servlet-name>
<servlet-class>com.ibm.websphere.jaxrs.server.IBMRestServlet</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.company.team.project.webservices.config.WinkApplication</param-value>
</init-param>
<!-- <init-param>
<param-name>propertiesLocation</param-name>
<param-value>/WEB-INF/my-wink-properties.properties</param-value>
</init-param> -->
<load-on-startup>1</load-on-startup>
<enabled>true</enabled>
<async-supported>false</async-supported>
</servlet>
<!-- Wink Servlet Mapping -->
<servlet-mapping>
<servlet-name>JAX-RS Servlet</servlet-name>
<url-pattern>/resources/*</url-pattern>
</servlet-mapping>
The point is, since WAS or Wink seems to ignore the Application implementation when using the ApplicationPath annotation, Wink loads the default Application class, which uses Jackson by default.
And yes, I've read documentation and even watched IBM videos online that mention that #ApplicationPath allows you to avoid XML config, however this problem seems to be a bug.
UPDATE:
An alternative approach could be what David Blevins has mentioned in another SO post.
Check out the section Using JAX-RS

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);

Categories

Resources