Wednesday, September 18, 2013

Using Google App Engine Endpoints and JPA to interact with data

The following example assumes you have a google account, have downloaded GAE (Google App Engine) dev kit and eclipse plugin.  The goal is to show how to create a Google End Point interface that interacts with records in google datastore thru JPA. Our example shows a car catalog which persists "Car" objects and has a method to query all of them too. The classes to be created are

Car.java -> represents an Entity within google datastore
EMF.java -> represents a static class to generate Entity Manager Factory.
CarHandler.java -> Acts as a controller between the End Point and the datastore.
CarEndPoint.java -> Implements the Car google API endpoint.

The following diagram shows up the classes and their relationships


Step 1
Create a new Google Web App within eclipse as follows

After generating your project make sure to include the following jars related to JPA within the directory WAR/WEB-INF/lib.  All of these can be obtained from the lib directory where app engine is installed.



Step 2
Generate the code for the Car Entity, note the annotations show how this pojo becomes a datastore entity
package com.salamander.cars;

import javax.persistence.Entity;
import com.google.appengine.api.datastore.Key;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Car {

private String brand;
private String model;
private Integer year;
@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
public Key getKey() {
        return key;
    }
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}

}

Step 3
Generate the entity manager factory class EMF.java
package com.salamander.cars;

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class EMF {
    private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("transactions-optional");

    private EMF() {}

    public static EntityManagerFactory get() {
        return emfInstance;
    }
}

Step 4
Generate the Controller aka CarHandler.java. This class implements 2 methods, one to insert new records to the datastore and another to retrieve all the records within it.  Note the use of JPA packages.

package com.salamander.cars;

import java.util.List;

import javax.persistence.EntityManager;

public class CarHandler {

public void addCar(String _sBrand, String _sModel, int _iYear){

Car car = new Car();
car.setBrand(_sBrand);
car.setModel(_sModel);
car.setYear(new Integer(_iYear));

EntityManager em = EMF.get().createEntityManager();
em.getTransaction().begin();
em.persist(car);
em.getTransaction().commit();

}

public List<Car> getAllCars(){

EntityManager em = EMF.get().createEntityManager();
em.getTransaction().begin();
List<Car> result = em.createQuery( "select car from Car car", Car.class ).getResultList();

em.getTransaction().commit();

return result;
}

}

Step 5
Generate the actual endpoint class CarEndPoint.java.  This API exposes 2 API Methods car.add and readall. The first one follows the pattern car/add/{brand}/{model}/{year} to add a new car to the datastore. Whereas the pattern for the second API method is just readall.

package com.salamander.cars;

import java.util.List;
import javax.inject.Named;
import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiMethod.HttpMethod;

@Api(name = "car"
, version = "v1"
, clientIds = {com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID}
, audiences = {"vehiclecarx.appspot.com"}
, scopes = {"https://www.googleapis.com/auth/userinfo.email"}
)
public class CarEndPoint {

@ApiMethod(name = "car.add"
, path = "car/add/{brand}/{model}/{year}"
, httpMethod = HttpMethod.PUT)
public void addCar(@Named("brand") String _sbrand
, @Named("model") String _smodel
, @Named("year") String _syear) {
 
CarHandler ch = new CarHandler();
ch.addCar(_sbrand, _smodel, Integer.parseInt(_syear));
}  

// url -->  https://vehiclecarx.appspot.com/_ah/api/car/v1/readall
// sample url --> https://vehiclecarx.appspot.com/_ah/api/car/v1/readall?fields=items(brand%2Cmodel%2Cyear)
@ApiMethod(name = "readall"
, path = "readall"
, httpMethod = HttpMethod.GET)
public List <Car> readall(){

CarHandler ch = new CarHandler();
List<Car> cars = ch.getAllCars();
 
    return cars;
}
}

Last but not least tthree things are missing here.  Set up your app web.xml file, setup persistence.xml file and create your google app id.  Web.xml should look as below

<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  
 <servlet>
  <servlet-name>SystemServiceServlet</servlet-name>
  <servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>
  <init-param>
   <param-name>services</param-name>
   <param-value>com.salamander.cars.CarEndPoint</param-value>
  </init-param>
 </servlet>
 <servlet-mapping>
  <servlet-name>SystemServiceServlet</servlet-name>
  <url-pattern>/_ah/spi/*</url-pattern>
 </servlet-mapping>
</web-app>

persistence.xml file should be located in the directory war/WEB-INF/META-INF and should look as

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

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </properties>
    </persistence-unit>
</persistence>

The google application id can be generated in google administration console (URL  https://appengine.google.com/).  And just like any other GAE project when deploying tie your app to the id you created within your account in GAE console.

After deploying you should be able to use google's apis explorer and test your scenarios.  In my case my google api explorer corresponds to https://developers.google.com/apis-explorer/?base=https://vehiclecarx.appspot.com/_ah/api#p/

and shows as 

Click on any of the exposed methods and happy testing.  Below is the result after testing the readall method. Note how google shows the url you could use in your clients to call any of the exposed APIs.










No comments:

Post a Comment