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 ContextResolverThis method is then used to register the relevantcreateMoxyJsonResolver() { 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(); }
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:
Buttso,
this JSON API looks a lot more fun to me: https://untappd.com/api/docs
@
Cheers to that! :-)
Post a Comment