My previous restful client example and server example were perfect, well, not exactly. They worked but they pretty much assuming a perfect world. That is to say that there is never a communication error or a server error.
This small example will return one of the magazine objects. Depending how you call it, it will return either the object directly or the xml that you can marshal into the object itself.
// example 2/3 setup ClientConfig config1 = new ClientConfig(); Client client1 = ClientBuilder.newClient(config1); WebTarget target1 = client1.target(uribase); // example 3 magazine xmlobj = target1.path("/Communicate/hello").request().accept(MediaType.APPLICATION_XML).get(magazine.class); showMagazine("pure xml object", xmlobj);
However, it should be pretty obvious in the previous simple server code that if for any reason the server cannot find the data it cannot really communicate that fact back to the client. Well, it cannot if you are returning magazine objects or general strings.
New and better server
The solution is to return a response record. This record will contain both the error code as well as our object. The code is actually not too different from from the previous example but there are some major differences.
The first is that we are no longer returning either xml(string) nor magazine objects but rather we are returning Response objects. This allows us to set the status to one of the values that matches the result of the call.
Response.Status.OK
It is possible to use one of the predefined values such as Response.Status.OK but it is just as acceptable to put the actual value of 200.
Here is a list of the values that are defined for us to use.
Response codes | Description |
---|---|
OK | 200 |
CREATED | 201 |
ACCEPTED | 202 |
NO_CONTENT | 204 |
MOVED_PERMANENTLY | 301 |
SEE_OTHER | 303 |
NOT_MODIFIED | 304 |
TEMPORARY_REDIRECT | 307 |
BAD_REQUEST | 400 |
UNAUTHORIZED | 401 |
FORBIDDEN | 403 |
NOT_FOUND | 404 |
NOT_ACCEPTABLE | 406 |
CONFLICT | 409 |
GONE | 410 |
PRECONDITION_FAILED | 412 |
UNSUPPORTED_MEDIA_TYPE | 415 |
INTERNAL_SERVER_ERROR | 500 |
SERVICE_UNAVAILABLE | 503 |
The code in this “server code” is not much larger than the original code.
Another one of the changes is that the URI is returned as part of the response object. This URI should point to the the data in the server. This is especially interesting if a new object is created on your server. The server then will return the URI to reference that object.
The final difference is that we pass back our object as part of the Response object. In the below code this is done as a simple string but it could just as easily be a your own Java object.
package com.acmesoft; import java.net.URI; import java.net.URISyntaxException; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @Path("/Communicate") public class HelloWorldEx { /* * http://192.168.178.70:8080/SecondRestful/Communicate/mag */ @GET @Path("/mag") @Produces( {MediaType.APPLICATION_XML}) public Response sayHelloXml(@Context UriInfo ui) throws URISyntaxException { String retval = "" + "2016" + "6" + "Economist" + "9.99" + ""; String path = ui.getAbsolutePath().toString() ; URI uri = new URI(path); return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build(); } /* * http://192.168.178.70:8080/SecondRestful/Communicate/mag */ @GET @Path("/mag") @Produces( {MediaType.APPLICATION_JSON}) public Response sayHelloJSON(@Context UriInfo ui) throws URISyntaxException { String retval = "{ \"magazine\": {\"year\":2016,\"month\":6,\"title\":\"Economist\",\"price\":9.99} }"; String path = ui.getAbsolutePath().toString() ; URI uri = new URI(path); return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(retval).build(); } /* * http://192.168.178.70:8080/SecondRestful/Communicate/oucha_bunga */ @Path("{c}") @GET @Produces( {MediaType.APPLICATION_XML}) public Response complexURI(@Context UriInfo ui, @PathParam("c") String msg) throws URISyntaxException { String mymsg = "Thinkin about " + msg; String xmlStr = "" + mymsg + ""; // this is a really clever way to get the URI but // if you pass in a string, it will throw an error. // and it all comes down to the URI that is added to the // response object (below) String path = ui.getAbsolutePath().toString() ; URI uri = new URI(path); return Response.created(uri).contentLocation(uri).status(Response.Status.OK).entity(xmlStr).build(); } }
The final method in this server example is is not actually called by my client but it is a clever example of a URI that can accept anything, in this case a string, and then that string can be used in some clever manner.
New Client
The new client changes is actually quite similar to the new server changes. As a matter of fact, the client changes are actually almost transparent. There is just a few small changes to accommodate the retrieving Response objects instead of strings or other java objects.
Once the Response object has been retrieved simply call the status method to find out the status returned from the server. The range of success codes is 200-299.
if (response.getStatus() == 200) { xmlAnswer = response.readEntity(String.class); System.out.println(xmlAnswer); magazine converted = convertXmlToMag(xmlAnswer); showMagazine("xml text data", converted); } response.close();
If the call was successful then simply call the readEntity method on the Response object. Simply pass in the class of the object that you are trying to read out. The object is retrieved just like magic.
In order to keep control on the system resources close the Response and the Client objects, failing to do so will cause your program to fail eventually due to resource issues.
package com.acmesoft; import java.io.StringReader; import java.net.URI; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import org.glassfish.jersey.client.ClientConfig; public class TestClient { String uribase = "http://192.168.178.70:8080/SecondRestful"; public void showMagazine(String desc, magazine value) { System.out.println("\n" + desc ); System.out.println("year " + value.getYear()); System.out.println("month " + value.getMonth()); System.out.println("price " + value.getPrice()); System.out.println("title " + value.getTitle()); } public magazine convertXmlToMag(String xmlstring) { magazine converted = null; JAXBContext jc = null; try { jc = JAXBContext.newInstance(magazine.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); StringReader reader = new StringReader(xmlstring); converted = (magazine) unmarshaller.unmarshal(reader); } catch (JAXBException ex) { ex.printStackTrace(); } return converted; } public WebTarget restConnect(String targetUrl) { URI uri = UriBuilder.fromUri(targetUrl).build(); ClientConfig config = new ClientConfig(); Client client = ClientBuilder.newClient(config); WebTarget target = client.target(uri); return target; } public TestClient() { ClientConfig config = new ClientConfig(); Client client = ClientBuilder.newClient(config); WebTarget target = client.target(uribase); Response response = target.path("/").path("Communicate").path("/mag").request().accept(MediaType.APPLICATION_XML).get(Response.class); System.out.println(response.getStatus()); String xmlAnswer = ""; if (response.getStatus() == 200) { xmlAnswer = response.readEntity(String.class); System.out.println(xmlAnswer); magazine converted = convertXmlToMag(xmlAnswer); showMagazine("xml text data", converted); } response.close(); response = target.path("/Communicate/mag").request().accept(MediaType.APPLICATION_XML).get(Response.class); if (response.getStatus() == 200) { magazine xmlobj = response.readEntity(magazine.class); showMagazine("pure xml object", xmlobj); } response.close(); client.close(); } public static void main(String[] args) { new TestClient(); } }
The output is not terribly interesting, it is just a few print statements in order to prove that the client can retrieve the data.
200
<Magazine><Year>2016</Year><Month>6</Month><Title>Economist</Title><Price>9.99</Price></Magazine>
xml text data
year 2016
month 6
price 9.99
title Economist
pure xml object
year 2016
month 6
price 9.99
title Economist
With the ability to verify the return status of the restful server calls it is possible to create much more interesting services than displayed here.
I have attempted to create more than your typical “foo” “bar” type example program. My final blog on restful services will be a fairly complete service that could be used to provide price data supplied by several “banks” on various exchanges.
The data is actually pseudo randomly generated and retrieved and displayed by web browser using java script to fetch, extract and display the data from a JSON formatted string.