Override Jackson Object Mapper properties on Websphere 8.5.5 using Apache Wink
Asked Answered
N

4

8

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

Nuncia answered 5/5, 2014 at 8:49 Comment(2)
Have you tried the WebSphere forum? ibm.biz/websphere-forumInspirational
I can't get this to work either. When I include a JacksonJsonProvider in Application getClasses(). Websphere will use Jackson. But there's no way to customize Jackson. If I instead include a custom configured JacksonJsonProvider in Application getSingletons(). Websphere won't use it. No Response objects (or their payloads) are serialized. REST method that should return a response body return nothing. (Life is so much easier with TomcatEE.)Paradrop
L
3

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.

Leatri answered 18/12, 2015 at 19:8 Comment(0)
S
3

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;
    }
}
Supercool answered 2/11, 2018 at 11:36 Comment(5)
I used this to help me include null properties in JSON responses. Thanks!Misgive
This looks exactly like what the OP was doing but didn't work for him. What's different about this?Sitwell
The difference is that you have to register ContextResolver and JacksonJaxbJsonProvider in Application.Supercool
This solution worked for me. But 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 @ProviderTwentieth
I tweaked similar solutions for days with no success, in the end thanks to your post I realized I was missing the registration of JacksonJaxbJsonProviderHowey
T
0

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.

Twentieth answered 20/2, 2020 at 11:51 Comment(1)
I also used JSOG library to serialize the cyclic references in the JSON structure: You need JSOG library along with the providers: github.com/jsog/jsog-jacksonTwentieth
W
-1

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

Wilma answered 20/3, 2015 at 8:49 Comment(1)
Try using the Jackson JSON processor with an EJB that's annotated with "@Path" and "@WebService". That's the alternative that's mentioned in the JAX-RS section. It may allow you to bypass WAS's flawed JAX-RS Application implementation.Wilma

© 2022 - 2024 — McMap. All rights reserved.