Rebecca Searls' Blog

pageicon Thursday Feb 19, 2009

Mobilizing a Web Service Using a JAX-RS Connector in Sun GlassFish Mobility Platform 1.1 - Part II

In Part I, we took a cursory look at the Salesforce sample application.  Now let's take a closer look at the JAX-RS connector interface.

The JAX-RS connector template is included in the Sun GlassFish Mobility Platform 1.1 bundle.  It is provided as a Maven archtype [1].  To install it in your repository and make it available to your IDE, do the following:

       1. Download Sun GlassFish Mobility Platform 1.1 and the client bundle, available here.

       2. Install Sun GlassFish Mobility Platform 1.1.

       3. Change to the lib directory in your Sun GlassFish Mobility Platform domain:

cd $AS_HOME/domains/mep/lib

       4. Install the maven archetype in your repository:

mvn install:<install-file>
           -DgroupId=com.sun.mep
           -DartifactId=mep-connector-jaxrs-archetype-rar
           -Dversion=3.1.39
           -Dpackaging=jar
           -Dfile=mep-connector-jaxrs-archetype-rar-3.1.39.jar


I use Netbeans IDE 6.5.  This is how I create a project with this archtype:

  1. Start NetBeans IDE.
  2. Select File -> New Project.
  3. Select category Maven, project Maven Project, and press the Next  button.
  4. From the Maven Archtype list, select the node, Archtypes from remote Maven Repositories.
  5. Locate and select MEP Connector Archtype (JAX-RS) then press the Next button.
  6. Type the name of the project in the Name field, accept the  default settings for the remaining fields, and then press Finish.

Two Java files are provided in the Maven archtype: MyBusinessObjectsResource and MyBusinessObjectResource. File MyBusinessObjectsResource is equivalent to the BusinessObjectProvider  class that Santiago Pericas-Geertsen describes in his blog [3].  It contains the entry point method to the connector, lifeCycle() and the method for the R or retrieve operation, getBusinessObjects().

The lifeCycle() method is called at the start and end of each synchronization session.  In the Salesforce connector, it is used to connect to and disconnect from the Salesforce web serverice.  As a convenience, this method returns the EXTENSION variable, which identifies the 3-character file extension that is used by all business objects returned by the connector. The default extension is obj but often a connector-specific extension is defined, making it easier for the client application to unambiguously distinguish objects.  This connector uses the default value, as shown.

@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")) {
            sfws.createConnection(user, password);
        }
        else if (operation.equals("terminate")) {
            sfws.logout(user, password);
        }
        else {
            throw new RuntimeException("Lifecycle operation " + operation + " not understood");
        }
        return EXTENSION;
    }



The getBusinessObjects() method retrieves the appropriate records from the backend repository.  In Salesforce connector, the method queries the SalesForce backend database for all account data from the identified user's Developer account.  A list of serialized BusinessObjects is returned. [2]

@GET
    @Produces("text/xml")
    public BusinessObjects getBusinessObjects(
            @QueryParam("username") @DefaultValue("username") String user,
            @QueryParam("password") @DefaultValue("password") String password,
            @QueryParam("sessionId") @DefaultValue("") String sessionId)
    {
            //- Get salesforce connection from the connection pool
        try {
            BusinessObjects result = new BusinessObjects();
            List<BusinessObject> resultList = result.getBusinessObject();

            //- call local method to get records from the web service
            //- Serialize data as a (EBCO) BusinessObject.
            List<BusinessObject> aList = sfws.getSFBusinessObjects();
            for (BusinessObject s : aList) {
                byte[] tmpB = ((BaseObjCommon)s).serialize();
                resultList.add((BusinessObject)s);
            }
            return result;
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


This method returns the requested business object, identified by parameter, id.

@Path("{id}")
    public MyBusinessObjectResource getBusinessObject(@PathParam("id") String id) {
        return new MyBusinessObjectResource(context);
    }

The other class, MyBusinessObjectResource, defines the interfaces for  CURD (create, retrieve, update, and delete) operations.  It is equivalent to the BusinessObject abstraction. [2]


This is a retrieve method.  The requested business object is identified by parameter, id.  A serialized BusinessObject is returned.

@GET
    @Produces("application/octet-stream")
    public byte[] getBusinessObject(
            @QueryParam("username") @DefaultValue("username") String user,
            @QueryParam("password") @DefaultValue("password") String password,
            @QueryParam("sessionId") @DefaultValue("") String sessionId,
            @PathParam("id") String id)
    {
        //-  setup code
        bObj = null;
        byte[] b = new byte[0];
        BusinessObject bObj = sfws.getSFObject(id);

        if (bObj != null){
            b= bObj.serialize();
        }
        return b;
    }


This method creates or updates a business object sent from the client application in the backend repository.  In the Salesforce connector, I deserialized the data and put it into a Salesforce web service-accessible format for updating.

@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)
    {
        try {
            ByteArrayInputStream in = new ByteArrayInputStream(object);
            DataInputStream dIn = new DataInputStream(in);
            String filePrefix = dIn.readUTF();

            SFObject sfo = new SFObject();
            sfo.deserialize(dIn);
            sfws.updateSFObject(sfo);

            dIn.close();
            in.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


This method deletes a business object, which the client application tagged for deletion, from the backend repository.  I deserialized the data and put it into a Salesforce web service-accessible format for deletetion.

@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)
    {
        try {
            ByteArrayInputStream in = new ByteArrayInputStream(object);
            DataInputStream dIn = new DataInputStream(in);
            String filePrefix = dIn.readUTF();

            SFObject sfo = new SFObject();
            sfo.deserialize(dIn);
            sfws.deleteSFObject(sfo);

            dIn.close();
            in.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


This method can be called to resolve an update conflict.  The Salesforce connector does not currently make use of it.

@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)
    {
        // INSERT CODE: merge entries 0 and 1 in objects.getBusinessObject()
        return null;
    }



All of these methods, except for the one mentioned next, are accessed via the URL http://server:port/context-root/businessObjects.  Method MyBusinessObjectsResource.getBusinessObject() is accessed by the URL http://server:port/context-root/businessObjects/{id}, where id identifies the specific object to retrieve.

In Part III, I'll discuss some of the design challenges that we encountered as we developed this connector and explain how we resolved them.

-----
[1] Developing MEP Connectors - Part IV
http://weblogs.java.net/blog/spericas/archive/2008/10/developing_mep_3.html

[2] Developing MEP Connectors - Part 1
http://weblogs.java.net/blog/spericas/archive/2008/07/developing_mep.html

    Developing MEP Connectors - Part II
http://weblogs.java.net/blog/spericas/archive/2008/08/developing_mep_1.html

[3] Developing MEP Connectors - Part III
http://weblogs.java.net/blog/spericas/archive/2008/10/developing_mep_2.html


Comments:

Post a Comment:
  • HTML Syntax: NOT allowed

« December 2009
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
Today

Feeds

Search this blog

Links

Weblog menu

Today's referrers

Today's Page Hits: 18