Sun GlassFish Mobility Platform 1.1 Developer's Guide for Enterprise Connectors

Defining Operations on Business Objects

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 methods in the MyBusinessObjectResource class perform operations on business objects as follows:

The following sections describe these methods more fully and provide examples of their implementation.

The MyBusinessObjectsResource Class

The MyBusinessObjectsResource class exposes the following URLs, as the comments for the class indicate:

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;

Example: The MusicAlbumsResource Container Class

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

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.

Example: The MusicAlbumResource Class

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.