I have a relatively simple RESTful web service which uses Jersey and Eclipselink MOXy.
I can POST requests to the service if the data is formatted as XML, but if I send it as JSON instead, the server generates an HTTP 400 (Bad Request), with the message: "The request sent by the client was syntactically incorrect.".
The service-side looks like this:
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Subscription post(Subscription Subscription) {
return Subscriptions.addSubscription(Subscription);
}
If I send it XML data from Javascript in a webpage like this, there is no problem:
$.ajax({
url: 'http://localhost:8080/MyService/subscription',
type: 'POST',
data: "<subscription><notificationType>EMAIL</notificationType><notificationAddress>[email protected]</notificationAddress></subscription>",
headers: {
Accept : "application/xml",
"Content-Type": "application/xml"
},
// .. other params ...
);
However, with the equivalent JSON I get HTTP 400 - Bad Request:
$.ajax({
url: 'http://localhost:8080/MyService/subscription',
type: 'POST',
data: JSON.stringify(
{subscription:{notificationType:"EMAIL",notificationAddress:"[email protected]"}}
),
dataType: 'json'
headers: {
Accept : "application/json",
"Content-Type": "application/json"
}
// .. other params ...
);
I have inspected the request using Fiddler, and the data formatting and headers all look correct.
The interesting thing is that I can successfully unmarshall the exact same JSON string if I plug it into this code:
String json = "{\"subscription\":{\"notificationType\":\"EMAIL\",\"notificationAddress\":\"[email protected]\"}}";
JAXBContext context = JAXBContext.newInstance(Subscription.class);
Unmarshaller m = context.createUnmarshaller();
m.setProperty("eclipselink.media-type", "application/json");
StringReader sr = new StringReader(json);
Subscription sub = (Subscription)m.unmarshal(sr);
System.out.println(sub.toString());
The subscription class is defined as:
@XmlRootElement(name="subscription")
public class Subscription {
public enum NotificationType { EMAIL, SMS };
private String notificationAddress;
private NotificationType notificationType;
public String getNotificationAddress() {
return notificationAddress;
}
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public NotificationType getNotificationType() {
return notificationType;
}
public void setNotificationType(NotificationType notificationType) {
this.notificationType = notificationType;
}
public Subscription() {
}
@Override
public String toString() {
String s = "Subscription";
if (getNotificationAddress() != null) {
s += "(" + getNotificationType().toString() + ":" + getNotificationAddress() + ")";
}
return s;
}
}
I have configured Eclipselink MOXy as my JAXB provider by adding this line to jaxb.properties in the package that contains my Subscriber class:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
and that seems to work, at least for marshalling objects going out from the service.
What am I missing?
EDIT: This is what Fiddler captures when I post the JSON data:
POST http://localhost:8080/MyService/subscription HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 86
Accept: application/json
Origin: http://localhost:8080
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36
Content-Type: application/json
Referer: http://localhost:8080/TestPage/AddSubscription.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
{"subscription":{"notificationType":"EMAIL","notificationAddress":"[email protected]"}}
UPDATE:
I took Option#2 from Blaise's answer below, created an Application-derived class, thus:
import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class MyApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>(2);
set.add(MOXyJsonProvider.class);
set.add(SubscriptionResource.class); // the class containing my @POST service method.
return set;
}
}
And added to web.xml:
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.example.MyApplication</param-value>
And now I don't get an HTTP 400, and the code in my service is hit, which it wasn't before, however the passed-in Subscription object has all uninitialized fields, e.g. notificationAddress is null. If I post using XML, it still works ok.
UPDATE#2:
I have reduced my code to the smallest subset that demonstrates the problem, and you can get it here:
https://www.dropbox.com/sh/2a6iqw65ey0ahrk/D2ILi_722z
The above link contains a .zip with 2 Eclipse projects; TestService (the Jersey RESTful service that accepts a Subscription object) and TestPage (a .html page with some JavaScript to POST a subscription object in either JSON or XML).
If you put a breakpoint in the post method of the service, and use the test page to send the JSON request, you'll see an un-initialised Subscription object gets passed in.
EclipseLink JAXB (MOXy) can be used with Jersey in a couple of different ways to produce JSON.
Option #1 - Add a jaxb.properties File
Jersey can leverage a JAXB (JSR-222) implementation to produced JSON. If you add a jaxb.properties
in with your domain model then you can specify MOXy as that provider. For a while the following bug existed in Jersey that prevented MOXy from being used in this way which may be what you are hitting now.
This method of producing JSON has some limitations and may not be what your ultimately want.
Option #2 - Leverage MOXyJsonProvider
As of EclipseLink 2.4.0 MOXy offers its own JSON-binding based on the JAXB and MOXy metadata. You can configure this using the MOXyJsonProvider.
import java.util.*;
import javax.ws.rs.core.Application;
import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
public class MyApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>(1);
set.add(SubscriptionResource.class);
return set;
}
@Override
public Set<Object> getSingletons() {
MOXyJsonProvider moxyJsonProvider = new MOXyJsonProvider();
moxyJsonProvider.setIncludeRoot(true);
HashSet<Object> set = new HashSet<Object>(1);
set.add(moxyJsonProvider);
return set;
}
}
For More Information
This is the way I would recommend using MOXy with Jersey, or any other JAX-RS provider.