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

Chapter 3 Creating an Enterprise Connector Using the Enterprise Connector Business Objects (ECBO) API

A Sun GlassFish Mobility Platform Enterprise Connector that uses the Enterprise Connector Business Object (ECBO) API typically extends five classes in the API and also includes an XML file that defines parameters used by the underlying implementation.

For details on the ECBO API classes and methods, see Chapter 5, Classes and Methods in the Enterprise Connector Business Object API Package. The API documentation is also included in the Sun GlassFish Mobility Platform client bundle. In the directory where you unzipped the client bundle (see the Sun GlassFish Mobility Platform 1.1 Installation Guide for details), it is in the subdirectory sgmp-client-1_1_01-fcs-b02/doc/ecbo/api.

This chapter uses the MusicDB ECBO sample Enterprise Connector provided with Sun GlassFish Mobility Platform to demonstrate how to use the ECBO API. 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.

By default, the MusicDB ECBO sample Enterprise Connector is deployed and configured in an installed version of Sun GlassFish Mobility Platform. You can view it in the Sun GlassFish Enterprise Server and Sun GlassFish Mobility Platform Administration Consoles as an example of how to deploy and configure an Enterprise Connector.

The source code for the MusicDB ECBO 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 in the subdirectory sgmp-client-1_1_01-fcs-b02/samples/secure-musicdb/src/connector/ecbo/. The ZIP file unzips as a Maven project.

This chapter covers the following topics:

Classes in the Enterprise Connector Business Object API

The Enterprise Connector Business Object (ECBO) API contains the following classes:

The MusicDB example implements its own versions of all of these classes except for SessionContext. It uses the default implementation of SessionContext.

See Chapter 5, Classes and Methods in the Enterprise Connector Business Object API Package, for summaries of the classes and methods in the ECBO API packages.

To synchronize data with an EIS/EAI system such as Siebel or SAP, your BusinessObjectProvider implementation and the three command implementations will need to call methods that access the Sun JCA Adapter for that system. These methods are provided by the Sun Java Composite Application Platform Suite (Java CAPS). See Chapter 4, Accessing a Sun JCA Adapter for an EIS/EAI System for details.

Extending the BusinessObject Class

The BusinessObject class holds the data you need to synchronize. In addition to the required properties name and extension, you specify properties and define getter and setter methods for this data.

For details on this class, see The BusinessObject Class.

The name property is the most important BusinessObject property. This property defines the identity of the business object. The name you specify must be unique within your database or EIS/EAI system.

For the MusicDB example, the class that extends BusinessObject is MusicAlbum. The source file MusicAlbum.java begins by importing Java SE packages and the required ECBO API class that it extends:

package com.sun.mep.connector.jdbc.album;

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

import com.sun.mep.connector.api.BusinessObject;

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

public class MusicAlbum extends BusinessObject<MusicAlbumProvider> {

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

This example then declares its data properties (there are only four in addition to the name property):

    /**
     * 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 and defines the one-argument constructor, which takes the MusicAlbumProvider as its argument:

    /**
     * String builder used to return SQL commands.
     */ 
    StringBuilder stringBuilder = new StringBuilder();
    
    public MusicAlbum(MusicAlbumProvider provider) {
        super(provider);
    }

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

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

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

The code next implements the getLastModified and setLastModified methods:

    /**
     * Returns last modified timestamp.
     */
    @Override
    public long getLastModified() {
        if (getBusinessObjectProvider().useTimestamps()) {
            return lastModified;
        }
        else {
            throw new UnsupportedOperationException(getClass().getName() +
                    " does not support lastModified timestamp");
        }
    }

    /**
     * Sets last modified timestamp.
     */
    @Override
    public void setLastModified(long millis) {
        if (getBusinessObjectProvider().useTimestamps()) {
            lastModified = millis;
        }
        else {
            throw new UnsupportedOperationException(getClass().getName() +
                    " does not support lastModified timestamp");
        }
    }

If the Enterprise Connector does not use timestamps, the methods throw exceptions that cause the Gateway to use digests.

The class implements the getExtension method by specifying .alb as the file extension for MusicAlbum objects. This extension must match the extension used by the client.

    @Override
    public String getExtension() {
        return ".alb";
    }

The class does not implement its own versions of the getName and setName methods; instead, it uses the versions defined in the BusinessObject class.

The class uses Java Serialization to implement the serialize and deserialize methods as follows. Note the calls to the BusinessObject versions of getName and setName in addition to the getter and setter methods defined by MusicAlbum. The format used in the serialize and deserialize methods is part of the contract between the client 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 the getInsertCommand, getUpdateCommand, and getDeleteCommand methods using the constructors specific to this business object:

    /**
     * {@inheritDoc}
     */
    @Override
    public MusicAlbumInsertCommand getInsertCommand() {
        return new MusicAlbumInsertCommand(this, getSQLConnection(),
                getInsertString());
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public MusicAlbumUpdateCommand getUpdateCommand() {
        return new MusicAlbumUpdateCommand(this, getSQLConnection(),
                getUpdateString());
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public MusicAlbumDeleteCommand getDeleteCommand() {
        return new MusicAlbumDeleteCommand(this, getSQLConnection(),
                getDeleteString());
    }

One of the constructor arguments for each command is the value returned by a helper method (getInsertString, getUpdateString, getDeleteString) that generates an SQL statement string. These methods are implemented as follows:

    /**
     * Returns an SQL insert statement to add this instance
     * to the database.
     */
    public String getInsertString() {
        stringBuilder.setLength(0);
        stringBuilder.append(hasLastModified() ? 
            "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(getBusinessObjectProvider().getUsername())
            .append("'");
        
        if (hasLastModified()) {
            Timestamp timestamp = new Timestamp(getLastModified());
            stringBuilder.append(", TIMESTAMP '")
                    .append(timestamp.toString())
                    .append("'");
        } 
        stringBuilder.append(')');
        
        return stringBuilder.toString();
    }

    /**
     * Returns an SQL update statement to modify this instance
     * in the 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 (hasLastModified()) {
            Timestamp timestamp = new Timestamp(getLastModified()); 
            stringBuilder.append(", last_modified = TIMESTAMP '")
                .append(timestamp.toString()).append("'");
        }
        
        stringBuilder.append(" WHERE name = '").append(getName())
             .append("' AND username = '")
             .append(getBusinessObjectProvider().getUsername())
             .append("'");       
        return stringBuilder.toString();
    }

    /**
     * Returns an SQL delete statement to remove this instance
     * from the database.
     */
    public String getDeleteString() {
        stringBuilder.setLength(0);
        stringBuilder.append("DELETE FROM album WHERE name = '")
                     .append(getName())
                     .append("' AND username = '" 
                            + getBusinessObjectProvider().getUsername() + "'");
        return stringBuilder.toString();
    }

You may notice that the SQL statements show an additional column, username, in the database's album table in addition to columns for the MusicAlbum class's properties (name, artist, datePublished, and rating). 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 MusicAlbumProvider.getBusinessObjects method to return only the albums for a particular user, so that multiple users can share the album table.

The getInsertString and getUpdateString methods also set the timestamp of the new or modified business object if the business object uses timestamps.

An additional method, getSelectString, is provided for testing purposes.

Another constructor argument for the commands is the value returned by another helper method, getSQLConnection, which returns a JDBC Connection object created by the MusicAlbumProvider class.

    /** 
     * Returns a connection object that can be used to 
     * execute SQL commands.
     */
    public Connection getSQLConnection() {
        return getBusinessObjectProvider().getSQLConnection();
    }

Extending the BusinessObjectProvider Class

The BusinessObjectProvider class serves several purposes:

For details on this class, see The BusinessObjectProvider Class.

For the MusicDB example, the class that extends BusinessObjectProvider is MusicAlbumProvider. Like the file for the MusicAlbum class, the MusicAlbumProvider.java source file begins by importing Java SE packages and the required ECBO API classes. It then begins by setting up a logger and declaring some string constants, a JDBC connection object, its implementation of the TransactionManager class, and a user name object:

public class MusicAlbumProvider extends BusinessObjectProvider<MusicAlbum> {

    static final Logger logger = BusinessObjectProvider.getLogger();
    
    public static final String REPOSITORY_NAME = "MusicDbRepository";    
    public static final String MUSICDB_JNDI_DATASOURCE = "jdbc/musicdb";
    public static final String DB_USER_NAME = "musicdbuser";
    public static final String DB_USER_PASS = "musicdbpass";
    Connection sqlConnection = null;
    
    MusicAlbumTransactionManager transactionManager;

    String username;

The REPOSITORY_NAME value is identical to the repository name specified in the resource file for the Enterprise Connector. In the MusicDB sample, the resource file is named MusicDbRepository.xml and defines a repository named MusicDbRepository. The MUSICDB_JNDI_DATASOURCE value defines the name of the JDBC Datasource used to connect with the database.

The code implements two forms of the business object constructor: the no-argument constructor specified by the API and a one-argument form that takes a user name as argument for testing purposes.

Next, the code implements the two lifecycle methods for the BusinessObjectProvider class, initialize and terminate, which coincide with the start and end of a synchronization session.

The initialize method allocates resources required for a synchronization session or for database authentication. In this case, the code does the following:

    /**
     * Creates a connection to the {@link #MUSICDB_JNDI_DATASOURCE}
     * database.
     */
    @Override
    public void initialize() {
        logger.fine("Initializing provider " + this);
       
        try {
            Context jndiContext = new InitialContext();
            DataSource ds = null;

            // If unable to get JNDI datasource, use local one for testing
            try {
                ds = (DataSource) jndiContext.lookup(MUSICDB_JNDI_DATASOURCE);
            } catch (NoInitialContextException e) {
                ds = new MusicDbDataSource();       // testing only!
            }

            // Get database credentials from provider's context
            SessionContext sessionContext = getSessionContext();
            username = sessionContext.getUsername();
            if (username == null || !username.startsWith(DB_USER_NAME)) {
                throw new RuntimeException("A MusicDB user name must start " 
                        + "with the '"+ DB_USER_NAME + "' prefix");
            }
            
            // Get connection using default credentials
            sqlConnection = ds.getConnection(DB_USER_NAME, DB_USER_PASS);
            
            // Init transaction manager
            transactionManager = new MusicAlbumTransactionManager();
        }
        catch (SQLException ex) {
            throw new RuntimeException(ex);
        } 
        catch (NamingException ex) {
            throw new RuntimeException(ex);
        } 
    }

The implementation of the terminate method releases any resources allocated by the initialize method. In this case, it closes the JDBC connection.

    /**
     * Closes a connection to the {@link #MUSICDB_JNDI_DATASOURCE}
     * database.
     */
    @Override
    public void terminate() {
        logger.debug("Terminating provider " + this);   
       
        try {
            if (sqlConnection != null) {
                sqlConnection.close();
            }
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

The implementation of the getRepositoryName method specifies the string value declared at the beginning of the class. The repository in question is the repository that is used for communication between the Gateway Engine and the Enterprise Connector and that is specified by the resource file.

    /**
     * {@inheritDoc}
     */
    @Override
    public String getRepositoryName() {
        return REPOSITORY_NAME;
    }

The implementation of the getBusinessObjects method uses a JDBC query to retrieve all the albums for the user username from the database, instantiates a MusicAlbum object for each retrieved album, and adds it to an ArrayList of albums.

    /**
     * {@inheritDoc}
     */
    @Override
    public List<MusicAlbum> getBusinessObjects() {
        logger.fine("Getting objects from provider " + this);   
       
        Statement stmt = null;
        List<MusicAlbum> albums = null;
        
        try {
            stmt = sqlConnection.createStatement();
            
            // Read all music albums and store them in array
            albums = new ArrayList<MusicAlbum>();
            ResultSet rs = stmt.executeQuery(
                    "SELECT * FROM album WHERE username = '" + username + "'");
            while (rs.next()) {
                MusicAlbum album = new MusicAlbum(this);
                album.setName(rs.getString(1));
                album.setArtist(rs.getString(2));
                album.setDatePublished(rs.getString(3).replace("-", ""));
                album.setRating(rs.getInt(4));
                if (album.hasLastModified()) {
                    album.setLastModified(rs.getTimestamp(6).getTime());
                }
                albums.add(album);
            }
            rs.close();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        } finally {
            if (stmt != null) {
                try { stmt.close(); } catch (Exception e) { /* ignore !*/ }
            }
        }        
        return albums;
    }

The implementation of the newBusinessObject method is much simpler: it calls the one-argument constructor for MusicAlbum.

    /**
     * {@inheritDoc}
     */
    @Override
    public MusicAlbum newBusinessObject() {
        return new MusicAlbum(this);
    }

The provider class also implements the helper method getSQLConnection, which returns the JDBC connection that was instantiated by the initialize method. This method is called by the MusicAlbum class.

    /** 
     * Returns a connection object that can be used to 
     * execute SQL commands.
     */
    public Connection getSQLConnection() {
        return sqlConnection;
    }

The provider class also implements a getUsername method that is called by the MusicAlbum class's utility methods:

    /**
     * Returns the user name logged into the session that
     * created this provider.
     */
    public String getUsername() {
        return username;
    }

The MusicDB provider also implements a useTimestamps method, which returns true if the provider is using timestamps to determine when an object was last modified. This method is not part of the ECBO API but is specific to this Enterprise Connector. Another connector may use another mechanism to implement the lastModified property.

    /**
     * Returns the value of parameter 'useTimestamps'. This 
     * parameter controls the use of timestamps over digests
     * by the gateway. Default value is false.
     */
    public boolean useTimestamps() {
        SessionContext sessionContext = getSessionContext();
        String v = sessionContext.getParameters().get("useTimestamps");
        return (v == null) ? false : v.equalsIgnoreCase("true");
    }

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 useTimestamps method sets the value to false.

The provider implements the mergeBusinessObjects method to resolve conflicts between the client and server:

    /**
     * If timestamps are available then pick the object that
     * has been updated last. Otherwise, since we don't know
     * which object was updated last, we just compute the
     * average of the ratings and assume that all the other
     * fields have not been updated.
     */
    @Override
    public void mergeBusinessObjects(MusicAlbum serverObject, 
            MusicAlbum clientObject) 
    {
        if (clientObject.hasLastModified() && serverObject.hasLastModified()) {
            if (clientObject.getLastModified() > serverObject.getLastModified()) {
                serverObject.setArtist(clientObject.getArtist());
                serverObject.setDatePublished(clientObject.getDatePublished());
                serverObject.setRating(clientObject.getRating());
            }
        }
        else {
            serverObject.setRating(
                    (clientObject.getRating() + serverObject.getRating()) / 2);
        }
    }

The getTransactionManager method retrieves the MusicAlbumTransactionManager that is declared at the beginning of the file, instantiated in the initialize method, and implemented within the MusicAlbumProvider.java file.

    /**
     * Returns a transaction manager that uses JDBC to start,
     * stop and abort transactions.
     */
    @Override
    public MusicAlbumTransactionManager getTransactionManager() {
        return transactionManager;
    }

Extending the TransactionManager Class

The MusicAlbumProvider.java file includes the implementation of the TransactionManager class. The TransactionManager class can be implemented in a separate file, but the relationships between the two classes mean that it is simpler to keep them together. It is also possible to use the default implementation of the TransactionManager class instead of implementing it yourself.

For details on this class, see The TransactionManager Class.

The MusicDB implementation of this class is called MusicAlbumTransactionManager. This class manages the database transactions for MusicAlbum objects using the JDBC API. It turns the database's auto-commit feature off if it has one and starts, stops, and aborts database transactions. The class definition begins with the constructor, which takes no arguments.

    public class MusicAlbumTransactionManager extends
            TransactionManager<MusicAlbumProvider> {

        public MusicAlbumTransactionManager() {
            super(MusicAlbumProvider.this);
            
            assert (sqlConnection != null);
            try {
                sqlConnection.setAutoCommit(false);
            }
            catch (SQLException e) {
                // Ignore if not supported by DB
            }
        }

The abortTransaction method calls the JDBC Connection.rollback method:

        @Override
        public void abortTransaction() {
            logger.debug("Aborting transaction on SQL connection " 
                    + sqlConnection);
            try {
                sqlConnection.rollback();
            }
            catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

The beginTransaction and endTransaction methods are closely linked. The endTransaction method commits the current transaction, an action that automatically starts the next transaction. The beginTransaction method simply calls endTransaction.

        @Override
        public void beginTransaction() {
            endTransaction();   // starts a new one
        }
        @Override
        public void endTransaction() {
            logger.debug("Starting/Committing transaction on SQL connection " 
                    + sqlConnection);
            try {
                sqlConnection.commit();
            }
            catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

Extending the InsertCommand, UpdateCommand, and DeleteCommand Classes

The InsertCommand, UpdateCommand, and DeleteCommand classes all extend the Command class.

For details on these classes, see The InsertCommand Class, The UpdateCommand Class, The DeleteCommand Class, and The Command Class.

For the MusicDB example, the implementations of the three classes are almost identical. The source files for the implementations are MusicAlbumInsertCommand.java, MusicAlbumUpdateCommand.java, and MusicAlbumDeleteCommand.java.

Each source file begins by importing Java SE packages and the required ECBO API classes. It then begins by setting up a logger and declaring two objects that are used by the constructor method and the execute method. For example, MusicAlbumInsertCommand.java begins as follows:

public class MusicAlbumInsertCommand extends InsertCommand<MusicAlbum> {

    static final Logger logger = BusinessObjectProvider.getLogger();
    
    private String sqlStatement;
    
    private Connection sqlConnection;

The code then extends the class constructor. While the constructor for the base class takes one argument, the constructor for each of the implementation classes takes three arguments: the MusicAlbum, a string that represents a SQL statement, and a JDBC connection. For example, the constructor for MusicAlbumUpdateCommand looks like this:

    public MusicAlbumUpdateCommand(MusicAlbum album,
            Connection sqlConnection, String sqlStatement)
    {
        super(album);
        this.sqlConnection = sqlConnection;
        this.sqlStatement = sqlStatement;
        logger.debug("Creating instance " + this + ": " + sqlStatement);
    }

Finally, the code for each command implements the execute method in exactly the same way. First, it uses the instantiated connection to create a JDBC Statement object using the instantiated string. Then it executes the statement.

    public void execute() {
        Statement stmt = null;
        
        try {
            logger.fine("Executing instance " + this + ": " + sqlStatement);
            stmt = sqlConnection.createStatement();
            stmt.executeUpdate(sqlStatement);
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        finally {
            if (stmt != null) {
                try { stmt.close(); } catch (Exception e) { /* ignore !*/ }
            }
        }
    }

Creating the Resource File for an Enterprise Connector

The resource file that you need to package with an Enterprise Connector (as described in Developing Connectors Using the Enterprise Connector Business Object (ECBO) API) is an XML file. The name of the file typically refers to the database or EIS/EAI system. For example, the resource file for MusicDB is named MusicDbRepository.xml. The file begins and ends as follows:

<jeceira xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    xmlns:jcr="http://www.jcp.org/jcr/1.0"
    xmlns:sync="http://www.synchronica.com/jcr/types"
    xmlns:aprzv="http://www.aparzev.com/jrc/aprzv"
    xmlns:udc="http://www.synchronica.com/udc/types/1.0">
    <repositories>
        <repository name="MusicDbRepository">

            <workspaces>
                <workspace name="MusicDbWorkspace" />
            </workspaces>
            ....
        </repository>
    </repositories>
</jeceira>

All resource files are identical except for two values:

To create your own resource file, you can copy the resource file from the sample Enterprise Connector source directory, rename it, and modify these two values. In the unzipped client bundle, you can find the file in sgmp-client-1_1_01-fcs-b02/samples/secure-musicdb/src/connector/ecbo/MusicDbRepository.xml.

You use these values when you configure the Enterprise Connector in the Sun GlassFish Mobility Platform Administration Console. See Using the Connectors Tab in Sun GlassFish Mobility Platform 1.1 Administration Guide for details.

Using NetBeans IDE To Create an ECBO Enterprise Connector

The simplest way to create an ECBO Enterprise Connector is to use NetBeans IDE 6.5 with the Maven plugin.

ProcedureTo Create a Maven Project for an ECBO Enterprise Connector

Before You Begin

Follow the instructions in To Install the Maven Plugin to set up NetBeans IDE.

  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 (RAR), then click Next.

  7. On the Name and Location page, accept all the default values, then click Finish.

    The new project, mavenproject2 (rar), appears in the Projects tab.

  8. Under mavenproject2, expand the Source Packages node.

  9. Expand the com.mycompany.mavenproject2 node.

    The following class files appear:

    • MyBusinessObject.java

    • MyBusinessObjectProvider.java

    • MyBusinessObjectDeleteCommand.java

    • MyBusinessObjectInsertCommand.java

    • MyBusinessObjectUpdateCommand.java

    These files provide the required classes for an ECBO Enterprise Connector.

  10. Expand the com.mycompany.mavenproject2.rar node.

    The following class files appear:

    • Adapter.java

    • InitializationJob.java

    • ManagedConnection.java

    • ManagedConnectionFactory.java

    • Metadata.java

    These classes implement the resource adapter. The ManagedConnectionFactory.java code contains a reference to the MyBusinessObjectProvider class.

  11. Rename the classes so that they are specific to your Enterprise Connector.

  12. In the ManagedConnectionFactory.java file, provide a unique ID for the serialVersionUID field to replace the value in the file.

    This step is not required, but it is recommended.

  13. Add code to the ECBO Enterprise Connector files.

    No changes to the resource adapter files are needed other than the name and unique ID changes.

  14. Build the project.

    When you build the project, all the class files are bundled together in a RAR file.

Deploying and Configuring an ECBO Enterprise Connector

The tasks involved in deploying and configuring an ECBO Enterprise Connector are very similar to the tasks for a JAX-RS Enterprise Connector.

By default, the MusicDB ECBO Enterprise Connector is both deployed and configured in an installed version of Sun GlassFish Mobility Platform. There is no need to redeploy it.

The steps to deploy and configure an ECBO Enterprise Connector are very similar to those in Building, Deploying, and Configuring a JAX-RS Enterprise Connector, with a few exceptions:

To Deploy an Enterprise Connector

You deploy the Enterprise Connector under the Connector Modules node of the Administration Console, not the Web Applications node. An ECBO Enterprise Connector is a resource adapter.

To Create a Connector Connection Pool for the Enterprise Connector

The resource adapter for the connection pool is the Enterprise Connector itself, so select the deployed ECBO Enterprise Connector from the Resource Adapter drop-down list.

To Verify that an Enterprise Connector Is Deployed

Do not perform this task.

To Configure the Enterprise Connector on Sun GlassFish Mobility Platform

In the Local Enterprise Connector Settings panel, the Workspace Name should be the same as the name specified for the workspace element in the resource file for the Enterprise Connector.

In the Local Enterprise Connector Properties panel, do not specify a value for the uri property. Instead, you may specify a value for the business-object-provider property (see the configured MusicDB ECBO Enterprise Connector for an example), but this is not required if you use the Maven plugin to create the Enterprise Connector.