The resource classes in the mavenproject1 project, MyBusinessObjectsResource.java and MyBusinessObjectResource.java, use the container-item design pattern typical of JAX-RS applications. The methods in these classes are invoked by the Gateway.
The methods in the MyBusinessObjectsResource class perform operations on business objects as follows:
The MyBusinessObjectsResource.getBusinessObjects method implements the GET operation, retrieving a list of business objects.
The MyBusinessObjectsResource.getBusinessObject method retrieves a single MyBusinessObjectResource object using a @path annotation. This method is invoked as part of the design pattern and is already implemented in the Maven archetype.
The MyBusinessObjectsResource.lifeCycle method implements the POST operation, initiating and terminating a session with the JAX-RS Enterprise Connector bridge. It also returns the file extension used by all business objects returned by this Enterprise Connector.
The methods in the MyBusinessObjectResource class perform operations on business objects as follows:
The MyBusinessObjectResource.getBusinessObject method is not currently invoked. The Maven archetype allows you to implement it, and the MusicDB JAX-RS Enterprise Connector implements it, but you are not required to implement it. The method implements the GET operation, retrieving a business object in serialized form.
The MyBusinessObjectResource.putBusinessObject method implements the PUT operation, creating or updating the binary representation of a business object.
The MyBusinessObjectResource.deleteBusinessObject method implements the DELETE operation, deleting the business object.
The following sections describe these methods more fully and provide examples of their implementation.
The MyBusinessObjectsResource class exposes the following URLs, as the comments for the class indicate:
http://server:port/context-root/resources/businessObjects
http://server:port/context-root/resources/businessObjects/{id}, where id is the name of the business object.
The @Path annotation at the beginning of the class establishes the path to these URLs:
@Path("businessObjects") public class MyBusinessObjectsResource {
The class begins with a definition of the file extension to be returned by the lifeCycle method, followed by a context declaration and class constructor.
// UPDATE CODE: Set extension for these objects public static final String EXTENSION = ".obj"; @Context private UriInfo context; public MyBusinessObjectsResource() { }
The getBusinessObjects method is mapped to the HTTP GET operation on this resource. The method template looks like this:
@GET @Produces("text/xml") public BusinessObjects getBusinessObjects( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId) { try { BusinessObjects result = new BusinessObjects(); List<BusinessObject> resultList = result.getBusinessObject(); // INSERT CODE: populate list of business objects return result; } catch (Exception e) { throw new RuntimeException(e); } }
The getBusinessObjects method returns an instance of a JAXB object, BusinessObjects, serialized as an XML document. Each object is identified by a name and a binary representation that is a contract between the Enterprise Connector and the client. The getBusinessObject method called on the result object is not one of the methods exposed in the resource classes. Instead, it is a JAXB accessor method defined in the package that is imported at the beginning of the resource class file:
import com.sun.mep.connector.jaxrs.getbusinessobjects.*;
Some operations go beyond the simple CRUD operations. The lifeCycle method corresponds to the HTTP POST operation. It has the following signature:
@POST @Produces("text/plain") public String lifeCycle( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @QueryParam("operation") String operation)
You can use this method to control the lifecycle of a synchronization session, identified by the sessionId parameter. Specifically, you can use the value of the operation argument to initialize a session by connecting to a database or EIS system, or to terminate the session:
if (operation.equals("initialize")) { // INSERT CODE: session initialization } else if (operation.equals("terminate")) { // INSERT CODE: session termination } else { throw new RuntimeException("Lifecycle operation " + operation + " not understood"); } return EXTENSION;
In the MusicDB example, the MusicAlbumsResource class is the container class. After importing needed packages, the resource class begins with a @Path annotation that establishes the URLs:
@Path("albums") public class MusicAlbumsResource {
The HTTP operations will use the URLs http://server:port/context-root/resources/albums and http://server:port/context-root/resources/albums/{id}. For example, if you specify musicdb-ws as the context root and are running the Enterprise Server on your own system, the URL for retrieving the business objects would be http://localhost:8080/musicdb-ws/resources/albums.
The next few lines define the file extension to be used for these business objects, a JAX-RS context, and a class constructor. They also define a user name prefix specific to this Enterprise Connector, to be used by the lifeCycle method:
private static final String EXTENSION = ".alb"; private static final String DB_USER_NAME = "musicdbuser"; @Context private UriInfo context; public MusicAlbumsResource() { }
Next, the lifeCycle method provides code to establish or terminate a connection to the database, using the ConnectionPool defined in src/main/java/com/sun/mep/ws/musicdb/util/ConnectionPool.java. Both the createConnection and destroyConnection methods take a session ID argument.
@POST @Produces("text/plain") public String lifeCycle( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @QueryParam("operation") String operation) { if (operation.equals("initialize")) { if (!user.startsWith(DB_USER_NAME)) { throw new RuntimeException("A MusicDB user name must start " + "with the '"+ DB_USER_NAME + "' prefix"); } ConnectionPool.getInstance().createConnection(sessionId); } else if (operation.equals("terminate")) { ConnectionPool.getInstance().destroyConnection(sessionId); } else { throw new RuntimeException("Lifecycle operation " + operation + " not understood"); } return EXTENSION; }
For the MusicDB application, a connection can be established only if the user name begins with musicdbuser. At the end, the method returns the string .alb, the file extension for music albums.
The getBusinessObjects method begins by using another connection to create a JDBC statement:
@GET @Produces("text/xml") public BusinessObjects getBusinessObjects( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @QueryParam("useTimestamps") @DefaultValue("false") boolean useTimestamps) { Statement stmt = null; try { // Create SQL statement from connection ConnectionPool pool = ConnectionPool.getInstance(); stmt = pool.getConnection(sessionId).createStatement();
The useTimestamps argument of the method, which is specific to the MusicDB Enterprise Connector, provides the value of the useTimestamps flag. An administrator can set the value of the useTimestamps property when defining the Enterprise Connector in the Sun GlassFish Mobility Platform Administration Console. If the property is not set, the argument value is set to false.
It then executes the template code that instantiates the list of JAXB objects:
// Get list of business objects to return BusinessObjects result = new BusinessObjects(); List<BusinessObject> resultList = result.getBusinessObject();
Next, the method performs an SQL query to retrieve from the back-end database all the business objects that belong to the user who is logged in to the Enterprise Connector:
ResultSet rs = stmt.executeQuery( "SELECT * FROM album WHERE username = '" + user + "'");
The method iterates through the query result set, instantiating each business object (in this case, a MusicAlbum) and populating it with its retrieved properties (including the lastModified property if the useTimestamps flag is set), then serializing it to a JAXB object and adding it to the list of objects. Finally, the method closes the result set and returns the list of JAXB objects.
while (rs.next()) { // Use temporary object for serialization purposes MusicAlbum album = new MusicAlbum(user, false); album.setName(rs.getString(1)); album.setArtist(rs.getString(2)); album.setDatePublished(rs.getString(3).replace("-", "")); album.setRating(rs.getInt(4)); if (useTimestamps) { album.setLastModified(rs.getTimestamp(6).getTime()); } // Map MusicAlbum to JAXB bean BusinessObject bo = new BusinessObject(); bo.setName(album.getName()); bo.setValue(album.serialize()); if (useTimestamps) { bo.setLastModified(album.getLastModified()); } // Add BusinessObject to result list resultList.add(bo); } rs.close(); return result; } catch (Exception e) { throw new RuntimeException(e); } finally { if (stmt != null) { try { stmt.close(); } catch (Exception e) { /* ignore !*/ } } } }
The MyBusinessObjectResource class implements the CUD operations create, update, and delete, using the HTTP operations PUT and DELETE.
The putBusinessObject method either creates or updates a business object, returning the serialized object. The method looks like this:
@PUT @Consumes("application/octet-stream") public void putBusinessObject( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @QueryParam("lastModified") @DefaultValue("0") long lastModified, @PathParam("id") String id, byte[] object) { // INSERT CODE: create/update object from client }
The deleteBusinessObject method removes a business object from the back-end system, given its identifier and, optionally, the object contents. The method signature looks like this:
@DELETE public void deleteBusinessObject( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @QueryParam("lastModified") @DefaultValue("0") long lastModified, @PathParam("id") String id, byte[] object)
The mergeBusinessObjects method, like the MyBusinessObjectsResource.lifeCycle method, goes beyond the simple CRUD operations to implement the HTTP POST operation:
@POST @Produces("application/octet-stream") public byte[] mergeBusinessObjects( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @PathParam("id") String id, BusinessObjects objects)
Two business objects are passed in the argument objects: the server object followed by the client object.
The Gateway can call this method when there is an update conflict. If an object is updated on both the client and the server, the Gateway asks the Enterprise Connector to provide a merged object containing updated information for both objects.
Because the getBusinessObject method is not currently used, it is not described here.
In the MusicDB example, the MusicAlbumResource class is the item class. The MusicAlbumResource class implements the CUD methods specified by the MyBusinessObjectResource template.
The putBusinessObject method begins by adding an additional boolean parameter, useTimestamps, to the method signature:
@PUT @Consumes("application/octet-stream") public void putBusinessObject( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @QueryParam("lastModified") @DefaultValue("0") long lastModified, @QueryParam("useTimestamps") @DefaultValue("false") boolean useTimestamps, @PathParam("id") String id, byte[] object)
The useTimestamps parameter allows the method to instantiate a MusicAlbum using its three-argument constructor. The method code begins by declaring two JDBC statements and creating a connection pool instance. It then instantiates the MusicAlbum object and deserializes its byte array argument into this object:
Statement stmt1 = null, stmt2 = null; try { // Get connection pool ConnectionPool pool = ConnectionPool.getInstance(); // Map to connector object to generate SQL statement MusicAlbum album = new MusicAlbum(id, user, useTimestamps); album.deserialize(object);
Next, the method determines whether the album already exists by executing the select statement for the MusicAlbum object.
// Check if album already exists boolean isInsert = true; stmt1 = pool.getConnection(sessionId).createStatement(); ResultSet rs = stmt1.executeQuery(album.getSelectString()); if (rs.next()) { isInsert = false; } rs.close();
If the select statement returns a non-empty result set, the boolean value isInsert is set to false, indicating that the method will perform an update. Otherwise, the method will perform an insert to create the object in the database. The following code executes either the insert statement or the update statement for the MusicAlbum object, then closes the two JDBC statements.
// Run INSERT or UPDATE against database stmt2 = pool.getConnection(sessionId).createStatement(); stmt2.executeUpdate(isInsert ? album.getInsertString() : album.getUpdateString()); } catch (Exception ex) { throw new RuntimeException(ex); } finally { if (stmt1 != null) { try { stmt1.close(); } catch (Exception e) { /* ignore !*/ } } if (stmt2 != null) { try { stmt2.close(); } catch (Exception e) { /* ignore !*/ } } } }
The deleteBusinessObject method for MusicAlbum, like putBusinessObject, adds a useTimestamps parameter to its signature:
@DELETE public void deleteBusinessObject( @QueryParam("username") @DefaultValue("username") String user, @QueryParam("password") @DefaultValue("password") String password, @QueryParam("sessionId") @DefaultValue("") String sessionId, @QueryParam("lastModified") @DefaultValue("0") long lastModified, @QueryParam("useTimestamps") @DefaultValue("false") boolean useTimestamps, @PathParam("id") String id, byte[] object)
The method declares a JDBC statement, then instantiates a MusicAlbum object using its three-argument constructor and deserializes its byte array argument into this object:
{ Statement stmt = null; try { // Map to connector object to generate SQL statement MusicAlbum album = new MusicAlbum(id, user, useTimestamps); album.deserialize(object);
The code creates a connection pool instance and a JDBC statement, then executes the delete statement for the MusicAlbum object to remove it from the database:
ConnectionPool pool = ConnectionPool.getInstance(); stmt = pool.getConnection(sessionId).createStatement(); stmt.executeUpdate(album.getDeleteString()); } catch (Exception ex) { throw new RuntimeException(ex); } finally { if (stmt != null) { try { stmt.close(); } catch (Exception e) { /* ignore !*/ } } } }
The MusicAlbum Enterprise Connector does not implement the mergeBusinessObjects method. This means that the object on the server always wins if there is a conflict.
The getBusinessObject method is implemented in the MusicAlbumResource class, but it is never invoked.