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

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();
    }