REST API with JSON representations using Apache CXF without Spring - json

I want to support both XML and JSON representations of Resources in my REST API. I have no choice but to use CXF implementation of Jax-RS without using Spring. I am using JAXB to marshall and unmarshall objects. I have the following methods defined that return different representations of the same resource.
#GET
#Produces({MediaType.APPLICATION_XML, MediaType.WILDCARD})
#ElementClass(response = MyList.class)
public Response listXML(){
MyList list = getList();
return Response.status(
Response.Status.FOUND).
entity(list).type(MediaType.APPLICATION_XML).build();
}
#GET
#Produces({MediaType.APPLICATION_JSON})
#ElementClass(response = MyList.class)
public Response listJson(){
MyList list = getList();
return Response.status(
Response.Status.FOUND).
entity(list).type(MediaType.APPLICATION_JSON).build();
}
This works fine for the XML representation. But if I set the Accept header of my HTTP request to application/json I get the following message.
No message body writer has been found for response class MyList.
What am I missing here? The MyList class is generated by JAXB from XSDs and has all the necessary annotations Looks like I need to configure CXF to use a JSON provider. I haven't been able to find good documentation on configuring the JSON provider in the web.xml for a webapplication that doesn't use Spring. If anyone has got this work, please guide me.

I got it figured out. I needed to configure JSONProvider as one of initparams for NonSpringServlet in the deployment descriptor. This wasn't working for me before as I was missng the cxf extensions library and the jettison library. These dependencies don't get automatically pulled by maven or gradle if you only have a dependency on cxf front end jars.

Related

Create JAX-RS Client Post Request with JSON String as Body

I am writing a REST Client for one of the Vendor REST Service. I use jersey 2.x and JSON-P, below are dependencies I add.
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>2.26</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.26</version>
</dependency>
</dependencies>
I successfully write code for GET request and received JSON output. I saved it to a file and used JSON-P to interpret and do my own logic without any issues.
But now I need to write a POST request. When I use CURL as below I am able do it. and want to implement the same using Jeresey 2.x and JSON-P.
curl -v -H "Accept:application/json" -H "Content-Type:application/json" -u user:password -X POST --databinary #./InputParameters.json https://<IP>:<PORT>/Configuration/server
InputParameters.json contents
{
"ip": "10.197.70.16",
"partNumber": 202067,
"model": "IBM P7"
}
When I tried to pass response body as String in JSON format ({"ip": "10.197.70.16", "partNumber": 202067, "model": "IBM P7"}), but didn't work. So tried as JsonObject as below still didn't work.
JsonObject jsonObj = Json.createObjectBuilder()
.add("ip", "10.197.70.16")
.add("partNumber", 202067)
.add("model", "IBM P7")
.build();
response = invocationBuilder.post(Entity.json(jsonObj));
I know core java, based on that experience I jumped into writing this program and got success with GET but not POST. I doubt I am doing something fundamentally wrong with POST.
Let's unpack what you're doing for a bit. First there's this part:
JsonObject jsonObj = Json.createObjectBuilder()
.add("ip", "10.197.70.16")
.add("partNumber", 202067)
.add("model", "IBM P7")
.build();
This creates a javax.json.JsonObject instance. JsonObject, which is part of the JSON-P Java API, is pretty much what it says: a class to represent a JSON object. A JsonObject instance contains a hierarchy of javax.json.JsonValue instances, which conform to more specific types like JsonArray, JsonString, other JsonObjects and so on. In this regard it's not unlike the classes of the DOM API for representing XML documents (let's hope Oracle keeps the API docs at that URL for a while). But JSON is fortunately a lot more straightforward than XML and DOM.
Your jsonObj instance would contain a JsonString instance with value "10.197.70.16" mapped to name "ip", a JsonNumber with value 202067 (probably represented as BigDecimal) mapped to name "partNumber" and so on.
Next your code executes this:
Entity.json(jsonObj)
javax.ws.rs.client.Entity.json(something) basically states that you want to create an entity that will provide the payload for a JAX-RS client invocation with as Content-Type application/json. In other words, the something you create it for must be transformed to a JSON representation when it's sent to the API, which should expect a JSON payload and know how to handle it. Note that Entity.json(...) has a generic type parameter. The method signature is static <T> Entity<t> json(T entity). So you're creating an instance of Entity<JsonObject> with the payload entity jsonObj.
When this is handed over to the post method of a javax.ws.rs.client.Invocation.Builder instance (the post method is actually defined in its parent interface SyncInvoker) the client implementation goes to work.
response = invocationBuilder.post(Entity.json(jsonObj));
It takes the provided Entity instance and its content (our jsonObj), checks what the desired output is of the Entity (this is application/json) and seeks a provider that can turn objects of the given type into that output. In other words, some component must be located that can be given a javax.json.JsonObject and write a representation of it as JSON to an OutputStream. The component handling this could be a javax.ws.rs.ext.MessageBodyWriter that claims it can perform this transformation and was registered to the JAX-RS runtime. Various libraries supply such providers and you can also write your own. This makes JAX-RS extensible to deal with various scenarios, handle non-standard input and output or lets you tune its behaviour. When multiple providers are capable of handling the given entity type and producing the desired output, there are rules to determine which one takes on the job. Note that this can depend on what is on your classpath. There are ways of forcing this explicitly.
The client puts together the invocation through its configuration, using the proper URL, query parameters, HTTP method, headers and so on. The payload is created by writing the entity to an OutputStream in the required format. In your example this results in a POST to the server. When the invocation has been completed you receive a javax.ws.rs.core.Response that you can use to determine the HTTP result code and retrieve a response payload, if any. The readEntity(Class<T> entityType) method of Response works like the reverse of turning an Entity into a payload. It searches for a MessageBodyReader that can interpret the response stream according to the value returned from response.getMediaType() and can create an instance of Class entityType from it.
So with all of that explained, what exactly is going wrong in your approach? Well, the issue is that the default implementations available to your JAX-RS runtime probably don't have a writer specifically for an input of type JsonObject and with expected output application/json. It may seem very logical if the server expects JSON, that you should be able to supply a JsonObject as payload. But if the JAX-RS implementation can't find something to handle that class, then at best it can just use some default approach. In that case it may try to interpret the object as a POJO and serialize it to JSON in a default manner, which could lead to weirdness like this:
{
"valueMap": {
"ip": {
"value": "10.197.70.16"
},
"partNumber": {
"num": 202067,
"integral": TRUE
},
...
}
}
That's what a literal interpretation of the JsonObject instance could look like, depending on which implementation it uses and what is used by JAX-RS to turn it into JSON output. Of course it's possible that the object can't be serialized to JSON at all, either because no suitable MessageBodyWriter can be found or it runs into an error when creating the output.
A first solution would be a very simple one. Just turn the JsonObject into a String and simply provide that as the entity:
StringWriter stringWriter = new StringWriter();
try (JsonWriter jsonWriter = Json.createWriter(stringWriter);) {
jsonWriter.writeObject(jsonObject);
} // some error handling would be needed here
String jsonPayload = stringWriter.toString();
response = invocationBuilder.post(Entity.json(jsonPayload));
It seems you had already tried that. A possible problem with this is that the MessageBodyWriter that gets used needs to just output the String's bytes in a suitable encoding (probably UTF-8) when presented with a String as output and application/json as the required content type. Some may not do that. You could try Entity.entity(jsonPayload, MediaType.TEXT_PLAIN_TYPE.withCharset("UTF-8")) but then the server might reject the call.
Other possible solutions are
Writing your own MessageBodyWriter for String objects with an #javax.ws.rs.Produces(MediaType.APPLICATION_JSON) annotation on it.
Better yet, writing such a class that accepts JsonObject instances.
Creating POJO classes for your JSON structure and letting those get used for generating JSON from instances or deserializing JSON responses to instances.
Finding an extension library that contains suitable providers for dealing with javax.json classes.
The addition of the com.owlike:genson dependency to your project is exactly the application of that last suggestion. The Genson library provides conversions between Java objects and JSON in both directions, as well as data binding. Part of its code base is dedicated to JAX-RS providers, among which a class suitable for accepting JsonObject as input.
Issue is resolved after adding below dependency. At this point I am not sure on what does it do.
Thanks to Swamy (TCS) for his support to resolve this.
<dependency>
<groupId>com.owlike</groupId>
<artifactId>genson</artifactId>
<version>1.4</version>
</dependency>
Example using genson
String serialize = new Genson().serialize(
Json.createObjectBuilder()
.add("ip", "10.197.70.16")
.add("partNumber", 202067)
.add("model", "IBM P7")
.build()
);
response = invocationBuilder.post(Entity.json(serialize));

Jersey / Jackson Unsupported Media Type (for XML requests)

I have spent two days on this with no luck. I have a project that uses jersey and jackson. It was working fine with JSON requests and tried to add XML also, after adding a couple of jars, it worked also with XML.
The problem was that for requests with no parameters (neither JSON nor XML), the API was retrieving XML instead JSON (we want JSON by default).
My code for requests looks like this:
#POST
#Produces({"application/json", "application/xml"})
#Consumes({"application/json", "application/xml"})
public TokenResponse authenticateUser(Credentials credentials) {//code here}
In another post a saw that this is a Jetty bug that is resolved in version 2.16, so I decided to migrate to 2.51.1 with no luck. The JSON request works fine, but when I send the xml post (with postman) the server returns an "Unsupported Media Type" error.
I have also tried with version jersey 2.17 because it works with the same jackson libraries than 2.7 and the result is the same than before.
I am not using maven so here it is a copy of the libraries I have used for the three cases:
1.- JSON and XML works but XML is always returned by default:
2.- JSON works but XML requests returns Unsupported Media Type
3.- JSON works but XML requests returns Unsupported Media Type
Thank you in advance
Finally, instead of updating the libraries I have decided to use the version I had already and add the solution below to #Produces annotation
How to set to default to json instead of xml in jersey?
Setting a qs factor to each media type as mentioned in this answer should work. It can take values between 0 (undesirable) and 1 (highly desirable):
#Produces({ "application/json; qs=1", "application/xml; qs=.5" })
If you don't want to change every single #Produces annotation, you can use a filter to add the Accept: application/json header if it's not present.
#Provider
#PreMatching
public class DefaultAcceptHeaderFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
MultivaluedMap<String, String> headers = requestContext.getHeaders();
if (!headers.containsKey(HttpHeaders.ACCEPT)) {
headers.putSingle(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
}
}
}
It will ensure that your resource methods will produce JSON even when the Accept header is not sent by the client.

Spring Boot to return JSON String from an external API

I have a simple Spring boot project that uses controller mappings to get hard coded information from a class in my project.
For example, if I run the request : localhost:8080/topics, A JSON response is returned with the list of Topic Objects that i have previously created
I want to take this one step further and have a class who's variables are populated by calling this API and parsing the response : https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=MSFT&interval=1min&apikey=demo
I believe this can be done in Java by creating a HTTP connection and reading the data from an input stream, but is the an easier way of doing this with spring boot? Im not fully sure of the name of this procedure hence Im having trouble finding solutions online
Since you are using Spring Boot, making use of Spring's RestTemplate makes sense. It comes with several message converters out of the box, and uses Jackson by default for json content.
Spring has published a good Getting Started page for consuming RESTful web services.
However, the json content returned by that services doesn't look like it will map well to a Java object, so you may have to deserialize it to a HashMap to get to the data you want.
I did an attempt to create something like this.
https://github.com/StanislavLapitsky/SpringSOAProxy
The idea is to register controller interfaces. Each of the interfaces are mapped to some URL. For the interfaces a dynamic proxy are generated (if the implementations are not available locally). So developer just call controller's interface method. The method is invoked for dynamically generated proxy. The proxy uses RestTemplate to call remote URL. It sends and receive JSON and deserializes the returned JSOn to POJO objects returned from the controller.
You need to declare contract - controller interfaces plus DTO to exchange data as well as mapping to understand which URL should be called for each controller.

Unable to get object from JSON response using Jersey Client API and Jackson

I have a test method that uses the JAX-RS Client API to call a service. When I run this code:
Response response = target.request(MediaType.APPLICATION_JSON).get();
List<Thing> list = response.readEntity(new GenericType<List<Thing>>() {});
I get this error:
Unable to find a MessageBodyReader of content-type application/json and type interface java.util.List
I have the jersey-media-json-jackson dependency configured correctly (it is used by the service I'm calling), and the project runs on WildFly 10.
Am I missing something?
I think you can do it like this:
ObjectMapper mapper = new ObjectMapper();
List<Thing> = (List<Thing>)mapper.readValue(response.getEntityInputStream(), List.class);
The problem was apparently caused by a conflict between Jersey and RESTEasy.
I couldn't find a way to disable RESTEasy in WildFly 10, so I ended up removing all Jersey dependencies from my project.
Since I'm only using JAX-RS standard features, it worked fine.

REST proxy for a SOAP Web Service: can I reuse the JAXB classes for the input/output JSON?

I'm creating a proxy service for translating an existing SOAP Web Service to REST. I mean, to create a REST Controller based on Spring for creating the REST interface that will call the existing SOAP Web Service.
The SOAP response must be translated into JSON on the REST service's response.
The steps I followed were:
I have generated the SOAP WebService classes thanks to CXF
(wsdl2java). OK.
I have created the REST Controller for invoking the existing SOAP WS with the previous classes. OK.
The input JSON parameter corresponds to the SOAP input parameter. Can I reuse the JAXB classes I generated on the wsdl2java process?
So I tried to define the REST controller as:
public #ResponseBody WebServiceJAXBOutput service(#RequestBody WebServiceJAXBInput input){
...
}
Nevertheless, the REST call always returns 400 (Bad request) if I specify the data values. Although it works if the input JSON fields are null:
{
"application":null,
"center":null,
"language":null
}
I guess the JAXB getter/setters are failing because of the JAXBElement (public JAXBElement getApplication()).
Should this approach work? Did I miss something?
Many thanks!!
Sergi