Skip to main content

Screencast #Web11: Travel Map - Another Real-life app using jMaki & Jersey

Posted by arungupta on January 1, 2008 at 3:52 PM PST

In
my role of Technology Evangelist, I get the opportunity to meet a lot of
community (folks like you :) all around the world. In the year 2007, I
represented GlassFish
(and related technologies - Metro,
jMaki and href="http://jersey.dev.java.net">Jersey)
at multiple
conferences
. This blog introduces a  new real-life
application that plots all the places I visited this year on a
jMaki-wrapped
Google Map widget. Clicking on the marker shows more information about
the event such as dates and the blog entry covering the event.

Play the video below to see how the application looks like.

Here is the architecture of this application:

alt="travel map architecture" title="Travel Map Architecture"
src="http://blogs.sun.com/arungupta/resource/images/travel-map-architecture.png">

It consists of a server-side and a client-side applications -
developed as NetBeans
projects.

  1. Server-side project - A RESTful Web service endpoint that
    provides resource represenations
    for all the events attended and associated meta information such as
    date and blog URLs. This endpoint is created using href="http://jersey.dev.java.net">Jersey.
  2. Client-side project - A href="http://ajax.dev.java.net">jMaki-enabled Web
    application that consumes the representations generated
    by the RESTful Web service and plots the information on a jMaki-wrapped
    Google Map widget.

Both the server-side and client-side are deployed on href="http://glassfish.dev.java.net">GlassFish.

This
is only a sample application so optimizations are certainly possible
and corner cases (such as no blog entry for a particular visit) are not
accounted for. But the application still demonstrates the concept. The
fully built application looks like as shown below:

title="Arun's Travel Map 2007" alt="Arun's Travel Map 2007"
src="http://blogs.sun.com/arungupta/resource/images/travel-map-2007.png">

My
first presentation in this role was Sun Tech Days Atlanta (highlighted
in the image). This application generates an interactive Google Map so
feel free to zoom in/out and click 

And one last thing before we build the application. Here is
the list of technologies and associated concepts used to build this
application:

  1. Jersey
    1. Shows an example of how RESTful Web services can be
      easily generated from JPA Entity Classes.
    2. Shows how all the resource representations (instead of
      reference to individual resources) can be returned by a Jersey endpoint.
  2. jMaki
    1. Shows how to consume XML data from an href="http://blogs.sun.com/arungupta/entry/jmaki_accessing_external_services">external
      service (RESTful Web service endpoint) in this case.
    2. Shows how the underlying data model of a widget (Google
      Map in this case) can be accessed and manipulated.
  3. GlassFish
    1. All
      the applications are deployed on GlassFish - implicit in the
      development/deplyment process through seamless integration with
      NetBeans.
  4. NetBeans 6
    1. Used for generation of RESTful Web services from JPA
      Entity Classes.
    2. Used for generating/deploying jMaki projects and
      drag-and-drop of jMaki-wrapped widgets.
  5. JavaScript Closures - to persist the state for asynchronous
    callback functions
  6. JavaScript DOM processing - to process the XML data
    received from Jersey endpoint.
  7. Google Maps API
    1. Generate meaningful markers on each location
    2. Populate Google Map from a RESTful Web service endpoint
  8. Java Persistence API - to retrieve data from the database.

And finally, lets build this application. Lets build the
RESTful Web service endpoint project first.

  1. Create and Populate the Database
    1. In the NetBeans IDE, go to Services tab, and connect to
      the database with URL "
      jdbc:derby://localhost:1527/sample
      [app on APP]
      " (right-click and select "Connect...").
    2. Right-click on this database and select "
      Execute
      Command...
      " and create a table by giving the following
      command:



      create table EVENTS (id int GENERATED ALWAYS AS
      IDENTITY,

       
                       
      event_name varchar(255),

                 
              dates varchar(20),

                         
      venue varchar(255),

                         
      blogs varchar(2056),

                 
              PRIMARY KEY (id))




      Notice,
      the "id" column is marked as IDENTITY that instructs the database to
      auto generate the values for this column and increment by 1 (default)
      for each row. This column is also marked as the primary key.
    3. Again right-click on the database and select "
      Execute
      Command...
      " to add data to the table by giving the
      following command:


           

      INSERT INTO EVENTS (event_name, dates, venue,
      blogs) VALUES('SunTech Days - Atlanta', 'Jan 16 - Jan 17',
      'Cobb Galleria Center, Two
      Galleria Parkway, Atlanta, Georgia, 30339',
      'http://blogs.sun.com/arungupta/entry/wsit_and_web_2_0');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('jMaki Day', '
      Feb 23', '4150
      Network Circle Santa Clara, CA 95054',
      'http://blogs.sun.com/arungupta/entry/sun_internal_jmaki_day_review');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Ajax World - New
      York', 'Mar 19 - Mar 21', 'The
      Roosevelt Hotel, 45 E 45th St, New York, NY 10017',
      'http://blogs.sun.com/arungupta/entry/sun_ajax_world');

      INSERT
      INTO
      EVENTS (event_name, dates, venue, blogs) VALUES('The Server Side Java
      Symposium - Las Vegas', 'Mar 22', '3355 Las Vegas Blvd. South
      Las
      Vegas, NV 89109',
      'http://blogs.sun.com/arungupta/entry/sun_the_server_side_java,
      http://blogs.sun.com/arungupta/entry/tango_at_venetian_las_vegas');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('JavaOne - San
      Francisco', 'May 7 - May 11', 'Moscone
      Center, 747 Howard Street, San Francisco, CA 94103',
      'http://blogs.sun.com/arungupta/entry/slides_for_ts_4865,
      http://blogs.sun.com/arungupta/entry/javaone_2007_day_1_finished,
      http://blogs.sun.com/arungupta/entry/javaone_2007_day_1,
      http://blogs.sun.com/arungupta/entry/javascript_everywhere_javaone_2007_demo,
      http://blogs.sun.com/arungupta/entry/excel_using_wsit_javaone_2007,
      http://blogs.sun.com/arungupta/entry/ts_4865_takes_two_to,
      http://blogs.sun.com/arungupta/entry/communityone_glassfish_day_report,
      http://blogs.sun.com/arungupta/entry/javaone_2007_backstage,
      http://blogs.sun.com/arungupta/entry/javaone_2007_is_almost_here,
      http://blogs.sun.com/arungupta/entry/my_javaone_2007_picks');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Rails Conf -
      Portland', 'May 17 - May 20', '777 NE MLK, Jr. Blvd. Portland,
      OR
      97232',
      'http://blogs.sun.com/arungupta/entry/tim_bray_s_keynote_session,
      http://blogs.sun.com/arungupta/entry/sun_rails_conf_2007_keep,
      http://blogs.sun.com/arungupta/entry/getting_started_with_jruby_tutorial,
      http://blogs.sun.com/arungupta/entry/jmaki_netbeans_and_glassfish_in');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Google Developer
      Day - San Jose', 'May 31', '150 W San Carlos St San Jose, CA 95113',
      'http://blogs.sun.com/arungupta/entry/google_developer_day_report');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Mashup Camp -
      Mountain View', 'Jul 18 - Jul 19', 'Computer History Museum, 1401 N
      Shoreline Blvd., Mountain View, CA 94043',
      'http://blogs.sun.com/arungupta/entry/jmaki_at_mashup_camp_report,
      http://blogs.sun.com/arungupta/entry/jmaki_mashup_camp');

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('OSCON -
      Portland',
      'Jul 23 - Jul 27', '777 NE MLK, Jr. Blvd. Portland, OR 97232',
      'http://blogs.sun.com/arungupta/entry/jmaki_oscon');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('JRuby Hack Day -
      San Francisco', 'Aug 8', '1201 8th St, San Francisco, CA 94107',
      'http://blogs.sun.com/arungupta/entry/jruby_on_rails_hackday_report,
      http://blogs.sun.com/arungupta/entry/learn_jruby_on_rails_free');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Rich Web
      Experience - San Jose', 'Sep 6 - Sep 8', '170 S Market St, San
      Jose,
      CA 95113',
      'http://blogs.sun.com/arungupta/entry/the_rich_web_experience_2007,
      http://blogs.sun.com/arungupta/entry/jmaki_javafx_the_rich_web');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Rails Conf Europe
      - Berlin', 'Sep 17 - Sep 19', 'Maritim Pro Arte,
      Friedrichstrasse 151,
      10117 Berlin',
      'http://blogs.sun.com/arungupta/entry/rails_conf_europe_2007_day2,
      http://blogs.sun.com/arungupta/entry/rails_conf_europe_2007_day1,
      http://blogs.sun.com/arungupta/entry/rails_conf_europe_2007_day,
      http://blogs.sun.com/arungupta/entry/jmaki_netbeans_and_glassfish_in1');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Sun Tech Days -
      Rome', 'Sep 24 - Sep 25', 'Meliá Roma Aurelia Antica, Vía
      Aldobrandeschi, 223 
      Rome ITALY  00163',
      'http://blogs.sun.com/arungupta/entry/netbeans_day_rome_2007,
      http://blogs.sun.com/arungupta/entry/travel_tips_to_rome,
      http://blogs.sun.com/arungupta/entry/glassfish_metro_jersey_and_jmaki');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Sun Tech Days -
      Milan', 'Sep 26 - Sep 28', 'ATA Hotel Quark - Via Lampedusa 11/a 20141
      Milano, Italia',
      'http://blogs.sun.com/arungupta/entry/glassfish_day_milan_2007');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Mid West Java
      Tech Days - Minneapolis', 'Oct 16', 'University of St Thomas,
      MPL 201,
      1000 LaSalle Avenue,
      Minneapolis, MN 55403-2005',
      'http://blogs.sun.com/arungupta/entry/mid_west_java_tech_days,
      http://blogs.sun.com/arungupta/entry/metro_and_jmaki_in_minneapolis');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Mid West Java
      Tech Days
      - Chicago', 'Oct 18', 'Donald E Stephens Convention Center, 9301, W
      Bryn Mawr Ave,
      Rosemont IL 60018',
      'http://blogs.sun.com/arungupta/entry/mid_west_java_tech_days1,
      http://blogs.sun.com/arungupta/entry/crowne_plaza_chicago_o_hare,
      http://blogs.sun.com/arungupta/entry/metro_and_jmaki_in_minneapolis');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Silicon Valley
      Code Camp
      - Los Altos', 'Oct 27', 'Foothill College, Los Altos, CA',
      'http://blogs.sun.com/arungupta/entry/silicon_valley_code_camp_trip,
      http://blogs.sun.com/arungupta/entry/metro_jmaki_silicon_valley_code');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('Sun Tech Days -
      Beijing', 'Nov 1 - Nov 3', 'Beijing International Convention
      Center,
      No.8
      Beichendong Road Chaoyang District, Beijing',
      'http://blogs.sun.com/arungupta/entry/glassfish_day_beijing_2007_by,
      http://blogs.sun.com/arungupta/entry/wangfujing_street_authentic_china_in,
      http://blogs.sun.com/arungupta/entry/sun_tech_days_beijing_talent,
      http://blogs.sun.com/arungupta/entry/sun_tech_days_beijing_day,
      http://blogs.sun.com/arungupta/entry/travel_tips_to_beijing,
      http://blogs.sun.com/arungupta/entry/glassfish_day_beijing');

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner
      Preso
      - Toronto', 'Nov 21', 'Toronto City Center',
      'http://blogs.sun.com/arungupta/entry/metro_jmaki_jruby_glassfish_q');

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner
      Preso
      - Montreal', 'Nov 21', 'Montreal City Center',
      'http://blogs.sun.com/arungupta/entry/metro_jmaki_jruby_glassfish_q');

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('GlassFish
      - Delhi University',
      'Dec 3', 'New Delhi',
      'http://blogs.sun.com/arungupta/entry/glassfish_delhi_university');

      INSERT
      INTO EVENTS (event_name, dates, venue, blogs) VALUES('FOSS.IN -
      Bangalore', 'Dec 4', 'India Institute of Science, Bangalore',
      'http://blogs.sun.com/arungupta/entry/packaging_java_apps_for_ubuntu,
      http://blogs.sun.com/arungupta/entry/foss_in_schedules_now_available,
      http://blogs.sun.com/arungupta/entry/glassfish_foss_in_2007');

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner
      Preso
      - Bangalore', 'Dec 4', 'Bangalore',
      'http://blogs.sun.com/arungupta/entry/glassfish_bangalore_chennai_and_pune');

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner
      Preso
      - Chennai', 'Dec 5', 'Chennai',
      'http://blogs.sun.com/arungupta/entry/glassfish_bangalore_chennai_and_pune');

      INSERT INTO EVENTS (event_name, dates, venue, blogs) VALUES('Partner
      Preso
      - Pune', 'Dec 6', 'Pune',
      'http://blogs.sun.com/arungupta/entry/glassfish_bangalore_chennai_and_pune');

           
      INSERT INTO EVENTS (event_name, dates,
      venue, blogs) VALUES('Partner
      Preso
      - San Francisco', 'Dec 17', 'San Francisco',
      'http://blogs.sun.com/arungupta/');




      These
      SQL statements populate the database with details about my visits in
      2007. If you'd like to develop a similar application highlighting your
      visits then you'll need to modify the VALUES
      clause to match accordingly.


    4.  

     
  2. Create and Configure RESTful Web service endpoint

  3.  

         
    1. Create a Persistence Unit as described in "Generating
      Entity Classes from Database" section in href="http://www.netbeans.org/kb/60/websvc/rest.html">Getting
      Started with RESTful Web Services. Lets say the project name
      is "WebApplication3", package name is "events" and the table name to
      generate Entity classes is EVENTS. Take everything else as the defaults.
    2. Generate a RESTful Web service as described in
      "Generating RESTful Web Services from Entity Classes" section in href="http://www.netbeans.org/kb/60/websvc/rest.html">Getting
      Started with RESTful Web Services.
      1. Add a new class EventsList in the events
        package as:



        @javax.xml.bind.annotation.XmlRootElement

        public class EventsList {

          @javax.xml.bind.annotation.XmlElement

          protected java.util.List events;

               

          public EventsList() {

            if (events == null)

              events = new
        java.util.ArrayList();

          }

               

          public void add(Events name) {

            events.add(name);

          }

               

          public java.util.List getValue() {

            return events;

          }

        }
      2. In service.EventsResource,
        change the method associated with GET to:



        public EventsList get() {

               
        EventsList eventsList = new EventsList();

               
        List list  =
        PersistenceService.getInstance().createQuery("SELECT e FROM Events
        e").getResultList();

               
        for (Events e : list) {

                   
        eventsList.add(e);

                }

               
        return eventsList;

        }

               


        This
        will ensure that all the resource representations are returned instead
        of a reference to the resource. Make sure to fix the imports.

That's it, our server-side project is now ready. "http://localhost:8080/WebApplication3/resources/events"
now return a complete RESTful representation of all the rows from the
database table EVENTS.



Lets build the client-side application next. Make sure  href="https://ajax.dev.java.net/jmaki-plugin.html">jMaki
plug-in in NetBeans IDE is already installed.

  1. In the NetBeans IDE, create a new Web project, enable "
    Ajax
    Framework
    " and choose the "Standard"
    layout for "index.jsp".  Lets say the
    project name is "WebApplication4".
  2. Drag-and-drop
    a jMaki-wrapped Google Map widget in the 'Main Content Area' and
    jMaki-wrapped Yahoo Button in the 'Sidebar Content Here'.
  3. Customise the widgets
    1. Add id="mymap" attribute to the
      Google Map widget. The updated widget looks like as shown below:



       style="font-weight: bold;">id="mymap"

              
      args="{ centerLat : 37.4041960114344,

                      
      centerLon : -122.008194923401 }" />

           

      id="mymap"
      will allow the Map widget to be accessed by
      name later.
    2. Add args="{label:'Plot Events'}"
      attribute to thes Yahoo button widget. The updated widget looks like as
      shown below:



       style="font-weight: bold;">args="{label:'Plot Events'}"/>
  4. In glue,js, add the following
    code to *onClick subscribe method:



    var url = jmaki.xhp + "?id=events";

    var _map = jmaki.getWidget("mymap").map;

    _map.setZoom(2);

    _map.clearOverlays();

    _map.enableInfoWindow();

       

    jmaki.doAjax({method: "GET",

        url: url,

        callback: function(_req)
    {          
       

           
    var xmlobject = (new DOMParser()).parseFromString(_req.responseText,
    "text/xml");

           
    var root = xmlobject.getElementsByTagName('eventsList')[0];

           
    var events = root.getElementsByTagName('events');

           
    for (var i = 0 ; i < events.length ; i++) {

               
    var event = events[i];

               
    var eventName =
    event.getElementsByTagName('eventName')[0].firstChild.nodeValue;

               
    var venue = event.getElementsByTagName('venue')[0].firstChild.nodeValue;

               
    var blogs = event.getElementsByTagName('blogs')[0].firstChild.nodeValue;

               
    var dates = event.getElementsByTagName('dates')[0].firstChild.nodeValue;

               
    var
    id = event.getElementsByTagName('id')[0].firstChild.nodeValue;


                   
       

               
    var encodedLocation = encodeURIComponent("location=" + venue);

               
    var url = jmaki.xhp + "?id=yahoogeocoder&urlparams=" +
    encodedLocation;

               
    jmaki.myHandler(url, eventName, blogs, dates, id, _map);

            }

        }

    });
  5. Add the following functions above the *onClick
    subscribe method:



    // "Function closure" used from
    http://econym.googlepages.com/basic1.htm

    // Creates local copy of "marker" and "html" variables to be preserved
    for later use

    function createMarker(point,html) {

        var marker = new GMarker(point);

        GEvent.addListener(marker, "click",
    function() {

           
    marker.openInfoWindowHtml(html);

        });

        return marker;

    };

       

    // Function closure that preserves "eventName", "blogs", "dates and "id"

    // Gets the latitude/longitude from Yahoo Geocoding service and plots
    them on the map

    // Also creates meaningful markers

    jmaki.myHandler = function(_url, eventName, blogs, dates, id, _map) {

        jmaki.doAjax({url: _url,

           
    callback : function(req) {

               
    if (req.responseText.length > 0) {

                   
    jmaki.log("name: " + eventName);

                   
    var response = eval("(" + req.responseText + ")");

                   
    var coordinates = response.coordinates;

                   
    jmaki.publish("/jmaki/plotmap", coordinates);

                   
    jmaki.log("plotting " + eventName);

                   
    var latlng = new GLatLng(coordinates[0].latitude,
    coordinates[0].longitude);

                   
       

                   
    var blogHtml = "";

                   
    b = blogs.split(', ');

                   
    for (i=0; i
                       
    blogHtml += '' + (i+1) +
    '
    ';

                       
    if (i
                           
    blogHtml += ", ";

                   
    }

                   
       

                   
    var txt = '' +

                   
    '' +

                   
    'Dates: ' + dates + ',
    2007' +

                   
    '' +

                   
    '
    #' + id + ": " + eventName +
    '
    Blogs: ' + blogHtml +
    '
    ';

                   
       

                   
    var marker = createMarker(latlng, txt);

                   
    _map.addOverlay(marker);

                   
    marker.openInfoWindowHtml(txt);

               
    } else {

                   
    jmaki.log("Failed to get coordinates for " + location );

               
    }

            }

        });   

    };
  6. Add the following entry in Web Pages, resources, xhp.json:



    ,

    {"id": "events",

     "url":"http://localhost:8080/WebApplication3/resources/events/"

    }




    assuming WebApplication3 is the project where RESTful Web service endpoint is hosted.

That completes our client-side web application as well. Now, either hit
F6 (default key to Run the NetBeans project) and this will show
http://localhost:8080/WebApplication4/index.jsp in the configured
browser. Once you click on "Plot Events" button, all the markers on the
Google Map are plotted.

Future Improvements

  1. If Jersey can return all the href="https://jersey.dev.java.net/servlets/ReadMsg?list=dev&msgNo=284">resource
    representations directly, then the workaround used above may
    not be required.
  2. Use e4x
    after