Question

I'm trying to set up a toy application (which may turn in to a real application someday). I'm running into a problem with Wink and Jackson. I've got two applications: one runs wink-server on jetty and seems to be providing some JSON data just fine; one runs wink-client on jetty and receives the JSON data just fine. The problem lies in automagically deserializing the JSON data back into my Java bean.

Here's the code I use in my wink client action:

RestClient client = new RestClient();
Resource resource = client.resource("http://localhost:8081/helloworld");
User user = resource.accept(MediaType.APPLICATION_JSON).get(User.class);

Here's the error I receive when I try to run the Struts action:

java.lang.RuntimeException: No javax.ws.rs.ext.MessageBodyReader found for type class my.package.structure.User and media type application/json. Verify that all entity providers are correctly registered.
org.apache.wink.client.internal.handlers.ClientResponseImpl.readEntity(ClientResponseImpl.java:123)
org.apache.wink.client.internal.handlers.ClientResponseImpl.getEntity(ClientResponseImpl.java:65)
org.apache.wink.client.internal.handlers.ClientResponseImpl.getEntity(ClientResponseImpl.java:52)
org.apache.wink.client.internal.ResourceImpl.invoke(ResourceImpl.java:186)
org.apache.wink.client.internal.ResourceImpl.get(ResourceImpl.java:294)
my.package.structure.action.HelloWorldAction.execute(HelloWorldAction.java:29)
...

If I replace the last line in the first code snippet with the following line, everything works fine and dandy.

String message = resource.accept(MediaType.APPLICATION_JSON).get(String.class);
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(message, User.class);

It's clear that the data is getting across just fine, but the problem seems to lie with the fact that the JacksonJsonProvider class is not registered with Wink client. I've seen a lot of ways to register the provider with the Wink server, but not the Wink client.

Is it possible to do make the first code snippet operate properly? If so, how?

(As an aside, the other problem may be that I'm missing annotations on my User class. Right now there aren't any. Maybe I need some...)

Was it helpful?

Solution

Step 1: Create a class that extends javax.ws.rs.core.Application that allows you to set singletons.

import java.util.Collections;
import java.util.Set;
import javax.ws.rs.core.Application;

public class ClientApplication extends Application {

    private Set<Object> singletons = Collections.emptySet();

    @Override
    public Set<Object> getSingletons() {
        return singletons;
    }

    public void setSingletons(final Set<Object> singletons) {
        this.singletons = singletons;
    }
}

Step 2: In your action, create a org.apache.wink.client.ClientConfig for your org.apache.wink.client.RestClient. This allows you add the org.codehaus.jackson.jaxrs.JacksonJsonProvider to your providers list.

ClientApplication clientApplication = new ClientApplication();
Set<Object> s = new HashSet<Object>();
s.add(new JacksonJsonProvider());
clientApplication.setSingletons(s);
ClientConfig clientConfig = new ClientConfig().applications(clientApplication);
RestClient restClient = new RestClient(clientConfig);

Step 3: Create the org.apache.wink.client.Resource, use the get(Class<T> responseEntity) method and everything will now work as expected.

Resource resource = client.resource("http://localhost:8081/helloworld");
User user = resource.accept(MediaType.APPLICATION_JSON).get(User.class);

If you want to be really slick about it, you can use Spring to set up a ClientConfig bean and inject it in to your actions. Then, you can just call new RestClient(clientConfig) every time and not worry about replicating the entire setup.

OTHER TIPS

i ran into this issue when trying to write some integration tests that POST an object for my rest plugin.

Rather then spinning out a new class you can provide the Jackson provider with an inline class.

  @Before
public void setup(){

    javax.ws.rs.core.Application app = new javax.ws.rs.core.Application() {
        public Set<Class<?>> getClasses() {
            Set<Class<?>> classes = new HashSet<Class<?>>();
            classes.add(JacksonJaxbJsonProvider.class);
            return classes;
        }

    };
    //create auth handler
    clientConfig = new ClientConfig();
    clientConfig.applications(app);
    BasicAuthSecurityHandler basicAuthSecurityHandler = new BasicAuthSecurityHandler();
    basicAuthSecurityHandler.setUserName(USERNAME);
    basicAuthSecurityHandler.setPassword(PASSWORD); 
    clientConfig.handlers(basicAuthSecurityHandler);
    //create client usin auth and provider
    client = new RestClient(clientConfig);

}

Then you can post and consume your annotated objects.

@Test
public void aReadWriteTokenCanBeCreatedAsRequested(){ 
    ClientResponse response = client.resource(resourceUrlToken).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(readAndWriteToken);
    assertEquals("Could not create token needed for test",200,response.getStatusCode());
    readAndWriteToken = response.getEntity(TokenResource.class);
    assertNotNull("Returned token does not have UUID",readAndWriteToken.getUuid());

}

If you're using maven you can make sure Jackson is on the test classpath (check for compatible versions):

<!-- TEST DEPENDENCIES -->
  <dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-core-asl</artifactId>
    <version>1.9.1</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.1</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.1</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-xc</artifactId>
    <version>1.9.1</version>
    <scope>test</scope>
  </dependency>

I wish I could help with registration; but with respect to annotations, I don't think you should need any for Jackson to try to deserialize value. If you are missing something you need you would get different kind of exception.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top