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

Chapter 2 Creating an Enterprise Connector Using the Java API for RESTful Web Services (JAX-RS)

A Sun GlassFish Mobility Platform Enterprise Connector that uses the Java API for RESTful Web Services (JAX-RS) typically includes JAX-RS resource classes as well as a Java class that defines the business objects to be synchronized. One of the resource classes may provide a reporting facility. For performance reasons, a connection pool facility may also be provided.

The Java class that defines the business objects fulfils a contract between the Enterprise Connector and the mobile client application that synchronizes data with the back-end system. The serializations of the Enterprise Connector objects must match the serializations of the client application objects, and both kinds of objects must correspond to objects on the back-end system.

The JAX-RS resource classes perform HTTP operations: GET, POST, PUT, and DELETE. These operations are mapped to CRUD operations on the data: create, retrieve, update, and delete.

For details on the JAX-RS API, see the specification at http://jcp.org/aboutJava/communityprocess/final/jsr311/index.html. The Reference Implementation (RI), called Jersey, is an open-source project located at https://jersey.dev.java.net/. Click the Learn link on the Jersey project page for more information.

This chapter uses the MusicDB JAX-RS sample Enterprise Connector provided with Sun GlassFish Mobility Platform to demonstrate how to develop an Enterprise Connector using JAX-RS. This Enterprise Connector acts as the intermediary between a client application on a mobile device and a back-end system. For this simple application, the back-end system is not a full-fledged EIS/EAI system but an ordinary database that is accessed using the Java Database Connectivity (JDBC) API.

The source code for the MusicDB JAX-RS Enterprise Connector is included in the Sun GlassFish Mobility Platform client bundle. In the directory where you unzipped the client bundle, it is in a ZIP file, musicdb-ws-3.1.39.zip, in the subdirectory sgmp-client-1_1_01-fcs-b02/samples/secure-musicdb/src/connector/jaxrs/. The ZIP file unzips as a Maven project. Within this project, the Java source files are in the directory musicdb-ws-3.1.39/src/main/java/com/sun/mep/ws/musicdb, in the subdirectories sync, report, and util. The sync directory contains the business object classes.

Use NetBeans IDE with the Maven plugin to develop your own Enterprise Connector. The plugin provides templates for the JAX-RS resource classes, but not for the business object.

Defining Business Objects

A business object that you define for a JAX-RS Enterprise Connector represents the object that the Enterprise Connector will synchronize. The object does not need to implement a specific API. However, it needs to match a business object defined by a client application, and the client application typically uses the Mobile Client Business Object (MCBO) API to define its business objects. Therefore, the JAX-RS Enterprise Connector business object looks very similar to that of the client application.

For the MusicDB example, the class that defines the business object is MusicAlbum. The MusicAlbum.java file begins by importing the Java SE packages it requires:

package com.sun.mep.ws.musicdb.sync;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.sql.Timestamp;

The class code itself begins by declaring a string value to be used by the serialize method:

public class MusicAlbum {

    private static final String DEFAULT_VALUE = "$$default$$";

This example then declares its data properties (there are only five):

    /**
     * Album's name.
     */
    String name;

    /**
     * Album's artist.
     */ 
    String artist;

    /**
     * Date when the album was published.
     */
    Calendar datePublished;
    
    /**
     * Album's rating from 1 to 5.
     */
    int rating;

    /**
     * Last modified timestamp.
     */
    long lastModified;

The lastModified property is unique to the Enterprise Connector version of the business object; it is not used in the mobile client application. This property allows the Gateway to use timestamps to query the back-end system to determine whether a business object has been updated since the last synchronization. If the Enterprise Connector does not use timestamps, the Gateway computes a digest over the serialized business object, using a hash function, and compares it to the previous digest. Using timestamps is more efficient than using digests.

The code then declares a StringBuilder, a user name value, and a flag indicating whether the object will use timestamps or digests:

    /**
     * String builder used to return SQL commands.
     */ 
    StringBuilder stringBuilder = new StringBuilder();
    
    /**
     * Name of user logged in.
     */
    String username;
    
    /**
     * Flag indicating if timestamps should be used instead 
     * of digests.
     */
    boolean useTimestamps;

The user name and the useTimestamps flag are used in the MusicAlbum business object constructors, which have both two-argument and three-argument forms:

    public MusicAlbum(String name, String username) {
        this(name, username, false);
    }
    
    public MusicAlbum(String username, boolean useTimestamps) {
        this(null, username, useTimestamps);
    }
    
    public MusicAlbum(String name, String username, boolean useTimestamps) {
        this.name = name;
        this.username = username; 
        this.useTimestamps = useTimestamps; 
    }

The username column and the name column provide a composite primary key for the album table. The username column identifies the owner of an album and allows the MusicAlbumsResource.getBusinessObjects method to return only the albums for a particular user, so that multiple users can share the album table.

Now the class implements its getter and setter methods for the name, artist, rating, lastModified, and datePublished properties:

    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getArtist() {
        return artist;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    public int getRating() {
        return rating;
    }

    public void setRating(int rating) {
        this.rating = rating;
    }
    
    public long getLastModified() {
        return lastModified;
    }
    
    public void setLastModified(long millis) {
        lastModified = millis;
    }
    
    /**
     * Returns the date published as a string in the format
     * 'YYYYMMDD'.
     */
    public String getDatePublished() {
         stringBuilder.setLength(0);
         stringBuilder.append(datePublished.get(Calendar.YEAR));
         int month = datePublished.get(Calendar.MONTH) + 1;
         if (month < 10) {
             stringBuilder.append('0');
         }
         stringBuilder.append(month);
         int day = datePublished.get(Calendar.DAY_OF_MONTH);
         if (day < 10) {
             stringBuilder.append('0');
         }
         stringBuilder.append(day);
         return stringBuilder.toString();
    }
    
    /**
     * Sets the date published in the format 'YYYYMMDD'.
     */ 
    public void setDatePublished(String date) {
        datePublished = Calendar.getInstance();
        datePublished.set(Calendar.YEAR, 
                Integer.parseInt(date.substring(0, 4)));
        datePublished.set(Calendar.MONTH, 
                Integer.parseInt(date.substring(4, 6)) - 1);
        datePublished.set(Calendar.DAY_OF_MONTH, 
                Integer.parseInt(date.substring(6, 8)));
    }

In a client application, a business object takes the form of a file whose name has an extension unique to that business object. For a JAX-RS Enterprise Connector, the filename extension is returned by the initialize operation in one of the resource classes rather than being defined in the business object class. (See The MyBusinessObjectsResource Class for details.)

The class uses Java Serialization to implement the serialize and deserialize methods as follows. The byte array format used in the serialize and deserialize methods, like the data types of the fields, is part of the contract between the client application and the Enterprise Connector.

    public byte[] serialize() throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        DataOutputStream dOut = new DataOutputStream(out);

        dOut.writeUTF(getName());
        dOut.writeUTF(getArtist() != null ? getArtist() : DEFAULT_VALUE);
        dOut.writeUTF(getDatePublished() != null ? getDatePublished() : DEFAULT_VALUE);
        dOut.writeUTF(Integer.toString(getRating()));
        dOut.flush();
        return out.toByteArray();
	}

    public void deserialize(byte[] array) throws IOException {
        ByteArrayInputStream in = new ByteArrayInputStream(array);
        DataInputStream dIn = new DataInputStream(in);
        
        setName(dIn.readUTF());
        artist = dIn.readUTF();
        if (artist.equals(DEFAULT_VALUE)) {
            artist = null;
        }
        String date = dIn.readUTF();
        if (date.equals(DEFAULT_VALUE)) {
            datePublished = null;
        }
        else {
            setDatePublished(date);
        }
        rating = Integer.parseInt(dIn.readUTF());
    }

The class implements getInsertString, getUpdateString, and getDeleteString helper methods. These methods return SQL statement strings to perform database operations on the business objects:

    /**
     * Returns an SQL insert statement to add this instance
     * to the back-end database.
     */
    public String getInsertString() {
        stringBuilder.setLength(0);        
        stringBuilder.append(useTimestamps ? 
            "INSERT INTO album" :
            "INSERT INTO album (name, artist, date_published, rating, username)")
            .append(" VALUES ('")
            .append(getName()).append("', '")
            .append(artist).append("', DATE '")
            .append(datePublished.get(Calendar.YEAR)).append("-")
            .append(datePublished.get(Calendar.MONTH) + 1).append("-")
            .append(datePublished.get(Calendar.DAY_OF_MONTH)).append("', ")
            .append(Integer.toString(rating)).append(", '")
            .append(username)
            .append("'");
        
        if (useTimestamps) {
            Timestamp timestamp = new Timestamp(lastModified);
            stringBuilder.append(", TIMESTAMP '")
                    .append(timestamp.toString())
                    .append("'");
        } 
        stringBuilder.append(')');
        
        return stringBuilder.toString();
    }

    /**
     * Returns an SQL update statement to modify this instance
     * in the back-end database.
     */
    public String getUpdateString() {
        stringBuilder.setLength(0);
        stringBuilder.append("UPDATE album SET artist='")
             .append(artist).append("', date_published=DATE '")
             .append(datePublished.get(Calendar.YEAR)).append("-")
             .append(datePublished.get(Calendar.MONTH) + 1).append("-")
             .append(datePublished.get(Calendar.DAY_OF_MONTH)).append("', rating = ")
             .append(Integer.toString(rating));
        
        if (useTimestamps) {
            Timestamp timestamp = new Timestamp(lastModified); 
            stringBuilder.append(", last_modified = TIMESTAMP '")
                .append(timestamp.toString()).append("'");
        }
        
        stringBuilder.append(" WHERE name = '").append(getName())
             .append("' AND username = '")
             .append(username)
             .append("'");       
        return stringBuilder.toString();
    }
        
    /**
     * Returns an SQL delete statement to remove this instance
     * from the back-end database.
     */
    public String getDeleteString() {
        stringBuilder.setLength(0);
        stringBuilder.append("DELETE FROM album WHERE name = '")
                     .append(getName())
                     .append("' AND username = '" + username + "'");
        return stringBuilder.toString();
    }

Using NetBeans IDE to Create a JAX-RS Enterprise Connector

The simplest way to create a JAX-RS Enterprise Connector is to use NetBeans IDE 6.5 with the Maven plugin and the Maven archetypes for Sun GlassFish Mobility Platform.

ProcedureTo Install the Maven Plugin

Before You Begin

If you do not already have NetBeans IDE 6.5, go to http://www.netbeans.org/ to download and install it.

On the NetBeans IDE download page, select Java as the bundle to download. This bundle includes Java SE, Web, Java EE, and Java ME. When you install NetBeans IDE, click Customize to install only some of the components. Deselect all of the listed runtimes, since you are using the Sun GlassFish Mobility Platform version of Sun GlassFish Enterprise Server.

  1. Start NetBeans IDE.

  2. From the Tools menu, select Plugins.

  3. In the Plugins dialog, click the Available Plugins tab, then type Maven in the Search field.

  4. Select the checkbox for the Maven plugin, then click the Install button.

  5. Follow the instructions in the NetBeans IDE Installer wizard.

  6. After the wizard returns, click Close in the Plugins dialog.

  7. Choose Windows->Other->Maven Repository Browser.

  8. In the Maven Repository Browser tree, right-click the Java.net Repository node and select Update Index.

ProcedureTo Create a Maven Project for a JAX-RS Enterprise Connector

  1. In NetBeans IDE, click the Projects tab.

  2. Choose File->New Project.

  3. In the New Project dialog, select Maven from the Categories tree.

  4. Select Maven Project from the Projects list, then click Next.

  5. On the Maven Archetype page, expand Archetypes from the remote Maven Repositories node.

  6. From the list, select MEP Connector Archetype (JAX-RS), then click Next.

  7. On the Name and Location page, modify any default values you wish to, then click Finish.

    The new project appears in the Projects tab. The default project name is mavenproject1 (war).

  8. Under mavenproject1, expand the Source Packages node.

  9. Expand the com.mycompany.mavenproject1 node.

    Two class files appear, named MyBusinessObjectsResource.java and MyBusinessObjectResource.java. These classes correspond to the container-item pattern familiar to JAX-RS programmers.

Next Steps

To create the Enterprise Connector, you add code to these files and define your business object.

ProcedureTo View and Build the MusicDB JAX-RS Connector Project

  1. In NetBeans IDE, select File -> Open Project.

  2. In the Open Project window, navigate to sgmp-client-1_1_01-fcs-b02/samples/secure-musicdb/src/connector/jaxrs/musicdb-ws-3.1.39 and click Open Project.

    The musicdb-ws project appears in the Projects tab. The source files are under the Source Packages node.

    If the project has a red exclamation point next to it, right click the project and choose Show and Resolve Problems. In the window that appears, click the Download Libraries button. After a short delay, thewindow disappears and the red exclamation point no longer appears next to the project.

  3. Right-click the project and choose Build to build the project.

    The first time you build a project using the Maven plugin, the build takes a long time while Maven downloads a large number of libraries.

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.

Building, Deploying, and Configuring a JAX-RS Enterprise Connector

Once you have used NetBeans IDE to create a JAX-RS Enterprise Connector, build it by right-clicking the project and choosing Build.

To deploy the Enterprise Connector, use the Sun GlassFish Enterprise Server Administration Console. You must also configure a connector connection pool and a connector resource for the Enterprise Connector.

By default, the MusicDB JAX-RS Enterprise Connector is deployed in an installed version of Sun GlassFish Mobility Platform. However, it is not configured. If you would like to use this connector, you must undeploy the deployed connector and deploy the version you opened and built in To View and Build the MusicDB JAX-RS Connector Project.

Deploying and configuring an Enterprise Connector involves the following tasks:

ProcedureTo Start the Sun GlassFish Enterprise Server and Database

  1. In NetBeans IDE, click the Services tab.

  2. Right-click the Servers node and choose Add Server.

    The Add Server Instance dialog opens.

  3. On the Choose Server page, select GlassFish V2 and click Next.

  4. On the Platform Folder Location page, click Browse and navigate to the location of the Enterprise Server, then click Choose.

    The Enterprise Server location appears in the Platform Location field.

  5. Click Next.

    The Domain Admin Login Info appears.

  6. Verify that the Admin Username is correct, and type the administrator password in the Admin Password field.

    The default password is adminpass.

  7. Click Finish.

    GlassFish V2 appears under the Servers node.

  8. If the Enterprise Server is not already running, right-click GlassFish V2 and choose Start.

  9. Expand the Databases node.

  10. If your database (either MySQL or Oracle) is not running, right-click the database and choose Start.

    If the database does not start, you may have to edit the database properties to specify your password.

ProcedureTo Deploy an Enterprise Connector

  1. Log in to the Enterprise Server Administration Console.

    1. In a browser, type the Administration Console URL (usually http://localhost:4848/).

    2. Type the administrator username and password (usually admin and adminpass).

    3. Click the Login button.

  2. In the navigation tree, expand the Applications node.

  3. Under the Applications node, select the Web Applications node.

    The Web Applications page opens.

  4. (Optional) If you plan to deploy the MusicDB JAX-RS Enterprise Connector, select musicdb-ws and click the Undeploy button.

  5. Click the Deploy button.

    The Deploy Enterprise Applications/Modules page opens.

  6. On the Deploy Enterprise Applications/Modules page, verify that Packaged File to be Uploaded to the Server is selected, then click Browse.

  7. In the file chooser, navigate to the location of the WAR file and click Open.

    The WAR file is normally in the directory NetBeansProjects/project-name/target. On a Windows system, the NetBeansProjects directory is under My Documents.

    The new musicdb-ws project is in the directory sgmp-client-1_1_01-fcs-b02/samples/secure-musicdb/src/connector/jaxrs/musicdb-ws-3.1.39/target.

  8. If necessary, change the Application Name and Context Root by removing the version part of the WAR file name.

  9. Click OK.

    The deployed Enterprise Connector appears in the Web Applications table.

Next Steps

Next, use the Enterprise Server Administration Console to configure the Enterprise Connector.

ProcedureTo Create a Connector Connection Pool for the Enterprise Connector

  1. In the navigation tree, expand the Resources node.

  2. Under the Resources node, expand the Connectors node.

  3. Select Connector Connection Pools.

    The Connector Connection Pools page opens.

  4. Click the New button.

    The New Connector Connection Pool (Step 1 of 2) page opens.

  5. In the Name field, type mep/connector-name, where connector-name is the name of your Enterprise Connector.

    For MusicDB, the name should be mep/musicdb-ws.

  6. From the Resource Adapter drop-down list, select ds-jcr-connector-jaxrs, the JAX-RS connector bridge.

    The connection definition, javax.resource.spi.ManagedConnectionFactory, is filled in automatically.

  7. Click Next.

    The New Connector Connection Pool (Step 2 of 2) page opens.

  8. Click Finish to accept the default settings.

    The new connector connection pool appears in the Connector Connection Pools table.

Next Steps

Next, you must create a connector resource for the Enterprise Connector.

ProcedureTo Create a Connector Resource for the Enterprise Connector

  1. Under the Connectors Node, select the Connector Resources node.

    The Connector Resources page opens.

  2. Click New.

    The New Connector Resource page opens.

  3. In the JNDI Name field, type mep/connector-name, where connector-name is the name of your Enterprise Connector.

    For MusicDB, the name should be mep/musicdb-ws.

  4. From the Pool Name drop-down list, select mep/connector-name, the connection pool you created.

    For MusicDB, the name should be mep/musicdb-ws.

  5. Click OK.

    The new connector resource appears in the Connector Resources table.

ProcedureTo Verify that an Enterprise Connector Is Deployed

  1. In your browser, open the URL for the business objects, using this syntax:


    http://server:port/context-root/resources/businessObjects
    

    For example, for the MusicDB Enterprise Connector, you would specify the following for a locally running Enterprise Server:


    http://localhost:8080/musicdb-ws/resources/albums
    

    Specifying this URL is equivalent to invoking the getBusinessObjects method.

  2. Verify the content of the page that opens.

    If the Enterprise Connector is running, an XML document opens in your browser, containing a businessObjects element that encloses zero or more businessObject elements. Each businessObject element contains a name field and a binary representation.

ProcedureTo Configure the Enterprise Connector on Sun GlassFish Mobility Platform

Add and configure the JAX-RS Enterprise Connector as a local Enterprise Connector using the Sun GlassFish Mobility Platform Administration Console.

You can also configure the Enterprise Connector as a remote Enterprise Connector if you deploy it on a remote system. For instructions, see To Create and Activate a New Enterprise Connector in Sun GlassFish Mobility Platform 1.1 Administration Guide and To Configure a Remote Enterprise Connector in Sun GlassFish Mobility Platform 1.1 Administration Guide.

  1. Log in to the Sun GlassFish Mobility Platform Administration Console.

    1. In a browser, type the Administration Console URL (usually http://localhost:8080/sync/admin).

    2. Type the username and password (the default values are admin and syncpass).

    3. Click the Login button.

  2. Click the Connectors tab.

  3. In the Name field of the Local Enterprise Connectors panel, type the name of the Enterprise Connector (for example, MusicDbRS).

  4. Click Add.

    The new Enterprise Connector appears in the list of repositories. The Active checkbox is selected by default, indicating that the Enterprise Connector has been activated.

    A new sub-tab for the Enterprise Connector appears. You can now configure the new Enterprise Connector.

  5. Click the sub-tab for the connector.

  6. In the Local Enterprise Connector Settings panel, type the JNDI name of the connector resource you created in To Create a Connector Resource for the Enterprise Connector (for example, mep/musicdb-ws) and click Save.

  7. In the Local Enterprise Connector Properties panel, specify a value for the uri property.

    For the MusicDB connector, this value is http://localhost:8080/musicdb-ws/resources/albums.

  8. (Optional) If you are configuring the MusicDB Enterprise Connector, in the same panel, specify a value of true for the useTimestamps property.

    If you do not set the property, or if you set it to false, the MusicDB connector will use digests instead of timestamps to determine whether a business object has been updated since the last synchronization.

  9. Click Save in the Local Enterprise Connector Properties panel.

Next Steps

You can now perform synchronizations using the Enterprise Connector.