Sun Java System Mobile Enterprise Platform 1.0 Developer's Guide for Enterprise Connectors

Accessing a Sun JCA Adapter for an EIS/EAI System

If you are designing your Enterprise Connector to access an EIS/EAI system instead of a database, the connector must access the Sun JCA Adapter for that system instead of making JDBC calls.

You must first create an Object Type Definition (OTD) that maps your business object properties to data on the EIS/EAI system. To create the OTD, you need to use the NetBeans IDE with plugins that are provided with MEP.

After you create the OTD, use the NetBeans code completion feature to call methods on the classes generated by the OTD wizard.

This section covers the following topics:

Creating an Object Type Definition (OTD)

For information on working with Sun JCA Adapters, see the Designing section of the Java CAPS documentation. Specific sections you will need to look at include the following:

To obtain the NetBeans plugins needed to create an OTD, follow the instructions in Installation of Netbeans Modules. The NetBeansModules referred to in these instructions are part of your MEP installation. In the location where you unzipped the installation bundle sjsmep-1_0-fcs-operating-system.zip, you will find them in the directory sjsmep-1_0-fcs/NetBeansModules. (The Java CAPS documentation states that they are in the directory AdapterPack/NetBeansModules/CommonLib, but for MEP they are in the directory NetBeansModules/commonlib.)

To develop an OTD for your application, follow the instructions appropriate to your EIS/EAI system in Developing OTDs for Application Adapters.

The instructions for adapters supported by MEP are in the following sections:

An Enterprise Connector is a Sun JCA Adapter client application that is not an Enterprise JavaBeans (EJB) component. You may find that you need to create the OTD and develop the Enterprise Connector inside an EJB project. However, you should then remove the Enterprise Connector and OTD from the EJB JAR file and place them in an ordinary JAR file before you place the JAR file in the domains/mep/lib directory for the Application Server. The OTD is generated in a separate JAR file, so it is easy to copy it to another project.

Writing Code to Access a Sun JCA Adapter

To access a Sun JCA Adapter, your code needs to use Java CAPS APIs, which are documented at http://developers.sun.com/docs/javacaps/reference/javadocs/index.jsp.

This section describes how to extend the ECBO classes to access a Sun JCA Adapter.

The default implementation of the TransactionManager class may be sufficient for your application.

Extending the BusinessObjectProvider Class to Access a Sun JCA Adapter

To allow your Enterprise Connector to work with a Sun JCA Adapter, your BusinessObjectProvider implementation needs to create a connection to the Adapter in its initialize method, close that connection in its terminate method, and retrieve objects through the Adapter in its getBusinessObject method.

For a SAP BAPI application, for example, you import the following packages:

import com.stc.connector.appconn.common.ApplicationConnectionFactory;
import com.stc.connector.appconn.common.ApplicationConnection;
import com.stc.connector.sapbapiadapter.appconn.SAPApplicationConnection;
import com.stc.util.OtdObjectFactory;

When you create a provider for a Customer business object, you declare objects like the following. The customer.Customer class is generated by the OTD wizard.

public class CustomerProvider extends BusinessObjectProvider<Customer> {
    ...
    public static final String SAP_JNDI_DATASOURCE = "jcaps/sap";
    public static final String REPOSITORY_NAME = "SAPRepository";
    
    private ApplicationConnectionFactory mJCAsapadapter = null;
    private ApplicationConnection mJCAsapadapterConnection = null;
    private customer.Customer mJCAsapcustomerCommObj = null;

The provider's initialize method then allocates these resources. It obtains an ApplicationConnectionFactory object by means of a JNDI lookup, then uses the factory to create the ApplicationConnection object. These method calls are the same no matter which EIS/EAI system you are using:

    @Override
    public void initialize() {
        logger.debug("Initializing provider " + this);

        try {
            InitialContext ic = new InitialContext();
            // First get ApplicationConnectionFactory through JNDI lookup
            mJCAsapadapter = 
                (ApplicationConnectionFactory) ic.lookup(SAP_JNDI_DATASOURCE);

            /* Then create ApplicationConnection. One AppConn can be dynamically
             * allocated to a physical connection defined in connection pool;
             * this results in connection reuse according to JCA and Appserver
             * contract
             */
            mJCAsapadapterConnection = mJCAsapadapter.getConnection();

The initialize method then uses the OtdObjectFactory to create an instance of a SAP customer communication object. Methods called on this object are specific to the SAP OTD. The code casts the generic ApplicationConnection object mJCAsapadapterConnection to another application connection specific to SAP:

            /* Create Customer communication object
             */
            mJCAsapcustomerCommObj = 
                (customer.Customer) OtdObjectFactory.createInstance(null,
                    "customer.Customer");

            /* Set ApplicationConnection on Customer communication object
             */
            mJCAsapcustomerCommObj.setAppConn(
                (SAPApplicationConnection) mJCAsapadapterConnection);

The initialize method next uses the ECBO API SessionContext object to retrieve the user name and password. It then uses these values to create user credentials specific to SAP, and finally connects to the Sun JCA Adapter for SAP.

            // Get backend credentials from provider's context
            SessionContext sessionContext = getSessionContext();
            String param = sessionContext.getUsername();
            if (param != null) {
                mJCAsapcustomerCommObj.getSAPConnectionParams().setUserid(param);
            }
            param = sessionContext.getPassword();
            if (param != null) {
                mJCAsapcustomerCommObj.getSAPConnectionParams().setPassword(param);
            }
            mJCAsapcustomerCommObj.connectWithNewParams();

            ic.close();
        }
        catch (Exception ex) {
            logger.debug("Initializing provider exception" + ex.getMessage());
            throw new RuntimeException(ex);
        }
    }

The terminate method closes the connection created by the initialize method:

    @Override
    public void terminate() {
        logger.debug("Terminating provider " + this);

        try {
            if (mJCAsapadapterConnection != null) {
                mJCAsapadapterConnection.close();
                logger.info("terminate provider close connection" 
                        + mJCAsapadapterConnection.toString());
            }
        }
        catch (Exception e) {
            logger.debug("terminating provider exception" + e.getMessage());
            throw new RuntimeException(e);
        }
    }

The provider code also implements a utility method, getSAPCustomerClient, which retrieves the customer communication object:

    /** 
     * @return SAPCustomerClient object that can be used to 
     * operate on a Customer BAPI.
     */
    public customer.Customer getSAPCustomerClient() {
        return mJCAsapcustomerCommObj;
    }

The getBusinessObjects method uses the getSAPCustomerClient method to retrieve the SAP customer data and store it in the Enterprise Connector's Customer object. It again calls methods on the communication object generated by the OTD wizard.

    @Override
    //Retrieve all IDocs and map here between Customer and VendorAccount Object
    public List<Customer> getBusinessObjects() {
        logger.debug("Getting objects from provider " + this);

        HashMap<String, Customer> customerMap = new HashMap<String, Customer>();

        try {
            // Getting customer list
            getSAPCustomerClient().getGetList().getIDRANGE(0).setOPTION("CP");
            getSAPCustomerClient().getGetList().getIDRANGE(0).setLOW("*");
            logger.info("Executing Customer with the following values Option " +
                "[" + getSAPCustomerClient().getGetList().getIDRANGE(0).getLOW() 
                + "] Option [" 
                + getSAPCustomerClient().getGetList().getIDRANGE(0).getOPTION() + "]");
            getSAPCustomerClient().getGetList().execute();

            // Process returned data and populate customer list
            customer.Customer.GetList.ExportParams.RETURN ret =
                getSAPCustomerClient().getGetList().getExportParams().getRETURN();
            logger.info("Retrieved [" 
                + getSAPCustomerClient().getGetList().countADDRESSDATA() 
                + "] customers");

            customer.Customer.GetList.ADDRESSDATA[] addressList =
                getSAPCustomerClient().getGetList().getADDRESSDATA();
            for (int i = 0; i < addressList.length; i++) {
                customer.Customer.GetList.ADDRESSDATA addr = addressList[i];

                // Ignore companies whose names start with "DELETED" -- hack
                if (!addr.getNAME().startsWith("DELETED")) {
                    // Ignore customers whose names are repeated
                    if (customerMap.containsKey(addr.getNAME())) {
                        continue;
                    }

                    // Create a new Customer instance 
                    Customer comp = new Customer(this);

                    // Set unique name for business object
                    comp.setName(addr.getNAME());

                    // Set customer number and name
                    comp.setCustomerNumber(addr.getCUSTOMER());
                    comp.setCustomerName(addr.getNAME());

                    // Get sales area
                    getSAPCustomerClient().getGetSalesAreas().getImportParams()
                        .setCUSTOMERNO(comp.getCustomerNumber());
                    getSAPCustomerClient().getGetSalesAreas().execute();
                    String retNo = getSAPCustomerClient().getGetSalesAreas()
                        .getExportParams().getRETURN().getMESSAGE();
                    String retMsg = getSAPCustomerClient().getGetSalesAreas()
                        .getExportParams().getRETURN().getCODE();
                    logger.info("Return Number [" + retNo + "] retMsg [" 
                        + retMsg + "].");
                    if (retNo.length() > 0) {
                        throw new RuntimeException(retMsg);
                    }

                    // Set sales related fields
                    comp.setSalesOrg(getSAPCustomerClient().getGetSalesAreas()
                        .getSALESAREAS(0).getSALESORG());
                    comp.setDistChannel(getSAPCustomerClient().getGetSalesAreas()
                        .getSALESAREAS(0).getDISTRCHN());
                    comp.setDivision(getSAPCustomerClient().getGetSalesAreas()
                        .getSALESAREAS(0).getDIVISION());

                    // Get detail on customer
                    getSAPCustomerClient().getGetDetail1().getImportParams()
                        .setCUSTOMERNO(comp.getCustomerNumber());
                    getSAPCustomerClient().getGetDetail1().getImportParams()
                        .setPI_SALESORG(comp.getSalesOrg());
                    getSAPCustomerClient().getGetDetail1().getImportParams()
                        .setPI_DISTR_CHAN(comp.getDistChannel());
                    getSAPCustomerClient().getGetDetail1().getImportParams()
                        .setPI_DIVISION(comp.getDivision());
                    getSAPCustomerClient().getGetDetail1().execute();
                    retNo = 
                        getSAPCustomerClient().getGetDetail1().getExportParams()
                            .getRETURN().getMESSAGE();
                    retMsg = 
                        getSAPCustomerClient().getGetDetail1().getExportParams()
                            .getRETURN().getNUMBER();
                    logger.info("Return Number [" + retNo + "] retMsg [" 
                        + retMsg + "].");
                    if (retNo.length() > 0) {
                        throw new RuntimeException(retMsg);
                    }

                    // Populate customer object data
                    customer.Customer.GetDetail1.ExportParams.PE_COMPANYDATA 
                        currAddr = getSAPCustomerClient().getGetDetail1()
                            .getExportParams().getPE_COMPANYDATA();
                    comp.setCity(currAddr.getCITY());
                    comp.setPostalCode(currAddr.getPOSTL_COD1());
                    comp.setStreet(currAddr.getSTREET());
                    comp.setCountryKey(currAddr.getCOUNTRY());
                    comp.setLanguageKey(currAddr.getLANGU_ISO());
                    comp.setRegion(currAddr.getREGION());
                    comp.setTelephone(currAddr.getTEL1_NUMBR());
                    comp.setFaxNumber(currAddr.getFAX_NUMBER());
                    comp.setCurrencyKey(currAddr.getCURRENCY());

                    customerMap.put(comp.getCustomerName(), comp);
                }
            }
            return new ArrayList<Customer>(customerMap.values());
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

The getRepositoryName and newBusinessObject methods have implementations very similar to those in the MusicAlbumProvider class:

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

    @Override
    public Customer newBusinessObject() {
        return new Customer(this);
    }

The other methods in the provider class use the default BusinessObjectProvider implementation: getSessionContext, setSessionContect, and getTransactionManager.

Extending the BusinessObject Class to Access a Sun JCA Adapter

The BusinessObject class for an Enterprise Connector that accesses a Sun JCA Adapter may have straightforward implementations of the BusinessObject methods, but it may also require some additional utility methods. A SAP BAPI Customer object, for example, implements a large number of getter and setter methods for its properties. Its serialize and deserialize methods can be relatively simple.

The Customer object implementations of the getInsertCommand, getUpdateCommand, and getDeleteCommand methods call the constructors for the command classes, as expected. However, here the constructors take two arguments, and the second argument is the value returned by a utility method.

    /**
     * {@inheritDoc}
     */
    @Override
    public CustomerInsertCommand getInsertCommand() {
        return new CustomerInsertCommand(this, getInsertCustomer());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CustomerUpdateCommand getUpdateCommand() {
        return new CustomerUpdateCommand(this, getUpdateCustomer());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CustomerDeleteCommand getDeleteCommand() {
        return new CustomerDeleteCommand(this, getDeleteCustomer());
    }

The utility methods use the provider class's getSAPCustomerClient method to retrieve first the customer communication object, and then the CreateFromData1 object. For example, the getInsertCustomer method begins as follows:

    /**
     * Returns a Customer CreateFromData1 object to be used for insert.
     */
    public customer.Customer.CreateFromData1 getInsertCustomer() {
        customer.Customer.CreateFromData1 cfd = getBusinessObjectProvider()
                .getSAPCustomerClient().getCreateFromData1();

The rest of the getInsertCustomer method uses the CreateFromData1 object to assign the Customer properties to the SAP BAPI customer object. Finally, it returns the CreateFromData1 object.

       // Set import parameters
        cfd.getImportParams().getPI_COPYREFERENCE().setREF_CUSTMR(
            CustomerProvider.REF_CUSTOMER);
        cfd.getImportParams().getPI_COPYREFERENCE().setSALESORG(getSalesOrg());
        cfd.getImportParams().getPI_COPYREFERENCE().setDISTR_CHAN(getDistChannel());
        cfd.getImportParams().getPI_COPYREFERENCE().setDIVISION(getDivision());

        // Required import parameters
        cfd.getImportParams().getPI_COMPANYDATA().setNAME(getCustomerName());
        cfd.getImportParams().getPI_COMPANYDATA().setLANGU_ISO(getLanguageKey());
        cfd.getImportParams().getPI_COMPANYDATA().setCURRENCY(getCurrencyKey());
        cfd.getImportParams().getPI_COMPANYDATA().setCOUNTRY(getCountryKey());
        cfd.getImportParams().getPI_COMPANYDATA().setPOSTL_COD1(getPostalCode());
        cfd.getImportParams().getPI_COMPANYDATA().setCITY(getCity());

        // Additional import parameters
        cfd.getImportParams().getPI_COMPANYDATA().setSTREET(getStreet());
        cfd.getImportParams().getPI_COMPANYDATA().setREGION(getRegion());
        cfd.getImportParams().getPI_COMPANYDATA().setTEL1_NUMBR(getTelephone());
        cfd.getImportParams().getPI_COMPANYDATA().setFAX_NUMBER(getFaxNumber());

        return cfd;
    }

The getUpdateCustomer method is almost identical to the getInsertCustomer method except that it also marks the fields as having changed:

        // Mark fields to be changed
        String ex = "X";
        cfd.getImportParams().getPI_COMPANYDATAX().setNAME(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setLANGU_ISO(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setCURRENCY(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setCOUNTRY(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setPOSTL_COD1(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setCITY(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setSTREET(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setREGION(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setTEL1_NUMBR(ex);
        cfd.getImportParams().getPI_COMPANYDATAX().setFAX_NUMBER(ex);

Similarly, the getDeleteCustomer method informs SAP to delete a record by changing its name to begin with the string DELETED:

        // Mark customer as deleted by prepending "DELETE" to the name
        setCustomerName("DELETED " + getCustomerName());
        logger.fine("Changing NAME field to [" + getCustomerName() + "].");
        cfd.getImportParams().getPI_COMPANYDATA().setNAME(getCustomerName());
        cfd.getImportParams().getPI_COMPANYDATAX().setNAME("X");
        cfd.getImportParams().getPI_COMPANYDATA().setLANGU_ISO(getLanguageKey());
        cfd.getImportParams().getPI_COMPANYDATA().setCURRENCY(getCurrencyKey());
        cfd.getImportParams().getPI_COMPANYDATA().setCOUNTRY(getCountryKey());
        cfd.getImportParams().getPI_COMPANYDATA().setPOSTL_COD1(getPostalCode());
        cfd.getImportParams().getPI_COMPANYDATA().setCITY(getCity());
        cfd.getImportParams().setCUSTOMERNO(getCustomerNumber());
        cfd.getImportParams().setPI_SALESORG(getSalesOrg());
        cfd.getImportParams().setPI_DISTR_CHAN(getDistChannel());
        cfd.getImportParams().setPI_DIVISION(getDivision());

Extending the InsertCommand, UpdateCommand, and DeleteCommand Classes to Access a Sun JCA Adapter

For the three command classes, you need to use generated classes and methods from the OTD. For a SAP BAPI application, for example, you need to import the following packages for the CustomerInsertCommand implementation:

import customer.Customer.CreateFromData1;
import customer.Customer.CreateFromData1.ExportParams.RETURN;

You then use the first of the imported classes in the class constructor, which takes two arguments instead of the single argument of the default implementation:

    public CustomerInsertCommand(Customer bobject, CreateFromData1 cfd) {
        super(bobject);
        mCreateFromData = cfd;
        logger.debug("Creating instance " + this);
    }

The CreateFromData1 object passed to the constructor is the returned value from the Customer class's getInsertCustomer method.

You implement the execute command by calling the class's own execute method and retrieving any return value through the second imported class:

    @Override
    public void execute() {
        try {
            mCreateFromData.execute();
            RETURN ret = mCreateFromData.getExportParams().getRETURN();
            String retMsg = ret.getMESSAGE();
            String message = "SAP (" + ret.getNUMBER() + "): " + retMsg;
            logger.info(message);
            if (retMsg.length() > 0) {
                throw new RuntimeException(message);
            }
        }
        catch (RuntimeException e) {
            throw e;
        }
        catch (Exception e) {
            logger.severe(e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
    }

For both the CustomerDeleteCommand and the CustomerUpdateCommand implementations, you import the following:

import customer.Customer.ChangeFromData1;
import customer.Customer.ChangeFromData1.ExportParams.RETURN;

The constructors and the execute methods for these classes use the ChangeFromData1 object returned from the Customer.getUpdateCustomer and Customer.getDeleteCustomer methods. Otherwise the class implementations are identical to those of the CustomerInsertCommand implementation.