Mashup explanation - part I


My AJAX presentation  from the Versailles JavaDay a couple of weeks ago featured a mashup quickly written using a JSF component to put on a map all participants to the event. The code was pretty ugly as I only had a few days to write the app from the day I found out that Google had a Geo service with good results in Europe. The application has two parts:

1/ a batch Java SE 6 (b86) application reading a PARTICIPANTS database using JPA (Java Persistence API), doing some address cleaning/normalization (haven't cleaned that part), invoking the geo web service, parsing the JSON result, and storing the resulting latitude/longitude in a COORDINATES table. Again, the reason for not making this address resolution an interactive part of the application is the time taken by this which is due to (in no particular order) the Google "1 request per 1.75 seconds" limitation, the request itself and the result parsing time.

2/ a single page Web Application built with Java Studio Creator using the JSF maps component which simply reads the COORDINATES table to build a collection of MapMarkers which is then bound to the component's markers property. The COORDINATES table is the only link between the two parts of the application.

Part I - JPA in Java SE, Geo-coding invocationJSON parsing.
Part II - Writting geo data back to the database, Web Service invocation & Google Map markers construction (give me another day or so before I publish part II).


JPA in Java SE
Retrieving data from the PARTICIPANTS table was quite easy using the JPA (Java Persistence API) and NetBeans "Entity Class from Database" feature (a simple Participants entity class is created from the PARTICIPANTS table). 

The persistence.xml configuration for the persistence unit is quite simple:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="GeoCoordPU" transaction-type="RESOURCE_LOCAL">
    <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
    <class>bo.Participants</class>
    <class>bo.GeoCoordonnees</class>
    <class>bo.GeoCoordonneesFull</class>
    <properties>
      <property name="toplink.jdbc.user" value="nbuser"/>
      <property name="toplink.jdbc.password" value="NOT_TELLING"/>
      <property name="toplink.jdbc.url" value="jdbc:derby://localhost:1527/javaday06"/>
      <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
      <!--
      <property name="toplink.logging.level" value="FINEST"/>
      -->
    </properties>
  </persistence-unit>
</persistence>


It uses Toplink as the provider (default in GlassFish) and Derby (JavaDB) as the database. Note this is all done outside the container and only required adding the Toplink Essential library to the project).

Reading the data from the table goes like this:

    private EntityManagerFactory factory;
    private EntityManager em;
    private List<Participants> allParticipants;
    
    public FetchCoordinates() {
        factory = Persistence.createEntityManagerFactory("GeoCoordPU");
        em = factory.createEntityManager();
        initData();
        ...
    }

    private void initData() {
        EntityTransaction tx = null;
        try {
            tx = em.getTransaction();
            tx.begin();
            
allParticipants = em.createQuery("SELECT p FROM PARTICIPANTS p").getResultList();
            tx.commit();
        } catch (RuntimeException e) {
            if (tx != null && tx.isActive()) {
                tx.rollback();
            }
            System.out.println("### Error: " + e);
        }
    }

Since, there is no transaction demarcation provided in this un-managed Java SE environment, there's a bit a boiler-plate for hand-doing it. All there's left to do is cycle through allParticipants to submit each address to the Geo web service.

You can read more on this subject here:
- "Stand-alone Persistence in a J2SE Project"
"An Introduction to Java Persistence for Client-Side Developers".


Geo-coding invocation
The Geo web service provided by Google, unlike it's SOAP API is REST-based and is described here.
I used the Apache HTTP Common API (talk about dependencies, I had to drag in 3 jars) to HTTP GET the url and decided to request a JSON-formatted response. When registering for a Google key, you need to give the URL from which it'll be used. In my case, it's not a web app, so no referrer. I had some trouble figuring out why my Google key would not always work. Registering using http://localhost and making sure to use an HTTP GET seemed to do the trick.

    HttpMethod method = null;
       
    // Build request URL with JSON result
    String urlAddress = "http://maps.google.com/maps/geo?q=" + address + "&output=json&key=" + gKey;
       
    try {
        // Using Apache HTTPClient
        HttpClient client = new HttpClient();
        method = new GetMethod(urlAddress);
           
        int reqStatusCode = client.executeMethod(method);
        if (reqStatusCode != HttpStatus.SC_OK) {
            System.err.println("\*\*\* Method failed: " + method.getStatusLine());
        }
           
        InputStreamReader reader = new InputStreamReader( method.getResponseBodyAsStream() );
        BufferedReader bReader = new BufferedReader(reader);
        // read the result in JSON format
        String str = bReader.readLine();
        ...
    }

The address was cleaned in various ways and if needed, subsequent invocations were tried with only the city name, in the end leaving unresolved mostly miss-spelled addresses.


JSON parsing
I'm still not sure why I chose json formatting over the XML alternative. My initial code was basic string manipulation (it did the job, but boy, like any String manipulation code, was it ugly!). JSON is less verbose than XML, readable by many languages, and its data is a valid JavaScript object, so I decided this was a good occasion to use the JavaScript Rhino engine that is now part of Mustang (Java 6) to do the parsing.

public class JSONGeoEngine {
    ScriptEngineManager manager;
    ScriptEngine engine;
    Compilable compilable;
    CompiledScript resultScript=null, latitudeScript=null, longitudeScript=null;
    // use the JavaScript compiler to evaluate the JSON result
    private final String jsonString2Object = "var jsonObject = eval('(' + jsonResult + ')');";
    private Bindings bindings;
    String currentJSONresult = null;
    
    public JSONGeoEngine() {
        manager = new ScriptEngineManager();
        engine = manager.getEngineByName("js");
        
        // use this for better performance
        compilable = (Compilable) engine;
        bindings = engine.createBindings();
        
        try {
            resultScript = compilable.compile( jsonString2Object +
                " result = jsonObject.Status.code;");

            // Using Placemark[0] and ignoring multiple results.
            latitudeScript = compilable.compile( jsonString2Object +
                " result = jsonObject.Placemark[0].Point.coordinates[1];");

            longitudeScript = compilable.compile( jsonString2Object +
                " result = jsonObject.Placemark[0].Point.coordinates[0];");

        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    // setJSONresult() needs to be called first
    public int getResultCode() {
        int returnCode = -1;
        try {
            Double r = (Double)resultScript.eval(bindings);
            returnCode = r.intValue();
        } catch (ScriptException ex) {
            ex.printStackTrace();
            returnCode = 500; // dummy, non-200 error
        }
        return returnCode;
    }
    
    public void setJSONresult( String result ) {
        if ( (currentJSONresult != null) &&  (currentJSONresult.equalsIgnoreCase(result)) ) {
            System.out.println ("WARNING: Passed the same JSONresult twice: "+ result);
        }
        currentJSONresult = result;
        bindings.put("jsonResult", currentJSONresult);
    }
    
    // setJSONresult() needs to be called first

    public void obtainCoordinates (Coordinates coord) {
        try {
            coord.setLatitude( latitudeScript.eval(bindings).toString() );
            coord.setLongitude( longitudeScript.eval(bindings).toString() );
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    }


The key code is in the jsonString2Object object. The constructor does some setting up to use compiled scripts to gain performance. I still need to run a quick benchmark to measure the gain of the interpreted version (and maybe also against the ugly Java String manipulation version).

I'm not sure JSON is the best approach here (especially given I need to retrieve multiple results from a single JSON result which required multiple calls to eval()), but it proved to be quite easy to set up.
Alternatives here could be:
- using the Java objects for JSON
- using the XML format and JAXB 2.0 to bind to Java objects (also now part of Mustang)
- using the XML format and a regular SAX parser (has been in the JDK for ages)

(part II real soon).

Comments:

Post a Comment:
Comments are closed for this entry.
About

This blog has moved

Alexis Moussine-Pouchkine's Weblog

GlassFish - Stay Connected

Search

Archives
« avril 2014
lun.mar.mer.jeu.ven.sam.dim.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today
Blogroll

No bookmarks in folder

News

No bookmarks in folder