24 March 2014

Using the JAX-RS 2.0 Client API with WebLogic Server 12.1.3



Please note: this blog discusses WebLogic Server 12.1.3
which has not yet been released.

As part of the JAX-RS 2.0 support we are providing with WebLogic Server 12.1.3, one really useful new feature is the new Client API it provides, enabling applications to easily interact with REST services to consume and publish information.

By way of a simple example, I'll build out an application that uses the freegeoip.net REST service to lookup the physical location of a specified IP address or domain name and deploy it to WebLogic Server 12.1.3.

The first step to perform is to make a call to the freegeoip.net REST API and examine the JSON payload that is returned.
$ curl http://freegeoip.net/json/buttso.blogspot.com

{"ip":"173.194.115.75","country_code":"US","country_name":"United States","region_code":"CA","region_name":"California","city":"Mountain View","zipcode":"94043","latitude":37.4192,"longitude":-122.0574,"metro_code":"807","area_code":"650"}

The next step is to build a Java class to represent the JSON payload that is returned. In this case, it's quite simple because the JSON payload that is returned doesn't contain any relationships or complex data structures.
/**
 *
 * @author sbutton
 * {"ip":"173.194.115.75","country_code":"US","country_name":"United States","region_code":"CA","region_name":"California","city":"Mountain View","zipcode":"94043","latitude":37.4192,"longitude":-122.0574,"metro_code":"807","area_code":"650"}            
 */
public class GeoIp implements Serializable {

    private String ipAddress;
    private String countryName;
    private String regionName;
    private String city;
    private String zipCode;
    private String latitude;
    private String longitude;

    public String getIpAddress() {
        return ipAddress;
    }

    public void setIpAddress(String ipAddress) {
        this.ipAddress = ipAddress;
    }    

    ...

}
With the GeoIP class defined, the next step is to consider how to convert the JSON payload into an instance of the GeoIP class. I'll show two ways this can be done.

The first way to do it is to create a class that reads the result of the REST request, parses the JSON payload and constructs a representative instance of the GeoIP class. Within the JAX-RS API, there is an interface MessageBodyReader that can be implemented to convert a Stream into a Java type.

http://docs.oracle.com/javaee/6/api/javax/ws/rs/ext/MessageBodyReader.html

Implementing this interface gives you the readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) method which supplies an InputStream containing the response to read. The method then parses out the JSON payload and constructs a responding GeoIP instance from it.

Parsing the JSON payload is straightforward with WebLogic Server 12.1.3 since we've included the (JSR-353) Java API for JSON Processing implementation which provides an API for reading and creating JSON objects.
package oracle.demo.wls.jaxrs.client.geoip;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.json.Json;
import javax.json.stream.JsonParser;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class GeoIpReader implements MessageBodyReader {

    @Override
    public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return GeoIp.class.isAssignableFrom(type) ;
    }

    @Override
    public GeoIp readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
        GeoIp g = new GeoIp();
        JsonParser parser = Json.createParser(entityStream);
        while (parser.hasNext()) {
            switch (parser.next()) {
                case KEY_NAME:
                    String key = parser.getString();
                    parser.next();
                    switch (key) {
                        case "ip":
                            g.setIpAddress(parser.getString());
                            break;
                        case "country_name":
                            g.setCountryName(parser.getString());
                            break;
                        case "latitude":
                            g.setLatitude(parser.getString());
                            break;
                        case "longitude":
                            g.setLongitude(parser.getString());
                            break;
                        case "region_name":
                            g.setRegionName(parser.getString());
                            break;
                        case "city":
                            g.setCity(parser.getString());
                            break;
                        case "zipcode":
                            g.setZipCode(parser.getString());
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
        }
        return g;
    }
}

Once this class is built, it can be registered with the Client so that it can be called when necessary to convert a payload of MessageType.APPLICATION_JSON type into an instance of the GeoIP object, here done in an @PostConstruct method on a JSF Bean
    @PostConstruct
    public void init() {
        client = ClientBuilder.newClient();
        client.register(GeoIpReader.class);
    }


The alternative way to do thi is to use the EcliseLink MOXY JAXB implementation that is provided with WebLogic Server, which can automatically marhsall and unmarshall JSON payloads to and from Java objects. Helpfully, the JAX-RS 2.0 shared-library that WebLogic Server 12.1.3 contains the jersey-media-moxy extension that enables the EclipseLInk MOXY implementation to be simply registered and used by applications when conversion is needed.

To use the JAXB/MOXY approach, the GeoIPReader class can be thrown away. No manual parsing of the payload is required. Instead, the base GeoIP class is annotated with JAXB annotations to denote it as being JAXB enabled and to provide some assistance in the mapping of the class properties to the payload property names.
package oracle.demo.wls.jaxrs.client.geoip;

import java.io.Serializable;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author sbutton
 * {"ip":"173.194.115.75","country_code":"US","country_name":"United States","region_code":"CA","region_name":"California","city":"Mountain View","zipcode":"94043","latitude":37.4192,"longitude":-122.0574,"metro_code":"807","area_code":"650"}            
 */

@XmlRootElement
public class GeoIp implements Serializable {
    
    @XmlAttribute(name = "ip")
    private String ipAddress;
    @XmlAttribute(name = "country_name")
    private String countryName;
    @XmlAttribute(name = "region_name")
    private String regionName;
    @XmlAttribute(name = "city")
    private String city;
    @XmlAttribute(name = "zipcode")
    private String zipCode;
    @XmlAttribute(name = "latitude")
    private String latitude;
    @XmlAttribute(name = "longitude")
    private String longitude;

    ...
   
}


With the JAXB annotations placed on the GeoIP class to enable it to be automatically marshalled/unmarshalled from JSON, the last step is to register the EclipseLink MOXY implementation with the Client. This is done with the assistance of a small utility method, as shown in the Jersey User Guide Media chapter.
    public static ContextResolver createMoxyJsonResolver() {
        final MoxyJsonConfig moxyJsonConfig = new MoxyJsonConfig();
        moxyJsonConfig.setFormattedOutput(true);

        Map namespacePrefixMapper = new HashMap(1);
        namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
        moxyJsonConfig.setNamespacePrefixMapper(namespacePrefixMapper).setNamespaceSeparator(':');

        return moxyJsonConfig.resolver();
    }
This method is then used to register the relevant ContextResolver with the Client to use to handle JSON_conversions, instead of the GeoIPReader class that was used before.<
    @PostConstruct
    public void init() {
        client = ClientBuilder.newClient();
        client.register(createMoxyJsonResolver());
        //client.register(GeoIpReader.class);
    }

With the JSON payload to GeoIP conversion now covered, the JAX-RS Client API can be used to make the call to the freegeoip REST service and process the response.

To make a client call, two classes are used: javax.ws.rs.client.Client and javax.ws.rs.client.WebTarget .

The Jersey User Guide provides a good description of theses two classes and their relationship:

The JAX-RS Client API is a designed to allow fluent programming model. This means, a construction of a Client instance, from which a WebTarget is created, from which a request Invocation is built and invoked can be chained in a single "flow" of invocations ... Once you have a Client instance you can create a WebTarget from it ... A resource in the JAX-RS client API is an instance of the Java class WebTarget and encapsulates an URI. The fixed set of HTTP methods can be invoked based on the WebTarget. The [base] representations are Java types, instances of which, may contain links that new instances of WebTarget may be created from.

In this example application, the Client is opened in an @PostConstruct method and closed in a @PreDestroy method, with the WebTarget being created and its GET method called when the lookup is executed by the user.
@Named
@RequestScoped
public class GeoIpBackingBean {

    private WebTarget target = null;
    private Client client = null;

    ...

    @PostConstruct
    public void init() {
        client = ClientBuilder.newClient();
        //client.register(createMoxyJsonResolver());
        client.register(GeoIpReader.class);
    }

    @PreDestroy
    public void byebye() {
        client.close();
    }

    public void lookupAddress() {
        try {
            target = client.target(String.format(rest_base_url, addressToLookup));
            geoIp = target.request().get(GeoIp.class);
        } catch (Exception e) {
            e.printStackTrace();
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Error executing REST call: " + e.getMessage()));
        }
    }
 
    ...
}  


Bringing it all together as a JSF based application results in a JSF Bean being created that allows the IP address to be entered and a method that invokes the JAX-RS Client API to call out to the freegeoip.net REST service to retrieve the JSON payload containing the location information. A simple JSF facelet page is used to support the entering of the IP address and the display of the relevant data from the GeoIP object.


    <h:form>
        <h:panelGrid columns="2" style="vertical-align: top;">
        <h:outputLabel value="Address"/>
        <h:inputText value="${geoIpBackingBean.addressToLookup}"/>
        <h:outputLabel value=""/>
        <h:commandButton action="${geoIpBackingBean.lookupAddress()}" value="Lookup" style="margin: 5px;"/>
        </h:panelGrid>
    </h:form>


    <h:panelGrid columns="2">
        <h:outputText value="IP:"/>
        <h:outputText value="${geoIpBackingBean.geoIp.ipAddress}"/>
        <h:outputText value="Country Code:"/>
        <h:outputText value="${geoIpBackingBean.geoIp.countryName}"/>
        <h:outputText value="State:"/>
        <h:outputText value="${geoIpBackingBean.geoIp.regionName}"/>
        <h:outputText value="City"/>
        <h:outputText value="${geoIpBackingBean.geoIp.city}"/>
        <h:outputText value="Zipcode:"/>
        <h:outputText value="${geoIpBackingBean.geoIp.zipCode}"/>
        <h:outputText value="Coords:"/>
        <c:if test="${geoIpBackingBean.geoIp.ipAddress != null}">
            <h:outputText value="${geoIpBackingBean.geoIp.latitude},${geoIpBackingBean.geoIp.longitude}"/>
        </c:if>
      </h:panelGrid>

The last step to perform is to add a weblogic.xml deployment descriptor with a library-ref to the [jsf,2.0] shared-library, which must be deployed as I described earlier in Using JAX-RS 2.0 with WebLogic Server 12.1.3.

The application is now ready to to deploy and run.

2 comments:

Anonymous said...

Buttso,
this JSON API looks a lot more fun to me: https://untappd.com/api/docs

@

Buttso said...

Cheers to that! :-)