Using Glassfish 3 and Java EE 6 for longitude/latitude calculations to implement server-side location based services

Since the introduction of the iPhone in the summer of 2007 smartphones are almost everywhere today. Smartphones are powerful utilities today that almost no one of us wants to miss. I remember how I was told about 20 years ago that "the most powerful computer hardware doesn't make any sense if you do not have the right software that can make use of it". Analogously, smartphones today are nothing without the right apps (no matter if you are running on iOS, Android...). Many of the apps we use make use of "location based services" (LBS). Think of the apps that tell you where you can find the closest gas station or hotel without having you to manually type in your current position. Instead, the apps simply use the APIs of the underlying smartphone platform to determine the current location. The coordinates can then be used to find the "Points of Interest" (POI) which you are looking for in order to display them on a map on the smartphone. The search of POIs depending on a given position (defined by its coordinates) can sometimes be implemented directly on client side (i.e. within the app), but sometimes you have or want to do all the calculations on the backend. If you want to use your backend for features like "find all POIs around me within a distance of 2 miles/Km" then this tutorial might help you with some ideas. Please consider that such server side location based services can be used for any kind of mobile device (not only for smartphones) as long as the corresponding platform offers the right APIs (i.e. for making REST calls). This could be maybe your "Smart TV", an "iWatch" or even the applications installed in your car's "infotainment system".

This tutorial will offer you a basic implementation for your own server side location based services including finding POIs (Point of Interest) within a given distance from a given position defined by its longitude and latitude coordinates. Even performance aspects related to database queries are discussed. This tutorial has been tested with Glassfish 3.1.2.2 to leverage the latest Java EE 6 features. The focus is clearly on server side implementation. The server side logic will be accessible to the "outer" world via REST. For demonstration purposes I will also implement a very simple web frontend which allows you to test the server side implementations (HTML, CSS, JavaScript, jQuery). Our little application (backend and frontend) is based on the following requirements:

  • Adding new POIs to the database
  • Deleting POIs from the database
  • Query POIs within a given distance

We will use Glassfish 3.1.2.2, PostgreSQL, EJBs, JPA 2.0, REST, JSON, AJAX, HTML, CSS, JavaScript, jQuery and Maven to implement our application. Since this is not an introduction to all these technologies you need to be familiar with all that stuff (at least a little bit). This tutorial does not handle how to install Glassfish. I also expect you have already installed a PostgreSQL database. Other databases are also fine, but I will only handle PostgreSQL in detail when it comes to creating a JDBC resource on Glassfish and then using JPA 2.0 to access it. If I find the time I will write a similar tutorial telling you how to implement all that stuff explained here by using a No-SQL database instead of PostgreSQL. Especially the performance comparison can be very interesting if you think of very large GEO databases which contain a lot of POIs.

In less than two weeks Java EE 7 and Glassfish 4 will be published offering features that are not available in Java EE 6. For this tutorial the interesting features of Java EE 7 are related to JAX-RS 2.0 (JSR 339), JPA 2.1 (JSR 338) and Java API for JSON Processing (JSR 353). Just to give you one example, in Java EE 7 you can define Indexes for your Entities (thanks to JPA 2.1). I will write a new version of this tutorial tailored for Java EE 7 and Glassfish 4 once released (and if I find some time...).


Table of Contents:


Creating this tutorial really meant a lot of effort. I hope it will help others. If you have any questions do not hesitate to contact me. Any feedback is welcome! Also feel free to leave a comment (see below). For helping me to maintain my tutorials any donation is welcome. I hope you will enjoy the tutorial.

I guess you will either use Eclipse or NetBeans IDE for following this tutorial. The Maven project structure looks as follows in Eclipse:


Eclipse 4.2 JEE:
Eclipse Project

1. Maven configuration (pom.xml)

I have decided to use Maven because I belive that most developers have experience with Maven nowadays. As you can see we will create a web project which will compile to a war file. SimpleLatLng is a library for common latitude and longitude calculation needs which even allows to make use of GeoHashing. Instead of using SimpleLatLng we will use a simple API written by Jan Philip Matuschek. He has done a great job describing the maths behind the longitude/latitude calculations in his paper with the tile Finding Points Within a Distance of a Latitude/Longitude Using Bounding Coordinates. We will use his implementation by simply copying his Java sources. Please also consider that I have added eclipse-link dependencies to the pom.xml because we will make use of some EclipseLink features which are not part of JPA 2.0. The scope of the EclipseLink dependencies can be set to provided because Glassfish is shipped with EclipseLink. EclipseLink is the reference implementation for JPA 2.0.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nabisoft.tutorials</groupId>
    <artifactId>location-based-services-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>location-based-services-demo</name>

    <properties>
        <endorsed.dir>/endorsed</endorsed.dir>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-json</artifactId>
            <version>1.11.1</version>
        </dependency>
        
        <!-- Interesting library which we will not use -->
        <!--
        <dependency>
            <groupId>com.javadocmd</groupId>
            <artifactId>simplelatlng</artifactId>
            <version>RELEASE</version>
        </dependency>
        -->
        
        <!-- we expect the postgresql jdbc to be already available in the glassfish  -->
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.4-702.jdbc4</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- EclipseLink -->
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.3.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>javax.persistence</artifactId>
            <version>2.0.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
            <version>2.3.2</version>
            <scope>provided</scope>
        </dependency>
        
        <!-- Java EE 6 API -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
        
    </dependencies>

    <build>
        <finalName>lbs-demo</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <compilerArguments>
                        <endorseddirs></endorseddirs>
                    </compilerArguments>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.8</version>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>copy</goal>
                        </goals>
                        <configuration>
                            <outputDirectory></outputDirectory>
                            <silent>true</silent>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>javax</groupId>
                                    <artifactId>javaee-endorsed-api</artifactId>
                                    <version>6.0</version>
                                    <type>jar</type>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    
    <repositories>
        
        <repository>
            <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
            <id>eclipselink</id>
            <layout>default</layout>
            <name>Repository for library Library[eclipselink]</name>
        </repository>
        
        <repository>
            <id>maven2-repository.dev.java.net</id>
            <name>Java.net Repository for Maven</name>
            <url>http://download.java.net/maven/2/</url>
            <layout>default</layout>
        </repository>
        
        <repository>
            <id>Central</id>
            <name>Maven Repository</name>
            <url>http://repo1.maven.org/maven2</url>
            <layout>default</layout>
        </repository>
        
        <repository>
            <id>central</id>
            <name>Central Repository</name>
            <url>http://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        
    </repositories>
</project>

2. Defining web.xml and glassfish-web.xml

Our web.xml configures Jersey. The glassfish-web.xml file is not really mandatory for us and I just want to show you that you can use this file for Glassfish specific configuration of your web applications. In our case we use it for disabling the xpoweredBy header in order to allow a better server obfuscation.


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <!-- Jersey REST -->
    <servlet>
        <servlet-name>ServletAdaptor</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>ServletAdaptor</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>
    
    <session-config>
        <session-timeout>30</session-timeout>
        <cookie-config>
            <name>SESSIONID</name>
        </cookie-config>
    </session-config>
    
</web-app>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
  <context-root>/lbs-demo</context-root>
  <class-loader delegate="true"/>
  <!-- see http://docs.oracle.com/cd/E18930_01/html/821-2417/beatx.html#scrolltoc -->
  <jsp-config>
    <property name="keepgenerated" value="true">
      <description>Keep a copy of the generated servlet class java code.</description>
    </property>        
    <property name="xpoweredBy" value="false">
      <description>server obfuscation</description>
    </property>    
  </jsp-config>
</glassfish-web-app>

3. Adding a JDBC Resource to Glassfish 3

Now we want to tell Glassfish where our DB is, which credentials to use when connecting to the DB and so on. As I have mentioned earlier we want to connect to a PorstgreSQL database. I have used the steps described here in my previous tutorials. There are different options for adding new JDBC Resources to Glassfish:

I never use option 1 because it doesn't support automated scripts. I prefer option 2 or 3, but I will only tell you how to configure all of your resources (in our case only a jdbc-connection-pool) into a glassfish-resources.xml file and afterwards how to add all of your resources defined in that file via asadmin add-resources to Glassfish (option 3). So first of all we will create the glassfish-resources.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
  <!-- JDBC -->
  <jdbc-connection-pool 
        allow-non-component-callers="false" 
        associate-with-thread="false" 
        connection-creation-retry-attempts="0" 
        connection-creation-retry-interval-in-seconds="10" 
        connection-leak-reclaim="false" 
        connection-leak-timeout-in-seconds="0" 
        connection-validation-method="auto-commit" 
        datasource-classname="org.postgresql.ds.PGSimpleDataSource" 
        fail-all-connections="false" 
        idle-timeout-in-seconds="300" 
        is-connection-validation-required="false" 
        is-isolation-level-guaranteed="true" 
        lazy-connection-association="false" 
        lazy-connection-enlistment="false" 
        match-connections="false" 
        max-connection-usage-count="0" 
        max-pool-size="32" 
        max-wait-time-in-millis="60000" 
        name="lbs-Pool" 
        non-transactional-connections="false" 
        ping="false" 
        pool-resize-quantity="2" 
        pooling="true" 
        res-type="javax.sql.DataSource" 
        statement-cache-size="0" 
        statement-leak-reclaim="false" 
        statement-leak-timeout-in-seconds="0" 
        statement-timeout-in-seconds="-1" 
        steady-pool-size="8" 
        validate-atmost-once-period-in-seconds="0" 
        wrap-jdbc-objects="false">
    <property name="serverName" value="localhost"/>
    <property name="portNumber" value="5432"/>
    <property name="databaseName" value="db_lbs"/>
    <property name="User" value="lbs"/>
    <property name="Password" value="lbs"/>
    <property name="URL" value="jdbc:postgresql://localhost:5432/db_lbs"/>
    <property name="driverClass" value="org.postgresql.Driver"/>
  </jdbc-connection-pool>
  <jdbc-resource enabled="true" jndi-name="jdbc/lbs" object-type="user" pool-name="lbs-Pool"/>
</resources>

As you can see, our glassfish-resources.xml defines a JDBC resource. With that configuration Glassfish can manage DB connections. In our case we have configured a PorstgreSQL database. Please see the official documentation for more details regarding the parameters I have used. The next step is to tell Glassfish about our jdbc-resource defined in the glassfish-resources.xml file by executing a asadmin add-resources command:


# switch to the setup dir of our maven project, where glassfish-resources.xml can be found
cd ./location-based-services-demo/src/main/setup

#you might not need "--secure"
asadmin --secure add-resources glassfish-resources.xml

#you can also specify the complete path to your glassfish-resources.xml file
asadmin --secure add-resources ./location-based-services-demo/src/main/setup/glassfish-resources.xml

Before implementing the EJB and JPA Entity classes let's first create our persistence.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
  <persistence-unit name="lbsPU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/lbs</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
      <!-- log JPA Statements -->
      <property name="eclipselink.logging.level" value="FINE"/>
      <!-- also log the values of the parameters used for the query -->
      <property name="eclipselink.logging.parameters" value="true"/>
      
      <!-- in case the fields are defined do not match the case the database returns them. @see http://www.eclipse.org/forums/index.php/m/885235/ -->
      <!-- we need this for native queries because of what our postgres db returns -->
      <property name="eclipselink.jpa.uppercase-column-names" value="true"/>
      
    </properties>
  </persistence-unit>
</persistence>


4. Creating a simple data model (EJBs and JPA Entity classes)

As mentioned earlier we will use Jan Philip Matuschek's Java class. In his paper Finding Points Within a Distance of a Latitude/Longitude Using Bounding Coordinates he describes the maths behind the longitude/latitude calculations. If you don't understand his paper you will need to read a good maths book, i.e. Handbook of Mathematics by I.N. Bronshtein, K.A. Semendyayev, Gerhard Musiol and Heiner Mühlig (Oct 11, 2007) and others. A good entry is also the Haversine Formula. If you search the web then you will find Java libraries for such longitude/latitude calculations, SimpleLatLng is one of them (it supports GeoHashing). PostgreSQL offers built in support for Geometric Types and Geometric Functions and Operators (we will not make any use of them). For your own interest you could also have a look at the following links:

The data model we want to implement is quite easy. For storing each of our POIs to the database we implement an Entity Class called Poi. This Entity Class is used in our (local) Stateless EJB called PoiFacade which extends the abstract class AbstractFacade<Poi>. The EJB PoiFacade gets the PersistenceContext injected and uses it to query the database. Its method findPoisWithinDistance(...) queries all POIs within a given air distance and it uses the GeoLocation class implemented by Jan Philip Matuschek. The following UML diagram hopefully helps for a better understanding:


UML Class Diagram describing the model (created with astah)
UML Class Diagram explaining the Model

Below you find the corresponding Java sources of the mentioned classes:


package com.nabisoft.tutorials.lbs.utils.geo;

/**
 * <p>Represents a point on the surface of a sphere. (The Earth is almost
 * spherical.)</p>
 *
 * <p>To create an instance, call one of the static methods fromDegrees() or
 * fromRadians().</p>
 *
 * <p>This code was originally published at 
 * <a href="http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates#Java">http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates#Java</a>.</p>
 *
 * @see http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates#Java
 * @author Jan Philip Matuschek
 * @version 22 September 2010
 */
public class GeoLocation {

    private double radLat;  // latitude in radians
    private double radLon;  // longitude in radians
    private double degLat;  // latitude in degrees
    private double degLon;  // longitude in degrees
    private static final double MIN_LAT = Math.toRadians(-90d);  // -PI/2
    private static final double MAX_LAT = Math.toRadians(90d);   //  PI/2
    private static final double MIN_LON = Math.toRadians(-180d); // -PI
    private static final double MAX_LON = Math.toRadians(180d);  //  PI

    private GeoLocation() {
    }

    /**
     * @param latitude the latitude, in degrees.
     * @param longitude the longitude, in degrees.
     */
    public static GeoLocation fromDegrees(double latitude, double longitude) {
        GeoLocation result = new GeoLocation();
        result.radLat = Math.toRadians(latitude);
        result.radLon = Math.toRadians(longitude);
        result.degLat = latitude;
        result.degLon = longitude;
        result.checkBounds();
        return result;
    }

    /**
     * @param latitude the latitude, in radians.
     * @param longitude the longitude, in radians.
     */
    public static GeoLocation fromRadians(double latitude, double longitude) {
        GeoLocation result = new GeoLocation();
        result.radLat = latitude;
        result.radLon = longitude;
        result.degLat = Math.toDegrees(latitude);
        result.degLon = Math.toDegrees(longitude);
        result.checkBounds();
        return result;
    }

    private void checkBounds() {
        if (radLat < MIN_LAT || radLat > MAX_LAT
                || radLon < MIN_LON || radLon > MAX_LON) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * @return the latitude, in degrees.
     */
    public double getLatitudeInDegrees() {
        return degLat;
    }

    /**
     * @return the longitude, in degrees.
     */
    public double getLongitudeInDegrees() {
        return degLon;
    }

    /**
     * @return the latitude, in radians.
     */
    public double getLatitudeInRadians() {
        return radLat;
    }

    /**
     * @return the longitude, in radians.
     */
    public double getLongitudeInRadians() {
        return radLon;
    }

    @Override
    public String toString() {
        return "(" + degLat + "\u00B0, " + degLon + "\u00B0) = ("
                + radLat + " rad, " + radLon + " rad)";
    }

    /**
     * Computes the great circle distance between this GeoLocation instance and
     * the location argument.
     *
     * @param radius the radius of the sphere, e.g. the average radius for a
     *               spherical approximation of the figure of the Earth is approximately
     *               6371.01 kilometers.
     * @return the distance, measured in the same unit as the radius argument.
     */
    public double distanceTo(GeoLocation location, double radius) {
        return Math.acos(Math.sin(radLat) * Math.sin(location.radLat)
                + Math.cos(radLat) * Math.cos(location.radLat)
                * Math.cos(radLon - location.radLon)) * radius;
    }

    /**
     * <p>Computes the bounding coordinates of all points on the surface of a
     * sphere that have a great circle distance to the point represented by this
     * GeoLocation instance that is less or equal to the distance argument.</p>
     * <p>For more information about the formulae used in this method visit <a
     * href="http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates">
     * http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates</a>.</p>
     *
     * @param distance the distance from the point represented by this
     *                 GeoLocation instance. Must me measured in the same unit as the radius argument.
     * @param radius the radius of the sphere, e.g. the average radius for a
     *               spherical approximation of the figure of the Earth is approximately 6371.01 kilometers.
     * 
     * @return an array of two GeoLocation objects such that:<ul> <li>The
     * latitude of any point within the specified distance is greater or equal
     * to the latitude of the first array element and smaller or equal to the
     * latitude of the second array element.</li> <li>If the longitude of the
     * first array element is smaller or equal to the longitude of the second
     * element, then the longitude of any point within the specified distance is
     * greater or equal to the longitude of the first array element and smaller
     * or equal to the longitude of the second array element.</li> <li>If the
     * longitude of the first array element is greater than the longitude of the
     * second element (this is the case if the 180th meridian is within the
     * distance), then the longitude of any point within the specified distance
     * is greater or equal to the longitude of the first array element
     * <strong>or</strong> smaller or equal to the longitude of the second array
     * element.</li> </ul>
     */
    public GeoLocation[] boundingCoordinates(double distance, double radius) {

        if (radius < 0d || distance < 0d) {
            throw new IllegalArgumentException();
        }

        // angular distance in radians on a great circle
        double radDist = distance / radius;

        double minLat = radLat - radDist;
        double maxLat = radLat + radDist;

        double minLon, maxLon;
        if (minLat > MIN_LAT && maxLat < MAX_LAT) {
            double deltaLon = Math.asin(Math.sin(radDist) / Math.cos(radLat));
            minLon = radLon - deltaLon;
            if (minLon < MIN_LON) {
                minLon += 2d * Math.PI;
            }
            maxLon = radLon + deltaLon;
            if (maxLon > MAX_LON) {
                maxLon -= 2d * Math.PI;
            }
        } else {
            // a pole is within the distance
            minLat = Math.max(minLat, MIN_LAT);
            maxLat = Math.min(maxLat, MAX_LAT);
            minLon = MIN_LON;
            maxLon = MAX_LON;
        }

        return new GeoLocation[]{
                fromRadians(minLat, minLon), 
                fromRadians(maxLat, maxLon)
        };
    }
}


package com.nabisoft.tutorials.lbs.model;

import java.util.List;
import javax.persistence.EntityManager;

public abstract class AbstractFacade<T> {
    private Class<T> entityClass;

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public void create(T entity) {
        getEntityManager().persist(entity);
    }

    public T edit(T entity) {
        return getEntityManager().merge(entity);
    }

    public void remove(T entity) {
        getEntityManager().remove(getEntityManager().merge(entity));
    }
    
    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

    public List<T> findAll() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<T> findRange(int[] range) {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(range[1] - range[0]);
        q.setFirstResult(range[0]);
        return q.getResultList();
    }

    public int count() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }
    
    public void flush() {
        getEntityManager().flush();
    }
    
}


In PoiFacade.java we use the EarthRadius enum to specify either kilometers or miles as the unit for the longitude/latitude calculations.


package com.nabisoft.tutorials.lbs.utils.geo;

public enum EarthRadius {
    
    KILOMETERS (6371.01), 
    MILES      (3959);
    
    private double value;

    private EarthRadius(double value) {
            this.value = value;
    }
    
    public double value(){
        return this.value;
    }
}


package com.nabisoft.tutorials.lbs.model;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import com.nabisoft.tutorials.lbs.utils.geo.EarthRadius;
import com.nabisoft.tutorials.lbs.utils.geo.GeoLocation;

@Stateless
public class PoiFacade extends AbstractFacade<Poi> {
    @PersistenceContext(unitName = "lbsPU")
    private EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public PoiFacade() {
        super(Poi.class);
    }
    
    /**
     * Returns all POIs from DB which are within a given distance from a given GEO location.  
     * 
     * @param radius radius of the sphere (i.e. earth radius of 6371.01 kilometers or 3959 miles).
     * @param location center of the query circle.
     * @param distance radius of the query circle (same unit as radius).
     * @return POIs within the specified distance from location.
     */
    public List<Poi> findPoisWithinDistance(EarthRadius radius, GeoLocation location, double distance) {

        GeoLocation[] boundingCoordinates = location.boundingCoordinates(distance, radius.value());
        boolean meridian180WithinDistance = boundingCoordinates[0].getLongitudeInRadians() > boundingCoordinates[1].getLongitudeInRadians();

        TypedQuery<Poi> q;
        if (meridian180WithinDistance) {
            q = em.createNamedQuery("findPoisWithinDistanceOr", Poi.class);
        } else {
            q = em.createNamedQuery("findPoisWithinDistanceAnd", Poi.class);
        }

        q.setParameter(1, boundingCoordinates[0].getLatitudeInRadians());
        q.setParameter(2, boundingCoordinates[1].getLatitudeInRadians());
        q.setParameter(3, boundingCoordinates[0].getLongitudeInRadians());
        q.setParameter(4, boundingCoordinates[1].getLongitudeInRadians());
        q.setParameter(5, location.getLatitudeInRadians()); //TODO passing Math.sin(location.getLatitudeInRadians()) would require to change the NamedNativeQuery 
        q.setParameter(6, location.getLatitudeInRadians()); //TODO passing Math.cos(location.getLatitudeInRadians()) would require to change the NamedNativeQuery
        q.setParameter(7, location.getLongitudeInRadians());
        q.setParameter(8, distance / radius.value());

        List<Poi> pois = q.getResultList();

        return pois;
    }

}


The Poi Entity Class defines two NamedNativeQueries (findPoisWithinDistanceAnd and findPoisWithinDistanceOr) which allow to query the database for POIs within a given distance (they don't differ much from each other). Together the queries and the Poi class allow a better performance when querying the database assuming we have a lot of data (=POIs) in our database. To reach a better performance it helps to prevent the database from executing too many mathematical operation for each row in the database. To reach this the Poi Entity Class also stores additional data as fields in the database table ("POIS") instead of making the calculations directly on the database. Such fields are sinLatitude and cosLatitude. You could improve the performance even more by replacing sin(?5) and cos(?6) within the NamedNativeQueries by the corresponding values calculated in Java (see the TODO comments in PoiFacade.java and in Poi.java). I did not do this here for explanation purposes. The reason why our NamedNativeQueries use Ordinal Parameters (?index) is that in JPA you cannot use Named Parameters (:index) for NamedNativeQueries.

Please consider that the implementation makes sure the values stored on the database are radians, but the values returned by getLatitude() and getLongitude() are always degrees. This is because our NamedNativeQueries and the algorithm expect radians whereas on frontend side it is more comfortable for users to use degrees (i.e. the coordinates you get via Google Earth are typically in degrees). How does that work? Well, because all of our JPA mapping annotations are placed on fields only. Never mix placing JPA mapping annotations on both fields and getters/setters because the JPA spec says that all the mapping annotations in the hierarchy have to be placed either on fields or on getters. Unfortunately, the spec does not tell what happens if you don't follow that rule. So back to our case, it means that our implicit access type is AccessType.FIELD and not AccessType.PROPERTY. We could have also used @Access(AccessType.Field) on the Entity Class to explicitly set the access type to AccessType.FIELD.

For performance reasons it can make sense to create an index on a database table. In JPA 2.1 you will be able to do that in a standard way. Unfortunately, in JPA 2.0 there is no way to define indexes. EclipseLink offers you an @Index annotation for creating indexes. For using this annotation we have added the correct EclipseLink dependencies to our pom.xml file. That allows us to simply use the @Index annotation for creating indexes on the Poi Entity. We simply need to add the @Index annotation to both the longitude and latitude field. Below you find the Java sources for Poi.java:


package com.nabisoft.tutorials.lbs.model;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.eclipse.persistence.annotations.Index;

@Entity
@Cacheable(false)
@Table(name="POIS")
//@Access(AccessType.FIELD)
@NamedNativeQueries({
    @NamedNativeQuery(name="findPoisWithinDistanceAnd", query="SELECT * FROM pois WHERE (latitude >= ?1 AND latitude <= ?2) AND (longitude >= ?3 AND longitude <= ?4) AND acos(sin(?5) * sinlatitude + cos(?6) * coslatitude * cos(longitude - ?7)) <= ?8", resultClass=Poi.class),
    @NamedNativeQuery(name="findPoisWithinDistanceOr" , query="SELECT * FROM pois WHERE (latitude >= ?1 AND latitude <= ?2) AND (longitude >= ?3 OR  longitude <= ?4) AND acos(sin(?5) * sinlatitude + cos(?6) * coslatitude * cos(longitude - ?7)) <= ?8", resultClass=Poi.class)
    
    //TODO these ones would offer better performance
    //@NamedNativeQuery(name="findPoisWithinDistanceAnd", query="SELECT * FROM pois WHERE (latitude >= ?1 AND latitude <= ?2) AND (longitude >= ?3 AND longitude <= ?4) AND acos(?5 * sinlatitude + ?6 * coslatitude * cos(longitude - ?7)) <= ?8", resultClass=Poi.class),
    //@NamedNativeQuery(name="findPoisWithinDistanceOr" , query="SELECT * FROM pois WHERE (latitude >= ?1 AND latitude <= ?2) AND (longitude >= ?3 OR  longitude <= ?4) AND acos(?5 * sinlatitude + ?6 * coslatitude * cos(longitude - ?7)) <= ?8", resultClass=Poi.class)
})
@XmlRootElement(name = "poi")
public class Poi implements Serializable {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    @Column(nullable=false)
    @JsonIgnore
    private Date createdOn;
    
    @Column(nullable=false)
    @Index
    private Double longitude;
    
    @Column(nullable=false)
    @Index
    private Double latitude;    
    
    @Column(nullable=false)
    @JsonIgnore
    private Double sinLatitude;
    
    @Column(nullable=false)
    @JsonIgnore
    private Double cosLatitude;
    
    @Column(length=128)
    private String title;
    
    public Poi(){
        this.createdOn = new Date();
    }
    
    @XmlAttribute(name="id")
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    @XmlAttribute(name="longitude")
    public Double getLongitude() {
        return Math.toDegrees(longitude);
    }

    public void setLongitude(Double longitude) {
        this.longitude = Math.toRadians(longitude);
    }
    
    @XmlAttribute(name="latitude")
    public Double getLatitude() {
        return Math.toDegrees(latitude);
    }

    public void setLatitude(Double latitude) {
        this.latitude = Math.toRadians(latitude);
        if (latitude != null){
            this.setCosLatitude(Math.cos(this.latitude));
            this.setSinLatitude(Math.sin(this.latitude));
        }
    }
    
    public Double getSinLatitude() {
        return sinLatitude;
    }

    private void setSinLatitude(Double sinLatitude) {
        this.sinLatitude = sinLatitude;
    }

    public Double getCosLatitude() {
        return cosLatitude;
    }

    private void setCosLatitude(Double cosLatitude) {
        this.cosLatitude = cosLatitude;
    }
    
    public String getTitle() {
        return title;
    }
    
    public void setTitle(String title) {
        this.title = title;
    }
    
    @JsonProperty
    public Date getCreatedOn() {
        return createdOn;
    }
    
    @JsonIgnore
    public void setCreatedOn(Date createdOn) {
        this.createdOn = createdOn;
    }
    
}


5. Implementing the RESTful Web Services (REST)

In the previous steps we have implemented our JPA Poi Entity as well as a locally accessible (No-Interface View) Stateless Session Bean (PoiFacade). We also have added a JDBC Resource to our Glassfish installation. In this step we want to implement some RESTful Web Services which can be called from any remote client. Glassfish comes with Jersey - the JAX-RS (JSR 311) Reference Implementation for building RESTful Web Services (see http://jersey.java.net/). As you can guess we will use Jersey for implementing our REST Services. Our REST Services will allow to add, delete and query POIs. The REST Services use the Stateless Session Bean we have created in step 4. PoiService.java implements our REST Services (see below). The method findPlacesWithinDistance(...) is the most interesting one. It uses our PoiFacade to find the POIs we are looking for. Here is the source code:

package com.nabisoft.tutorials.lbs.service;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
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.Response.Status;
import javax.ws.rs.core.UriInfo;

import com.nabisoft.tutorials.lbs.model.Poi;
import com.nabisoft.tutorials.lbs.model.PoiFacade;
import com.nabisoft.tutorials.lbs.utils.geo.EarthRadius;
import com.nabisoft.tutorials.lbs.utils.geo.GeoLocation;

@Path("/poi")
@Stateless
public class PoiService {
        
    @EJB
    private PoiFacade poiFacade;
    
    @POST
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Consumes(MediaType.APPLICATION_JSON)
    @TransactionAttribute(TransactionAttributeType.NEVER)
    @Path("/")
    public Response createPoi(@Context UriInfo uriInfo, Poi b) throws URISyntaxException {
        
        this.poiFacade.create(b);
        if (b.getId() == null){
            //error processing...
        }
        return Response.created(new URI(uriInfo.getRequestUri()+"/"+b.getId())).entity(b).build();
    }
    
    @GET
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Path("/")
    public List<Poi> getAll() {
        return this.poiFacade.findAll();
    }
    
    @GET
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Path("/{latitude}/{longitude}/{distance}")
    public List<Poi> findPlacesWithinDistance(@PathParam("longitude") Double longitude, @PathParam("latitude") Double latitude, @PathParam("distance") Double distance) {
        GeoLocation myLocation = GeoLocation.fromDegrees(latitude, longitude);
        return this.poiFacade.findPoisWithinDistance(EarthRadius.KILOMETERS, myLocation, distance);
    }
    
    @GET
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Path("/{poiId}")
    public Response getById(@PathParam("poiId") Long id) {
        Poi b = this.poiFacade.find(id);
        if (b!=null){
            return Response.ok(b).build();
        }
        return Response.status(Status.NOT_FOUND).build();
    }
    
    @DELETE
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Path("/{poiId}")
    public Response deleteById(@PathParam("poiId") Long id) {
        Poi poiFound = this.poiFacade.find(id);
        if (poiFound != null){
            this.poiFacade.remove(poiFound);
            return Response.ok(poiFound).build();
        }
        return Response.status(Status.NOT_FOUND).build();
    }
    
    @PUT
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/{poiId}")
    public Response updateById(@PathParam("poiId") Long id, Poi poi) {      
        Poi poiFound = this.poiFacade.find(id);
        if (poiFound != null){
            poiFound.setTitle(poi.getTitle());
            this.poiFacade.edit(poiFound);
            return Response.status(Status.ACCEPTED).entity(poiFound).build();
        }
        return Response.status(Status.NOT_FOUND).build();
    }
    
}


For testing the services you need some test data in the database. You can use Google Earth to navigate to the POIs which you want to add to your database and get the longitude/latitude coordinates directly from Google Earth. If you want to skip this step you can simply use the following class for generating some POIs (test data) and saving them to your database. It creates some POIs and saves them to the database. You might know some of the places (POIs). To run the service simply hit http://localhost:8080/lbs-demo/service/poi/testdata/generate in your favorite browser. The service returns a list of all generated POIs either as JSON or XML.


package com.nabisoft.tutorials.lbs.service;

import java.util.ArrayList;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.servlet.ServletContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import com.nabisoft.tutorials.lbs.model.Poi;
import com.nabisoft.tutorials.lbs.model.PoiFacade;

@Path("/poi/testdata")
@Stateless
public class PoiTestData {
    
    @EJB
    private PoiFacade poiFacade;
    
    @Context
    ServletContext context;
    
    @GET
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Path("/generate")
    public List<Poi> generateTestData() {
        
        Poi poi;
        List<Poi> pois = new ArrayList<>();
       
        //create POIs     
        poi = new Poi();
        poi.setTitle("Statue of Liberty, New York (USA)");
        poi.setLatitude(40.6892);
        poi.setLongitude(-74.0444);        
        this.poiFacade.create(poi);
        pois.add(poi);
        
        poi = new Poi();
        poi.setTitle("Eiffel Tower, Paris (France)");
        poi.setLatitude(48.8583);
        poi.setLongitude(2.2945);        
        this.poiFacade.create(poi);
        pois.add(poi);
        
        poi = new Poi();
        poi.setTitle("Water Tower, Mannheim (Germany)");
        poi.setLatitude(49.484075);
        poi.setLongitude(8.475557);
        this.poiFacade.create(poi);
        pois.add(poi);
        
        poi = new Poi();
        poi.setTitle("Paradeplatz, Mannheim (Germany)");
        poi.setLatitude(49.487174);
        poi.setLongitude(8.466237);
        this.poiFacade.create(poi);
        pois.add(poi);
        
        poi = new Poi();
        poi.setTitle("Bismarckplatz, Heidelberg (Germany)");
        poi.setLatitude(49.409366);
        poi.setLongitude(8.693269);
        this.poiFacade.create(poi);
        pois.add(poi);
        
        poi = new Poi();
        poi.setTitle("SAP Headquarters, Walldorf (Germany)");
        poi.setLatitude(49.293487);
        poi.setLongitude(8.641966);
        this.poiFacade.create(poi);
        pois.add(poi);
        
        poi = new Poi();
        poi.setTitle("Cinemaxx, Mannheim (Germany)");
        poi.setLatitude(49.483663);
        poi.setLongitude(8.471590);
        this.poiFacade.create(poi);
        pois.add(poi);
        
        poi = new Poi();
        poi.setTitle("Brandenburg Gate, Berlin (Germany)");
        poi.setLatitude(52.516276);
        poi.setLongitude(13.377641);
        this.poiFacade.create(poi);
        pois.add(poi);
        
        return pois;
    }
}

6. Implementing the view with Java Server Pages (JSP)

We want to offer a very simply web frontend for our web application which allows us to play around a little with our implementation. For this tutorial I was a little lazy and I did not want to copy all content from my previous tutorials. Please see 7. Implementing the view with Java Server Pages (JSP) for more details about how to best reference jQuery, how to handly JSON and much more. There I have discussed a lot of important stuff you usually should know when you do web development. I will now list all the code without much of explanation.


styles.css contains all of our styles. We don't have many styles:

.crudForm{
  font-family: Arial,sans-serif;
  font-size: 13px;
}
  
.crudForm fieldset{
  padding-top: 1em;
  padding-bottom: 1em;
  border: 1px solid #0053A1;
  width: 600px;
}
  
.crudForm legend{
  color: #fff;
  background: #0053A1;
  border: 1px solid #0053A1;
  padding: 1px 5px;
}
      
.crudForm div{
  margin-bottom: 0.5em;
  text-align: left;
}
      
.crudForm label{
  width: 140px;
  float: left;
  text-align: left;
  margin-right: 10px;
  padding: 2px 0px;
  display: block;
}
  
.crudForm input[type=text], .crudForm input[type=password]{
  color: #0053A1;
  background: #fee3ad;
  border: 1px solid #0053A1;
  padding: 2px 5px;
  width:400px;
}

.crudForm textarea{
  color: #0053A1;
  background: #fee3ad;
  border: 1px solid #0053A1;
  padding: 2px 5px;
  width:400px;
  height:150px;
}
  
.crudForm .buttonRow{
  text-align: left;
}
  
.crudForm input[type=submit]{
  
}

.crudForm .querySelectors{
  margin-bottom: 20px;
}

.crudForm .inputFields.disabled{
    display:none;
}

Our head.jsp file is very important because it references the relevant JavaScript libraries (jQuery, json2, jquery.form.js, ...). It also references the relevant css files (styles.css, ...). Furthermore it makes John Resig's Simple JavaScript Templating available for all pages of our web application.

<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%>

<!-- jq integration from google cdn (content delivery network) -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.js" type="text/javascript"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js" type="text/javascript"></script>

<script src="<%=request.getContextPath() %>/web/js/json2.js" type="text/javascript"></script>
<script src="<%=request.getContextPath() %>/web/js/jquery.form.js" type="text/javascript"></script>

<link rel="stylesheet" type="text/css" href="<%=request.getContextPath() %>/web/css/styles.css" />
<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.0/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/>

<script type="text/javascript">
    // Simple JavaScript Templating
    // John Resig - http://ejohn.org/ - MIT Licensed
    // http://ejohn.org/blog/javascript-micro-templating/
    (function () {
        var cache = {};

        this.tmpl = function tmpl(str, data) {

          //
          // Figure out if we're getting a template, or if we need to
          // load the template - and be sure to cache the result.
          //
          var fn = !/\W/.test(str) ?
              cache[str] = cache[str] ||
                  tmpl(document.getElementById(str).innerHTML) :

            //
            // Generate a reusable function that will serve as a template
            // generator (and which will be cached).
            //
              new Function("obj",
                  "var p=[],print=function(){p.push.apply(p,arguments);};" +

                    // Introduce the data as local variables using with
                      "with(obj){p.push('" +

                    // Convert the template into pure JavaScript
                      str
                          .replace(/[\r\t\n]/g, " ")
                          .split("<%").join("\t")
                          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
                          .replace(/\t=(.*?)%>/g, "',$1,'")
                          .split("\t").join("');")
                          .split("%>").join("p.push('")
                          .split("\r").join("\\'")
                      + "');}return p.join('');");

          // Provide some basic currying to the user
          return data ? fn(data) : fn;
        };
      })();
      
      (function(){
          var charsToReplace = {
                '&': '&amp;',
                '<': '&lt;',
                '>': '&gt;'
          };

          this.escapeHtml = function(str) {
              if (!str)
                  return null;
              return str.replace(/[&<>]/g, function(matchedChar) {
                    return charsToReplace[matchedChar] || matchedChar;
                });
          }
      })();
      
</script>

<script type="text/tmpl" id="renderedPoiTemplate">
    <br/>
    <% for (var i=0; i < items.length; i++) { %>
    <table border="1">
        <tr>
            <td>ID:</td>
            <td><%=items[i].id%></td>
        </tr>
        <tr>
            <td>Created on:</td>
            <td><%=new Date(items[i].createdOn)%></td>
        </tr>
        <tr>
            <td>Title:</td>
            <td><%=escapeHtml(items[i].title)%></td>
        </tr>

        <tr>
            <td>Longitude:</td>
            <td><%=items[i].longitude%></td>
        </tr>
        <tr>
            <td>Latitude:</td>
            <td><%=items[i].latitude%></td>
        </tr>

        <% var files = items[i].attachments; %>
        
    </table>
    <br/>
    <% } %>
</script>

The index.jsp does nothing but implementing a simple starting page which displays links too all the other pages:

Poi Demo (index.jsp)
Poi Demo (index.jsp)
<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%>
<!DOCTYPE html>
<html>
    <head>
        <title>POI Demo</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">        
        <%@ include file="/WEB-INF/jsp/includes/head.jsp" %>
    </head>
    <body>
        
        <div>
            <a href="./web/create.jsp">Create POI</a>
        </div>
        
        <div>
            <a href="./web/query.jsp">Query POI</a>
        </div>
        
        <div>
            <a href="./web/update.jsp">Update POI</a>
        </div>
        
        <div>
            <a href="./web/delete.jsp">Delete POI</a>
        </div>
        
    </body>
</html>

create.jsp allows us to add new POIs to the database:

Create POI (create.jsp)
Create POI (create.jsp)
<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%>
<!DOCTYPE html>
<html>
    <head>
        <title>Create</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <%@ include file="/WEB-INF/jsp/includes/head.jsp" %>
        
        <script type="text/javascript">
        $(function(){
            "use strict";
             
            var ctxPath = "<%=request.getContextPath() %>";
            
            $(document.forms['createForm']).submit(function(event){
                var data = {
                    longitude: parseFloat(this.longitude.value),
                    latitude: parseFloat(this.latitude.value),
                    title: this.title.value
                }; 
                var destinationUrl = ctxPath+"/service/poi";
                 
                $.ajax({
                    url: destinationUrl,
                    type: "POST",
                    data: JSON.stringify(data),
                    contentType: "application/json",
                    cache: false,
                    dataType: "json",
                     
                    success: function (data, textStatus, jqXHR){
                        alert("POI created successfully.");
                        $("#queryResults").html('');
                        var items = [];
                        items.push(data);
                        var target = document.getElementById("results");
                        target.innerHTML = tmpl("renderedPoiTemplate", {items: items});
                    },
                     
                    error: function (jqXHR, textStatus, errorThrown){
                        alert("error - HTTP STATUS: "+jqXHR.status);
                         
                    },
                     
                    complete: function(jqXHR, textStatus){
                        //alert("complete");
                        //i.e. hide loading spinner
                    },
                     
                    statusCode: {
                        404: function() {
                          alert("service not found");
                        }
                    }
                     
                 
                });
                 
                //event.preventDefault();
                return false;
            });
             
            
        });
        </script>
        
    </head>
    <body>

        <div class="crudForm">
          <form id="createForm" name="createForm" action="">
            <fieldset>
              <legend>Create POI</legend>

              <div>
                <label for="longitude">Longitude</label> 
                <input type="text" id="longitude" name="longitude"/>
              </div>

              <div>
                <label for="latitude">Latitude</label> 
                <input type="text" id="latitude" name="latitude"/>
              </div>
              
              <div>
                <label for="title">Title</label> 
                <input type="text" id="title" name="title"/>
              </div>

              <div class="buttonRow">
                <input type="submit" value="Create" />
              </div>

            </fieldset>
          </form> 
        </div>
        
        <div id="results">    
        </div>
        
    </body>
</html>


delete.jsp allows to delete POIs from the database:

Delete POI (delete.jsp)
Delete POI (delete.jsp)
<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%>
<!DOCTYPE html>
<html>
    <head>
        <title>Delete</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <%@ include file="/WEB-INF/jsp/includes/head.jsp" %>
        
        <script type="text/javascript">
        $(function(){
            "use strict";
             
            var ctxPath = "<%=request.getContextPath() %>";
            
            $(document.forms['deleteForm']).submit(function(event){
                var data = {
                    id : parseInt(this.poiId.value)
                }; 
                var destinationUrl = ctxPath+"/service/poi/"+data.id;
                 
                $.ajax({
                    url: destinationUrl,
                    type: "DELETE",
                    //data: JSON.stringify(data),
                    //contentType: "application/json",
                    cache: false,
                    dataType: "json",
                     
                    success: function (data, textStatus, jqXHR){
                        alert("POI deleted successfully.");
                        $("#queryResults").html('');
                            
                        var items = [];
                        items.push(data);
                        var target = document.getElementById("results");
                        target.innerHTML = tmpl("renderedPoiTemplate", {items: items});
                    },
                     
                    error: function (jqXHR, textStatus, errorThrown){
                        alert("error - HTTP STATUS: "+jqXHR.status);
                         
                    },
                     
                    complete: function(jqXHR, textStatus){
                        //alert("complete");
                        //i.e. hide loading spinner
                    },
                     
                    statusCode: {
                        404: function() {
                          alert("service not found");
                        }
                    }
                     
                 
                });
                 
                //event.preventDefault();
                return false;
            });
             
            
        });
        </script>
        
    </head>
    <body>

        <div class="crudForm">
          <form id="deleteForm" name="deleteForm" action="">
            <fieldset>
              <legend>Delete POI</legend>

              <div>
                <label for="poiId">POI Id</label> 
                <input type="text" id="poiId" name="poiId"/>
              </div>

              <div class="buttonRow">
                <input type="submit" value="Delete" />
              </div>

            </fieldset>
          </form> 
        </div>
        
        <div id="results">    
        </div>
        
    </body>
</html>


query.jsp is a little more complex compared to the others. It allows to query the database for POIs. You have different options, i.e. by id, by geo or simply all POIs.

Query POIs by GEO coordinates and distance (query.jsp)
Query POIs by GEO coordinates and distance (query.jsp)
<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%>
<!DOCTYPE html>
<html>
    <head>
        <title>Welcome to the demo</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">        
        <%@ include file="/WEB-INF/jsp/includes/head.jsp" %>
        
        <script type="text/javascript">
            $(function(){
                
               var ctxPath = "<%=request.getContextPath() %>";
               $('#querySelectors input:radio').change(function(){
                    if ($(this).is(':checked')) {
                        var val = $(this).val();
                        if (val === "all"){
                            $('#byGeoFields, #byIdFields').slideUp();
                        }else if (val === "byGeo"){
                            $('#byIdFields').slideUp(function(){
                               $('#byGeoFields').slideDown(); 
                            });
                        }else if (val === "byId"){
                            $('#byGeoFields').slideUp(function(){
                               $('#byIdFields').slideDown(); 
                            });
                        }
                    }
                });
                
                $(document.forms['readForm']).submit(function(event){
                    var val = $('input[name=querySelector]:checked', '#querySelectors').val()
                    
                    var destinationUrl ="";
                    
                    if (val === "all"){
                        destinationUrl = ctxPath+"/service/poi";
                        
                    }else if (val === "byGeo"){
                        var longitude = parseFloat(this.longitude.value);
                        var latitude  = parseFloat(this.latitude.value);
                        var distance  = parseFloat(this.distance.value);
                        destinationUrl = ctxPath+"/service/poi/"+latitude+"/"+longitude+"/"+distance;
                        
                    }else if (val === "byId"){
                        destinationUrl = ctxPath+"/service/poi/"+this.id.value;
                    }
                    
                    $.ajax({
                        url: destinationUrl,
                        type: "GET",
                        //contentType: "application/json",
                        cache: false,
                        dataType: "json",

                        success: function (data, textStatus, jqXHR){
                            $("#queryResults").html('');
                            
                            var items = data;
                            if (!$.isArray(data)){
                                items = [];
                                items.push(data);
                            }
                            var target = document.getElementById("results");
                            target.innerHTML = tmpl("renderedPoiTemplate", {items: items});
                            
                        },

                        error: function (jqXHR, textStatus, errorThrown){
                            alert("error - HTTP STATUS: "+jqXHR.status);

                        },

                        complete: function(jqXHR, textStatus){
                            //alert("complete");
                            //i.e. hide loading spinner
                        },

                        statusCode: {
                            404: function() {
                              alert("service not found");
                            }
                        }
                    });
                 
                    //event.preventDefault();
                    return false;
                });
            });
        </script>
        
    </head>
    <body>
        
        <div class="crudForm">
          <form id="readForm" name="readForm" action="">
            <fieldset>
              <legend>Query POI</legend>
              
              <div id="querySelectors" class="querySelectors">
                <div>
                    <label for="radioAll">All</label>
                    <input type="radio" id="radioAll" name="querySelector" value="all" checked="checked">
                </div>

                <div>
                    <label for="radioByGeo">by Geo</label>
                    <input type="radio" id="radioByGeo" name="querySelector" value="byGeo">
                </div>

                <div>
                    <label for="radioById">by Id</label>
                    <input type="radio" id="radioById" name="querySelector" value="byId">
                </div>
              </div>
              
              <div id="byGeoFields" class="inputFields disabled">
                <div>
                  <label for="longitude">Longitude</label> 
                  <input type="text" id="longitude" name="longitude"/>
                </div>

                <div>
                  <label for="latitude">Latitude</label> 
                  <input type="text" id="latitude" name="latitude"/>
                </div>

                <div>
                  <label for="distance">distance (Km)</label> 
                  <input type="text" id="distance" name="distance"/>
                </div>
              </div>
              
              <div id="byIdFields" class="inputFields disabled">
                <div>
                  <label for="id">Id</label> 
                  <input type="text" id="id" name="id"/>
                </div>
              </div>

              <div class="buttonRow">
                <input type="submit" value="Read" />
              </div>

            </fieldset>
          </form> 
        </div>
        
        <div id="results">    
        </div>
        
    </body>
</html>


update.jsp allows to change the title of a POI in the database. I decided to add this in oder to make our CRUD services complete.

Update POI (update.jsp)
Update POI (update.jsp)
<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%>
<!DOCTYPE html>
<html>
    <head>
        <title>Update</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <%@ include file="/WEB-INF/jsp/includes/head.jsp" %>
        
        <script type="text/javascript">
        $(function(){
            "use strict";
            
            var ctxPath = "<%=request.getContextPath() %>";
             
            $(document.forms['updateForm']).submit(function(event){
                var data = {
                    id : parseInt(this.poiId.value),
                    title: this.title.value
                }; 
                var destinationUrl = ctxPath+"/service/poi/"+data.id;
                 
                $.ajax({
                    url: destinationUrl,
                    type: "PUT",
                    data: JSON.stringify(data),
                    contentType: "application/json",
                    cache: false,
                    dataType: "json",
                     
                    success: function (data, textStatus, jqXHR){
                        alert("POI updated successfully.");
                            
                        var items = [];
                        items.push(data);
                        var target = document.getElementById("results");
                        target.innerHTML = tmpl("renderedPoiTemplate", {items: items});
                    },
                     
                    error: function (jqXHR, textStatus, errorThrown){
                        alert("error - HTTP STATUS: "+jqXHR.status);
                         
                    },
                     
                    complete: function(jqXHR, textStatus){
                        //alert("complete");
                        //i.e. hide loading spinner
                    },
                     
                    statusCode: {
                        404: function() {
                          alert("service not found");
                        }
                    }
                     
                 
                });
                 
                //event.preventDefault();
                return false;
            });
             
            
        });
        </script>
        
    </head>
    <body>

        <div class="crudForm">
          <form id="updateForm" name="updateForm" action="<%=request.getContextPath() %>/service/update/poi" method="put">
            <fieldset>
              <legend>Update POI</legend>

              <div>
                <label for="poiId">POI Id</label> 
                <input type="text" id="poiId" name="poiId"/>
              </div>
                            
              <div>
                <label for="title">Title</label> 
                <input type="text" id="title" name="title"/>
              </div>

              <div class="buttonRow">
                <input type="submit" value="Update" />
              </div>

            </fieldset>
          </form> 
        </div>
        
        <div id="results">    
        </div>
        
    </body>
</html>



7. Running the application

When deploying the application to your local Glassfish just call http://localhost:8080/lbs-demo/ and see what happens (I assume you have deployed to "lbs-demo", the HTTP port is 8080 and the HTTPS port is 8181). From there you can choose what to do next. If you want to generate some test data then simply call http://localhost:8080/lbs-demo/service/poi/testdata/generate. To get a list of all POIs available in the database you can simply call http://localhost:8080/lbs-demo/service/poi/ instead of navigating to the Query POI page and choosing what you want from there.


8. Download Source Code

Make sure you have already installed Glassfish 3.1.2.2. You should also have a PostgreSQL already installed on your machine. You can download the maven project here. The archive contains a launch directory which you can delete. It just offers a launch configuration for Eclipse (maven clean install). You should be able to import the Maven project into the IDE of your choice (Eclipse, Netbeans, ...) and try out our little application yourself.


Comments
  • If you like this tutorial help us to maintain it:
  • And don't forget to let others know about it:
still not correct
posted by Dakshina Murthy Gandikota
Fri Aug 21 20:55:32 GMT 2015
 var transform = {'tag':'table border=1','html': '&lt;tr&gt;&lt;td&gt;\${id}&lt;/td&gt;&lt;/tr&gt; &lt;tr&gt;&lt;td&gt;\${createdOn}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt; \${title}&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;\${longitude}&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;\${latitude}&lt;/td&gt;&lt;/tr&gt;'};
correction
posted by Dakshina Murthy Gandikota
Fri Aug 21 20:52:31 GMT 2015
The browser parsed the html tags. The corrected transform is

<pre>
 var transform = {'tag':'table border=1','html': '<tr><td>\${id}</td></tr> <tr><td>\${createdOn}</td></tr><tr><td> \${title}</td> </tr><tr><td>\${longitude}</td> </tr><tr><td>\${latitude}</td></tr>'};

</pre>
json2html
posted by Dakshina Murthy Gandikota
Fri Aug 21 20:49:32 GMT 2015
Using json2html (www.json2html.com) the transformation is  quite simple. Here is a snippet from
query.jsp

 var newItems = [];
 for (var i=0; i < items.length;i++) {
        var item = items[i];
        item.createdOn = new Date(item.createdOn);
        newItems.push(item);
}

var transform = {'tag':'table border=1','html': '<tr><td>\${id}</td></tr> <tr><td>\${createdOn}</td></tr><tr><td> \${title}</td> </tr><tr><td>\${longitude}</td> </tr><tr><td>\${latitude}</td></tr>'};
var html = json2html.transform(newItems,transform);
target.innerHTML=html;
MyJacksonJasonProvider
posted by Dakshina Murthy Gandikota
Fri Aug 21 18:21:19 GMT 2015
Thank you for the excellent tutorial. Don't understand what MyJacksonJasonProvider is for. The application ran fine even without it.
Regards
Why do not use postgis
posted by nono
Wed Dec 11 15:17:32 GMT 2013
The same article with using Postgis will be great. 
I don't know if it's possible to use Postgis with glassfish 4 but it ok, you don't have to redifine lat,lon but just use a postgis geometry point and spatial query like st_distance(). 

It will simplify a lot all your API.