Come unto me all ye who are weary of heavy-laden clients and I will give you REST.
Continued from GWT with JAX-RS (aka RPC/REST) Part 2
OK, the cast of the technology mix is
- JAX-RS (either Resteasy or Jersey on the server side)
- JAX-RS + GWT = RestyGWT on the client-side
- JPA on the server-side
- JAXB over JAX-RS on both GWT client and server-side.
- Jackson JSON processor on server-side.
I feel those acronyms are intimidating, but they are actually not difficult to implement. If you could understand the intricacies of GWT-RPC, you should have no problem understanding the soup mix above.
JPA remains to be the most difficult part of the mix to learn/master (most difficult but not difficult). Mastering GWT is actually the more difficult part than JPA. However, if you have no desire for your server to connect to a JDBC-enabled database, you do not need to concern with JPA.
This is your JAX-RS POJO:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement (name="employee")
public class Employee
implements Serializable {
@XmlAttribute
private Long eid = null;
@XmlAttribute
private String name = null;
}
This is the same POJO, if your app is obsessed with the order of appearance:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement (name="employee")
@XmlType(propOrder = {
"eid",
"name"
}
public class Employee implements Serializable {
@XmlAttribute
private Long eid = null;
@XmlAttribute
private String name = null;
}
This is the POJO with public getters/setters (generated by Eclipse) so to enable RestyGWT to operate on it. Hoever, RestyGWT would not need the public getters/setters if the fields are public instead of private.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement (name="employee")
@XmlType(propOrder = {
"eid",
"name"
}
public class Employee
implements Serializable {
@XmlAttribute
private Long eid = null;
@XmlAttribute
private String name = null;
public Long getEid() {
return eid;
}
public void setEid (Long eid ) {
this. eid = eid ;
}
public String getName () {
return name ;
}
public void setName (String name ) {
this. name = name ;
}
}
And finally, this is your Three-in-one POJO to accommodate JPA.
@Entity
@Table(name="Employee", schema="bombay")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement (name="employee")
@XmlType(propOrder = {
"eid",
"name"
}
public class Employee
implements Serializable {
@XmlAttribute
@Id
private Long eid = null;
@XmlAttribute
private String name = null;
public Long getEid() {
return eid;
}
public void setEid (Long eid ) {
this. eid = eid ;
}
public String getName () {
return name ;
}
public void setName (String name ) {
this. name = name ;
}
}
Let's create another POJO:
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement (name="employees")
public class EmployeeList {
@XmlElement(name="enployee")
private List<Employee> employees;
public List<Employee> getEmployees() {
if (employees==null)
employees = new ArrayList<Employee>();
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
But, WHERE's THE JSON? JSON output will be borrowing the JAXB XML annotation. The following is the server side service API interface.
@Path("/employee")
@Produces({"application/xml", "application/json"})
public interface EmployeeServiceAPI {
/**
* Find all employee ids with the given name
*/
@GET
@Path("/eids/name")
LongList getEmployeeIdsWithName(@QueryParam ("name")String name) ;
/**
* Get employee wit Id
*/
@GET
@Path("/employee/{id}")
Employee getEmployeeWithId(@PathParam ("eid")Long eid) ;
/**
* Get project list for an employee
*/
@POST
@Path("/projects/employee")
ProjectList getProjects(Employee employee) ;
}
And this is the async service API on the GWT Client.
@Path("/employee")
@Consumes({"application/json"}) // put in a http header to tell server to return me JSON
public interface EmployeeServiceAPI {
/**
* Find all employee ids with the given name
*/
@GET
@Path("/eids/name")
LongList getEmployeeIdsWithName(@QueryParam ("name")String name, MethodCallback <LongList > callback) ;
/**
* Get employee wit Id
*/
@GET
@Path("/employee/{id}")
Employee getEmployeeWithId(@PathParam ("eid")Long eid, MethodCallback <Employee > callback) ;
/**
* Get project list for an employee
*/
@POST
@Path("/projects/employee")
ProjectList getProjects(Employee employee, MethodCallback <ProjectList > callback) ;
}
The server-side implementation to access the database.
public class EmployeeServiceAPIImpl
implements EmployeeServiceAPI {
@Override
public Employee getEmployeeIdsWithName(Long eid) {
Employee emp = new Employee();
EntityManager em = null;
try {
em = EMF.get().createEntityManager();
Query q = em.createQuery(
"SELECT n FROM Employee n WHERE n.status = 'active' AND n.eid = ?1"
).setParameter(1, eid);
Employee emp = q.getSingleResult();
return emp;
}
finally{
if (em!=null)
em.close();
}
}
......
}
This is how your GWT client will call the async REST service:
public class EmployeePresenter{
static public EmployeeAsyncServiceAPI enpServiceAPI = GWT.create(EmployeeAsyncServiceAPI.class);
public void search(Long eid) {
enpServiceAPI.getEmployeeIdsWithName(eid, new EmployeeCallBack());
}
public class EmployeeCallBack
implements MethodCallback{
@Override
public void onFailure(Method method, Throwable exception) {
Window.alert("Error accessing data");
}
@Override
public void onSuccess(Method method, Employee response) {
EmployeePresenter.this.view.setEmployeeRecords(
response.getEmployee());
if (response.getEmployee().size()==0) {
Window.alert("No records found");
}
}
}
}
Let's say function search(Long eid) got called as
search(12529);
RestyGWT code generated by GWT.create() will send this request over the URL
/employee/employee/12529
RestyGWT would find the phrase in the async service API
@Consumes({"application/json"})
and proceeds to place a header in the HTTP request
Accepts: application/json
When the RestEasy Listener intercepts the request, it would search for the class implementing EmployeeServiceAPI and finds EmployeeServiceAPIImpl. RestEasy loads that class which proceeds to service the request.
Therefore, you could use the
browser to debug your web service independent of the GWT client.
You could use python or PHP to poke the server too. May be, your QA hates Java and loves PHP - so he/she could write unit tests using PHP.
Furthermore, now, you do not have to bother with SmartGWT XML or JSON datasource and you can take full control over your POJO datasource. It's really frustrating to have to take lots of time going thro nooks and corners whenever you need to tweak your datasource a lil' bit. Now you don't have to.
Here is your web.xml to tell RestEasy to do its
strut.
<web-app>
<display-name>Employee Service</display-name>
<!-- Auto scan REST service -->
<context-param>
<param-name>resteasy.scan</param-name>
<param-value>true</param-value>
</context-param>
<!-- this need same with resteasy servlet url-pattern -->
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/CurryMuttonOilDrilling/</param-value>
</context-param>
<listener>
<listener-class>
org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
</listener-class>
</listener>
<servlet>
<servlet-name>resteasy-servlet</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>resteasy-servlet</servlet-name>
<url-pattern>/CurryMuttonOilDrilling/*</url-pattern>
</servlet-mapping>
</web-app>
Therefore, according to the web.xml, the actual URL path sent to the server is:
/CurryMuttonOilDrilling/employee/employee/12529
And notice the presence of Jackson json provider in the following maven dependency?
<properties>
<resteasy.version>2.3.4.Final</resteasy.version>
<gwt.version>2.4.0</gwt.version>
</properties>
<repositories>
<repository>
<id>fusesource-snapshots</id>
<name>Fusesource Snapshots</name>
<url>http://repo.fusesource.com/nexus/content/repositories/snapshots</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${gwt.version}</version>
</dependency>
<dependency>
<groupId>com.sencha.gxt</groupId>
<artifactId>gxt</artifactId>
<version>2.2.5-gwt22</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/GWT/gxt-2.2.5-gwt22.jar</systemPath>
</dependency>
<dependency>
<groupId>com.googlecode.mvp4g</groupId>
<artifactId>mvp4g</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>${resteasy.version}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>org.fusesource.restygwt</groupId>
<artifactId>restygwt</artifactId>
<version>1.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Final</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-gwt</artifactId>
<version>13.0-rc1</version>
</dependency>
<!--dependency> <groupId>com.sun.faces</groupId> <artifactId>mojarra-jsf-api</artifactId>
<version>2.0.0-b04</version> </dependency> <dependency> <groupId>com.sun.faces</groupId>
<artifactId>mojarra-jsf-impl</artifactId> <version>2.0.0-b04</version> </dependency -->