2

I am working on an embedded jersey instance which will run a JAXB RESTful service. I have configured Jackson with two steps:

  1. Adding this to my POM

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.23.2</version>
    </dependency>
    
  2. Registering it in my application

    public HandheldApplication() {
    scripts.add(HandheldServer.class);
    scripts.add(BasicScript.class);
    // Add JacksonFeature.
    scripts.add(JacksonFeature.class);
    scripts.add(LoggingFilter.class);
    

    }

I have a complex object being passed back and forth as shown below:

package com.ziath.handheldserver.valueobjects;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.*;

@SuppressWarnings("restriction")
@XmlRootElement
public class Widget {

private String key;
private String name;
private List<String> options = new ArrayList<String>();
private String value;
private String type;

public Widget(){
    super();
}

public Widget(String key, String name, List<String> options, String value,
        String type) {
    super();
    this.key = key;
    this.name = name;
    this.options = options;
    this.value = value;
    this.type = type;
}

public String getKey() {
    return key;
}
public void setKey(String key) {
    this.key = key;
}
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public List<String> getOptions() {
    return options;
}
public void setOptions(List<String> options) {
    this.options = options;
}
public String getValue() {
    return value;
}
public void setValue(String value) {
    this.value = value;
}
public String getType() {
    return type;
}
public void setType(String type) {
    this.type = type;
}

}

When I execute this in a GET method as shown below:

@Override
@GET
@Path("getKeys")
@Produces(MediaType.APPLICATION_JSON)
public List<Widget> getKeys(@QueryParam(value = "page") int page) 

This works fine and I get JSON back; however when I execute it is a PUT as shown below:

@Override
@PUT
@Path("validateKeys")
@Produces({MediaType.APPLICATION_JSON})
@Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(@QueryParam(value = "page")int page, @QueryParam(value = "widgets")List<Widget> widgets) 

When I execute a PUT to access this method I get a stack trace as follows:

Caused by: org.glassfish.jersey.internal.inject.ExtractorException: Error un-marshalling JAXB object of type: class com.ziath.handheldserver.valueobjects.Widget.
    at org.glassfish.jersey.jaxb.internal.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:195)
    at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.convert(AbstractParamValueExtractor.java:139)
    at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.fromString(AbstractParamValueExtractor.java:130)
    at org.glassfish.jersey.server.internal.inject.CollectionExtractor.extract(CollectionExtractor.java:88)
    at org.glassfish.jersey.server.internal.inject.CollectionExtractor$ListValueOf.extract(CollectionExtractor.java:107)
    at org.glassfish.jersey.server.internal.inject.QueryParamValueFactoryProvider$QueryParamValueFactory.provide(QueryParamValueFactoryProvider.java:89)
    ... 38 more
Caused by: javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.]
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:335)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:563)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:249)
    at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:214)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:140)
    at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:123)
    at org.glassfish.jersey.jaxb.internal.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:190)
    ... 43 more

So it seems to me that Jackson is correctly marshalling my POJO into JSON but trying to unmarshall it as XML. Note that I switched to Jackson away from MOXy because I needed to be able to handle collections coming back and forth and apparently MOXy cannot do that.

Is there a setting I've missed to tell Jackson/Jersey to go both ways for JSON?

Neil Benn
  • 904
  • 1
  • 12
  • 31
  • How are you executing the PUT? From javascript, a command line, a JAX-RS client, or something else? – Jason Hoetger Sep 07 '16 at 02:25
  • Sorry; I found the answer the problem was that Jackson/Jersey automatically marshalls a POJO into JSON but doesn't unmarshall it so I needed to remove the XML related annotation at the top and manually put in a fromString method to unmarhsall JSON into the object. – Neil Benn Sep 07 '16 at 09:55
  • Jersey definitely can unmarshall entities from the body as well. I suspect you were not supplying the proper Content-Type header (application/json) when making the request, which is why I was wondering how you were invoking it (when I `curl` from the command line, I often forget the `-H 'Content-Type: application/json`). – Jason Hoetger Sep 09 '16 at 04:31
  • Hello - no the problem was that I needed to provide a unmarshall method for the object - see my own answer. The correct content type was provided but it could not automatically unmarshall the JSON without a helper method - the helper method fromString is sought by Jersey/Jackson and it all works just fine. Now I have provided the unmarshall method it works just fine. Thanks for taking the time to comment however. – Neil Benn Sep 09 '16 at 12:23
  • I suspect you could've done it without a static fromString method and without a @FormParam. But it sounds like your solution works for you! – Jason Hoetger Sep 09 '16 at 18:31

2 Answers2

0

Try removing @QueryParam(value = "widgets") because you should pass it as entity body - not query param.

@PUT
@Path("validateKeys")
@Produces({MediaType.APPLICATION_JSON})
@Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(@QueryParam(value = "page")int page, List<Widget> widgets) 

Also you can make wrapper class:

@XmlRootElement
public class Widgets {

    private List<Widget> widgets; 

    // other fields, setters and getters
}

And then:

@PUT
@Path("validateKeys")
@Produces({MediaType.APPLICATION_JSON})
@Consumes(MediaType.APPLICATION_JSON)
public boolean validateKeys(@QueryParam(value = "page")int page, Widgets widgets) 

I would suggest to read some discussions about REST design because you're using verbs in your paths:

Community
  • 1
  • 1
Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114
  • Thanks I posted the answer above and thanks for the REST grammar; I autogenerated the code stubs at first which I'll clean up later when going into deployment. – Neil Benn Aug 31 '16 at 13:21
0

I was switching between QueryParam and FormParam to try and get one of them to work. If I use FormParam I also need to change the consumes to APPLICATION_FORM_URLENCODED.

The actual issue was that the default unmarshalling with Jackson was using XML because it was tagged as an XML resource - take that out! I finally managed to work out how to unmarshall from JSON by using a static fromString method. Then to handle the list; I cannot use a wrapper class because this needs to be highly cross language and exposing a wrapper with a list would have complicated the implementation from Python, C#, etc. The way to get it to accept a list with a wrapper is to post the name of the param (in this case widgets) multiple time. Then each JSON passed in will be called against the fromString method.

Neil Benn
  • 904
  • 1
  • 12
  • 31