Introduction
This article shows how to make a simple, end-to-end, mobile enterprise application for developers who are just learning how to make enterprise applications or are learning how to use a combination of the components described here in an application. Our example includes a RESTful web service that provides access to Java entity beans on Oracle’s
Glassfish application server and a simple Android client to consume it.
Serialization of the entity beans between Android and Glassfish is encoded
through JSON. All code is in Java unless otherwise specified.
Background
Earlier
this year (2012), during a student assignment at Humber College in Etobicoke,
Ontario, Canada, I was suddenly struck by the clever idea that one could
simultaneously demonstrate the consumption of our RESTful web services with an
Android application and the serialization of Java entity beans through JSON. It
was only a few weeks later that my Java instructor informed me to my dismay
that this was not an original idea even with his own past classes. Nonetheless,
I did not find another article on the web that duplicates all that this one
does (although my search may not have been thorough enough); therefore, I bestow
the precious insight herein, gleaned by the sweat of my brow, as a warm light through
all mankind to share. (Self-deprecating, Avengers-inspired sarcasm intended.)
The code shown
has been extracted from my assignment and simplified for this presentation.
Using the code
We assume that our audience knows how to make simple examples of a database, an EJB-hosted RESTful web service, and an Android Activity. However, one should be able to get the gist of the code with just a little programming experience.
For the NetBeans web service project, I added the free, current, jettison.jar from jettison.codehaus.org. It includes the JSON STaX implementation, which contains the BadgerFish mapping of JSON to XML.
Data Model
Database
The underlying database table has just enough fields for the purposes of this article: an identifier, a
label, and an info field to update. Here is the MySQL DDL script snippet that describes it:
CREATE TABLE `simpleuser` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
Entity
I
generated the corresponding entity class with the “New > Entity Classes from
Database” command in NetBeans.
public class SimpleUser implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@NotNull
@Column(name = "id")
private Integer id;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 255)
@Column(name = "name")
private String name;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 255)
@Column(name = "email")
private String email;
...
}
Business Logic
The RESTful
web service provides the basic CRUD access operations to the entity beans,
which are the resources in the REST paradigm: create, retrieve, update, delete.
Glassfish
includes Jersey, the Reference Implementation for building RESTful web services
in Java. We also add the JSON/XML mapping provided by BadgerFish in the
Jettison library available at jettison.codehaus.org.
Our web
service is implemented by a stateless Enterprise JavaBean, SimpleUserResource.
We have also set the REST ApplicationPath attribute to be “rest”.
Create
Our
adopted REST paradigm calls for resources to be created by the HTTP request
POST. This
snippet includes the start of our implementation class.
import java.net.URI;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.*;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.xml.bind.JAXBElement;
import org.codehaus.jettison.json.JSONArray;
@Path("/appuser")
@Produces(MediaType.WILDCARD)
@Consumes(MediaType.WILDCARD)
@Stateless
public class SimpleUserResource {
@PersistenceContext(unitName = "SimpleUserRESTService-warPU")
private EntityManager entityManager;
@Context
private UriInfo uriInfo;
@POST
@Consumes({MediaType.APPLICATION_JSON})
public Response createSimpleUser(JAXBElement<SimpleUser> userJAXB) {
SimpleUser user = userJAXB.getValue();
user.setId(0);
entityManager.persist(user);
entityManager.flush();
URI userURI = uriInfo.getAbsolutePathBuilder().path(user.getId().
toString()).build();
return Response.created(userURI).build();
}
...
The @Path
attribute specifies the URL path component that indicates to Glassfish that
containing HTTP requests should be directed to this EJB.
The @Consumes
and @Produces
attributes specify the acceptable MIME types of the content of
the HTTP requests that are received and responses that are sent, respectively.
(WILDCARD indicates all types, of course.) An attribute specified for a method
overrides a corresponding one for its class.
The JAXBElement
parameters wraps the posted entity bean after BadgerFish has converted the
bean’s HTTP JSON representation to XML and Project JAXB has converted the XML
representation to a SimpleUser
class instance.
Retrieval
Our
adopted REST paradigm calls for resources to be retrieved by the HTTP request
GET.
There are
two basic types of retrieval. One is to retrieve a specific resource.
@GET
@Path("{id}/")
@Produces({MediaType.APPLICATION_JSON})
public SimpleUser retrieveSimpleUser(@PathParam("id") int id) {
URI userURI = uriInfo.getAbsolutePathBuilder().build();
if (id < 1) {
ResponseBuilder rBuild = Response.status(
Response.Status.NOT_FOUND).entity(
userURI.toASCIIString());
throw new WebApplicationException(rBuild.build());
}
SimpleUser user = entityManager.find(SimpleUser.class, id);
if (user == null) {
ResponseBuilder rBuild = Response.status(
Response.Status.NOT_FOUND).entity(
userURI.toASCIIString());
throw new WebApplicationException(rBuild.build());
}
return user;
}
The {id}
template in @Path
specifies that an
URL in
a HTTP GET request with an extra path component after “appuser” indicates that
retrieveSimpleUser()
will be invoked to handle the request with the text of
that extra component being passed as an integer in the parameter named id
.
The returned SimpleUser instance will be converted
to XML by JAXB and JSON by BadgerFish before being sent to the client in the
resulting HTTP response.
Another type of retrieval is to fetch the list of
resources located at a specific directory.
@GET
@Produces(MediaType.APPLICATION_JSON)
public JSONArray retrieveSimpleUsers() {
Query query = entityManager.createNamedQuery("SimpleUser.findAll");
List<SimpleUser> userList = query.getResultList();
JSONArray resUriArray = new JSONArray();
for (SimpleUser user : userList) {
UriBuilder ub = uriInfo.getAbsolutePathBuilder();
URI resUri = ub.path(user.getId().toString()).build();
resUriArray.put(resUri.toASCIIString());
}
return resUriArray;
}
The returned
array will contain the URIs of the entities as JSON strings, not the entities
themselves.
Update
Our
adopted REST paradigm calls for resources to be updated by the HTTP request PUT.
@PUT
@Consumes({MediaType.APPLICATION_JSON})
public Response updateSimpleUser(JAXBElement<SimpleUser> userJAXB) {
SimpleUser user = userJAXB.getValue();
URI userURI = uriInfo.getAbsolutePathBuilder().
path(user.getId().toString()).build();
try {
SimpleUser matchU = (SimpleUser) entityManager.find(
SimpleUser.class, user.getId());
if (matchU == null) {
String msg = "PUT is the wrong HTTP request for creating
new user " + user.getId() + ".";
ResponseBuilder rBuild = Response.status(
Response.Status.BAD_REQUEST).entity(msg);
throw new WebApplicationException(rBuild.build());
}
entityManager.merge(user);
return Response.ok().build();
} catch (IllegalArgumentException ex) {
ResponseBuilder rBuild = Response.status(
Response.Status.NOT_FOUND).entity(
userURI.toASCIIString());
return rBuild.build();
}
}
Delete
Our
adopted REST paradigm calls for resources to be removed by the HTTP request DELETE.
@DELETE
@Path("{id}/")
public Response deleteSimpleUser(@PathParam("id") int id) {
URI userURI = uriInfo.getAbsolutePathBuilder().build();
if (id < 1) {
ResponseBuilder rBuild = Response.status(
Response.Status.NOT_FOUND).entity(
userURI.toASCIIString());
return rBuild.build();
}
SimpleUser user = entityManager.find(SimpleUser.class, id);
if (user == null) {
ResponseBuilder rBuild = Response.status(
Response.Status.NOT_FOUND).entity(
userURI.toASCIIString());
return rBuild.build();
}
entityManager.remove(user);
ResponseBuilder rBuild = Response.status(Response.Status.NO_CONTENT);
return rBuild.build();
}
The {id}
template in @Path
works the same way as
for retrieveSimpleUser()
.
Android Client
Now, brace yourself for the stunning beauty of this
Android client application.
Displayed in this UI is
the entity bean with id
=3, name
=”Dustin Penner”, and email
="dustin.penner@simpleuser.org".
Now, a consideration in
building this app is how to serialize the bean. A reflexive instinct of the
typical contemporary software developer, trained to use application frameworks,
is to reach for a library off the internet that will handle the serialization
to JSON automatically, e.g. Jackson at jackson.codehaus.org. You know the
thinking – don’t reinvent the wheel; don’t create errors by, heaven forbid, programming
your own code; outsource the responsibility to the library maker. However, I
think that, in this case, adding a library to the client is overkill. The
SimpleUser
entity is *simple* - we can handle it. We can include our own
conversion methods in the entity class, which needs to be imported as a library
into the Android client project anyway. We don’t have to be burdened with the
overhead of including and updating the extra library in multitudes of client apps.
And, for the purpose of enlightenment, we also get to show JSON serialization in
action in this article.
Let’s create an entity to
represent the great, young Canadian hockey defenseman, Drew Doughty. The method in the client sends
out a HTTP POST request over a raw HttpURLConnection
as follows:
private void createUser(SimpleUser user) {
URL url;
HttpURLConnection connection = null;
try {
String wsUri = getBaseContext().getResources().getString(
R.string.rest_web_service_uri);
url = new URL(wsUri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
String userSer = user.getJSONSerialization();
connection.setRequestProperty("Content-Length", Integer
.toString(userSer.getBytes().length));
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(connection
.getOutputStream());
wr.writeBytes(userSer);
wr.flush();
wr.close();
...
This method also displays
the HTTP response from the server with a Toast
pop-up, but we’ll dismiss that
as unimportant for this article.
Now let’s look at SimpleUser
’s
serialization method.
public String getJSONSerialization() {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append(serializeJSONField("id", Integer.toString(id)) + ",");
sb.append(serializeJSONField("name", name) + ",");
sb.append(serializeJSONField("email", email) + ",");
sb.append("}");
return sb.toString();
}
private String serializeJSONField(String name, String value) {
StringBuilder sb = new StringBuilder();
sb.append("\"");
sb.append(name);
sb.append("\":\"");
sb.append(value);
sb.append("\"");
return sb.toString();
}
Elegant, eh? That’s the
nice thing about JSON serialization in comparison with a JAXB XML serialization
– little overhead.
Thus, when an Android-wielding
hockey fan wants to record forever the name and email of a certain slick-skating
L.A. King, his press of the Create User
button will send a textual request
like the following, bypassing any typical firewalls, directly to our web
service:
POST /SimpleUserRESTService-war/rest/appuser HTTP/1.1
Host: localhost:8080
Accept: */*
Content-Type: application/json
Content-Length: 70
{"id":"0","name":"Drew Doughty","email":"drew.doughty@simpleuser.org"}
After clicking Retrieve All Users
, the fan can select the last user id in the spinner and click Retrieve User Details
in order to check his new addition.
private void retrieveUserDetails() {
URL url;
HttpURLConnection connection = null;
try {
String wsUri = getBaseContext().
getResources().getString(
R.string.rest_web_service_uri);
Spinner spinner = (Spinner) findViewById(
R.id.retrieveAllSpinner);
String userId = (String) spinner.getSelectedItem();
wsUri += userId;
url = new URL(wsUri);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/json");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.connect();
int rspCode = connection.getResponseCode();
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String rspBody = sb.toString();
if (rspCode == HttpURLConnection.HTTP_OK) {
currentUser = new SimpleUser(rspBody);
TextView detailsTextView = (TextView) findViewById(
R.id.detailsTextView);
String userLabel = currentUser.getName() + " (" +
currentUser.getId() + ")";
detailsTextView.setText(userLabel);
EditText emailEditText = (EditText) findViewById(
R.id.emailEditText);
emailEditText.setText(currentUser.getEmail());
}
...
This causes the following
HTTP request to and response from the server:
GET /SimpleUserRESTService-war/rest/appuser/4 HTTP/1.1
Host: localhost:8080
Accept: application/json
HTTP/1.1 200 OK
X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 3.1.1 Java/Oracle Corporation/1.7)
Server: GlassFish Server Open Source Edition 3.1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 May 2012 19:28:14 GMT
{"email":"drew.doughty@simpleuser.org","id":"4","name":"Drew Doughty"}*
And this response is
deserialized in the SimpleUser
constructor as follows:
public SimpleUser(String jSONSerialization) {
try {
String valStr = extractJSONFieldValue(jSONSerialization, "id");
this.id = Integer.parseInt(valStr);
this.name = extractJSONFieldValue(jSONSerialization, "name");
this.email = extractJSONFieldValue(jSONSerialization, "email");
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(SimpleUser.class.getName()).
log(Level.SEVERE, null, ex);
ex.printStackTrace();
}
}
private String extractJSONFieldValue(String jSONSerialization,
String field) throws UnsupportedEncodingException {
String fieldLab = '"' + field + '"' + ':';
int i = jSONSerialization.indexOf(fieldLab);
if (i < 0)
throw new IllegalArgumentException(
"The JSON serialization is missing field label:" +
fieldLab);
i = jSONSerialization.indexOf('"', i + fieldLab.length());
if (i < 0)
throw new IllegalArgumentException("The JSON serialization " +
"is missing the opening quote for the value for field " +
field);
int j = jSONSerialization.indexOf('"', ++i);
if (j < 0)
throw new IllegalArgumentException("The JSON serialization " +
"is missing the closing quote for the value for field " +
field);
String valStr = jSONSerialization.substring(i, j);
return URLDecoder.decode(valStr, "UTF-8");
}
You should be able to
extrapolate from here the necessary methods for retrieving all entities, updating
an entity, and deleting one, so I won’t overburden you with more code.
Here is the Android form
after the fan has checked his new entry:
Points of Interest
Implementing your own JSON serialization of your database objects over HTTP is fun because it is simple and can pass through just about any network barriers between your client and server.