A JSON REST client in WebSphere 8.5 (Full Profile)
REST Client
At this version of WebSphere, JAX-RS 1.1 is supported, but that doesn’t include any REST Client support. JAX-RS 2.0 apparently does, but that’s not yet available here on WAS 8.5.5 Full Profile. It is on Liberty Profile, but that’s not what we’re using.
However, WebSphere’s JAX-RS Server support uses Apache Wink, which does include REST Client capability. And, in fact, is the REST Client mechanism recommended by WebSphere’s official documentation:
Implementing clients that use the Apache Wink REST client
Those steps are actually pretty clear and comprehensive. I’ll give quick examples of the mostly-default code I used. (Showing full class packages inline, just once, for simplicity.)
org.apache.wink.client.RestClient client = new RestClient();
org.apache.wink.client.Resource resource = client.resource(resourceURL);
try {
MyJsonResponseDataTypeBean responseBean =
resource.accept(javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE)
.get(MyJsonResponseDataTypeBean.class);
// work with or return responseBean
}
// HTTP error response codes. Unchecked Exception. Subclass of ClientRuntimeException
catch (org.apache.wink.client.ClientWebException e) {
org.apache.wink.client.ClientResponse response = e.getResponse();
// however you want to handle HTTP error response codes
}
// Other call errors. Also Unchecked Exception
catch (org.apache.wink.client.ClientRuntimeException e) {
// however you want to handle other exeptions
}
The Wink Javadoc shows those unchecked Exceptions for one form of the operations, like post().
Another form of each operation does not throw that Exception, but allows you to manually check the ClientResponse
yourself. This is how the example in the above WAS documentation does things. (Note that even those methods do seem to throw ClientRuntimeException
for things like communication errors, despite the javadoc not indicating that.)
POST
Speaking of POST, here’s what that looks like. With a JSON payload as well.
MyJsonResponseDataTypeBean responseBean =
resource.contentType(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON_TYPE)
.post(MyJsonResponseDataTypeBean.class, MyJsonRequestDataTypeBean);
Adding HTTP Basic Authentication
From another WebSphere Knowledge Center article, Securing JAX-RS applications within the web container, which largely describes securing your JAX-RS services, we also find the technique for passing HTTP Basic Authentication in your Wink client requests:
org.apache.wink.client.ClientConfig config = new ClientConfig();
org.apache.wink.client.handlers.BasicAuthSecurityHandler handler =
new BasicAuthSecurityHandler(username, password);
config.handlers(handler);
RestClient client = new RestClient(config); // instead of just new RestClient()
JSON Beans and Serializing/Deserializing
So what allows automatically serializing a request POJO into JSON or deserializing a response JSON into a POJO? I had some naive hope that perhaps just being a JavaBean would be sufficient. But alas, it is not.
Would I have to manually parse the JSON, using something like com.ibm.json.java.JSONObject?
Thankfully, no. Also in the original WebSphere Rest Client article linked above, we find this statement:
Instead of calling the
response.getEntity(String.class)
object with String.class file, you can use any other class that has a valid javax.ws.rs.ext.MessageBodyReader object, such as a JAXB annotated class, a byte[], or a custom class that has a custom entity provider.
JAXB annotated class, that’s super simple. (Still feels a bit weird that JAXB annotations are used for JSON serialization in WAS. Don’t know if other servers do this as well. But easy.) If I do this in a base class, the subclasses don’t have to also add the annotation:
import javax.xml.bind.annotation.XmlType;
@XmlType
public class JsonBase {}
Unwanted Fields
Hmmm… do I have to declare in the POJO every field and sub-object the JSON could have? That would be really frustrating and difficult to maintain. Maybe I’d rather just parse the fields I care about after all.
Yeah, look. If I don’t declare every field, I get errors like this on deserialization:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "start" (Class MyJsonDataType), not marked as ignorable
Jackson, eh? So WebSphere uses Jackson for its JSON serialization.
And, as usual, StackOverflow to the rescue. @JsonIgnoreProperties will do the trick. So change the base class to:
import javax.xml.bind.annotation.XmlType;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
@XmlType
@JsonIgnoreProperties(ignoreUnknown = true)
public class JsonBase {}
Now all subclasses only have to declare whatever fields they want parsed or sent.
Some additional notes
- For reference, if/when moving from JAX-RS 1.1, based on Wink, to 2.0, based on CXF: https://www.ibm.com/support/knowledgecenter/was_beta_liberty/com.ibm.websphere.wlp.nd.multiplatform.doc/ae/cwlp_jaxrs_behavior.html (Oct 2 2017)
- FWIW, appears JAX-RS 2.0 under WebSphere no longer exposes the Jackson classes, so that will necessitate a change to this approach: https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/cwlp_jaxrs_behavior.html (Feb 15 2017)