Creating Flows for Adapter for EJB Services

Overview

This appendix explains how to build flow services in Designer that use adapter services created with Adapter for EJB. For more information about working with flow services, see the IBM webMethods Service Development Help for your release.

Note: The examples shown assume that the class files for deployed EJBs are located on Designer's classpath. If they are not, Designer will incorrectly display the value of any object it cannot de-serialize as “null” in the run-time results. This does not prevent the flow service from working properly. However, it can cause confusion for the user. To avoid this situation, make sure that the class files for any relevant objects (EJB or otherwise) are available to Designer.

About Flow Services and Adapter for EJB

Adapter for EJB enables you to configure adapter services that create or fetch EJB instances on the application server. You can then create adapter services that execute a public method exposed by an EJB. This model implies a logical sequence, or flow of events:

  • Execute the adapter service to create or fetch the EJB.
  • Then execute the adapter service that, in turn, invokes one of the EJB's business methods.

Standalone Adapter for EJB services have little value. Their value is only realized when the services are combined. There are several ways to do this on IBM webMethods Integration Server, but the most common way is to combine the two operations in a single flow service. Using Designer’s flow service editor you can link or map the inputs and outputs of any individual step in a flow service to other steps in the same flow service. This function is important for the following reasons:

  • Any execution of an InvokeEJB 2.1 or InvokeEJB 3.0 service must be preceded by an execution of a corresponding CreateEJB 2.1 or FetchEJB 3.0 service.
  • Any execution of an InvokeEJB 2.1 or InvokeEJB 3.0 service always requires at least one input: the output of a prior CreateEJB 2.1 or FetchEJB 3.0 execution. That is, before you can execute an EJB method, you must first obtain that EJB.
    Note: The CreateEJB 2.1, InvokeEJB 2.1, and CreateInvokeEJB 2.1 adapter services are used to access the 2.1 or earlier versions of EJBs. The FetchEJB 3.0, InvokeEJB 3.0, and FetchInvokeEJB 3.0 adapter services are used to access the 3.0 EJBs. The 2.1 adapter services cannot be used with the 3.0 adapter services and vice versa. For more information about the adapter’s services, see Adapter Services.

Obtaining an EJB

To obtain an EJB object, you can use either a CreateEJB 2.1 or FetchEJB 3.0 adapter service or a CreateInvokeEJB 2.1 or FetchInvokeEJB 3.0 adapter service. The EJB object returned can be a single EJB object, or multiple EJB objects. Multiple EJB objects are all of the same type.

The following sections explain how to:

  • Pass a single EJB object generated by calling a configured CreateEJB 2.1 or FetchEJB 3.0 adapter service instance to a corresponding configured InvokeEJB 2.1 or InvokeEJB 3.0 adapter service instance.
  • Use a loop to pass a single EJB object generated when multiple EJBs are returned by CreateEJB 2.1 or FetchEJB 3.0.
  • Use services to pass third-party or user-defined objects.

Working with a Single EJB Object Instance

To obtain an EJB object, use a CreateEJB 2.1 or FetchEJB 3.0 adapter service. Then in a flow service in Designer you use the pipeline to pass the EJB obtained by a CreateEJB 2.1 or FetchEJB 3.0 execution to a subsequent InvokeEJB 2.1 or InvokeEJB 3.0 instance. By editing the inbound pipeline of the corresponding InvokeEJB 2.1 or InvokeEJB 3.0 service, you can map the Results array of the CreateEJB 2.1 or FetchEJB 3.0 instance to the EJB parameter of the corresponding InvokeEJB 2.1 or InvokeEJB 3.0 instance.

For example, a CreateEJB 2.1 or FetchEJB 3.0 adapter service, services.fetch.GetComplexTraderEJB, creates a single EJB instance and returns it as the first element of the Results array, which Designer places in the pipeline.

To then pass this value to the corresponding InvokeEJB 2.1 or InvokeEJB 3.0 adapter service, services.invoke.InvokeComplexBuy, insert a link from the Results array in the pipeline to the EJB parameter in the corresponding InvokeEJB 2.1's or InvokeEJB 3.0's input signature as shown in the figure below.

To insert the link, click on the input side of the Pipeline view. At run time, the link tells Integration Server to assign the values in Results to EJB.

However, because the input is an array and the target destination parameter is a scalar, you need to indicate which array value to assign. To do this, open the Link Indices dialog box by selecting the link between the variables and clicking on the Pipeline view toolbar.

The Link Indices dialog box appears indicating all the parameters at each end of the selected link. Use the Results parameter to specify which element of Results to assign. Note that the services.fetch.GetComplexTraderEJB service will always return exactly one EJB instance, placing the EJB instance in the very first element of the Results array. Therefore in this example, you would enter the value of “0” into the Results parameter.

For more information about mapping data in flow services, see the IBM webMethods Service Development Help for your release.

Working with Multiple EJB Object Instances

To build on the example in Working with a Single EJB Object Instance, suppose that instead of returning a single EJB in the Results array, the CreateEJB 2.1 or FetchEJB 3.0 adapter service instance returns multiple EJB instances. In this case you use the LOOP step in Designer to make the flow service iterate over the contents of the array, and invoke one (or more) methods on each EJB in the array.

Note: For information about working with loops and the logic involved, see the IBM webMethods Service Development Help for your release.

Any given instance of a CreateEJB 2.1 adapter service will invoke a single method on the javax.ejb.EJBHome interface. This method is called to construct an instance of the actual EJB remote class and return it to the caller. This remote EJB instance is the EJB and is where the business methods of the EJB are found.

It is possible for a home interface method to return multiple remote EJB objects (“finder” methods on entity beans, for example). The CreateEJB 2.1 adapter service accounts for this with the Results array.

Similarly, for any given instance of a FetchEJB 3.0 adapter service, the service fetches the EJB based on the selected JNDI Name and returns the EJB Object to the caller.

The same principle applies to instances of CreateInvokeEJB 2.1 and FetchInvokeEJB 3.0 as well. However, it is somewhat more restrictive. CreateInvokeEJB 2.1 or FetchInvokeEJB 3.0 will automatically loop over each EJB returned and call the same business method on each. However, if you must be able to call more than one business method, you need to configure the necessary CreateEJB 2.1 or FetchEJB 3.0 and the corresponding InvokeEJB 2.1 or InvokeEJB 3.0 services and incorporate them into a flow service.

Regardless of how many EJB objects are returned by CreateEJB 2.1 or FetchEJB 3.0, each object is of the same remote EJB class. That is, they are the same type. Therefore, any invocation of a CreateEJB 2.1 or FetchEJB 3.0 adapter service will return one or more remote EJB objects of the same class.

This is important to note when designing flows. Because any given CreateEJB 2.1 or FetchEJB 3.0 instance is configured to work with a single EJB class, you can only expect to call the business methods exposed by that class. If in your flow you need to access some other EJB type, you will need to configure a separate CreateEJB 2.1 or FetchEJB 3.0 service for that type. If you attempt to pass an EJB object of one class into a corresponding InvokeEJB 2.1 or InvokeEJB 3.0 service configured for an entirely different class, you will see run-time exceptions.

The following example shows how to use the LOOP step in Designer to implement this processing. To begin, you configure a CreateEJB 2.1 or FetchEJB 3.0 adapter service instance (services.fetch.FindLargeAccountEJBs) that may return one or more EJBs in its Results array. You want to execute the same InvokeEJB 2.1 or InvokeEJB 3.0 adapter service against each EJB returned. However, you do not know how many EJBs might be returned.

For information about configuring CreateEJB 2.1 and FetchEJB 3.0 adapter services, see Configuring CreateEJB 2.1 Services and Configuring FetchEJB 3.0 Services respectively.

You must first define an Object list in the outbound pipeline immediately following FindLargeAccountEJBs. This list can have any valid name but it cannot be nested inside any other structure (for example, a Document). Create a link between the Results array and the new Object list in the outbound pipeline as shown in the figure below.

Now insert a LOOP between the two adapter service instances (services.fetch.FindLargeAccountEJBs and services.invoke:GetAccountBalance). In this example there is a single child step to be executed within the body of the LOOP step: this is the InvokeEJB 2.1 or InvokeEJB 3.0 adapter service instance (services.invoke.GetAccountBalance).

The loop should iterate once for each EJB found in the Beans array (the Object list defined earlier). To do this, enter the array's name (without the leading forward slash-Designer inserts this) in the Input array field in the Properties view.

You can see the steps for inserting a LOOP and entering the array’s name in the figure below.

Finally, complete the flow by selecting the services.invoke.GetAccountBalance service and creating a link between the Beans parameter (Designer automatically generated this parameter for you) and the input EJB parameter to the service as shown in the figure below.

Working with Different Object Types

EJB home and remote methods can take and return objects of practically any arbitrary type. The only real restriction is that the objects must implement the java.io.Serializable interface. How you work with these objects in your integrations depends on what it is that you need to do with the objects.

For example, if you need to pass an object generated by one adapter service call into a subsequent service in the same flow, you create the appropriate links in the flow service editor. Designer supports the java.lang.Object type natively. Any object that appears in the pipeline that is not recognized is treated as an Object. This is what happens when passing EJB objects themselves.

However, you may, at some point in your flow, need to interact directly with these objects. Suppose you need to assign a value to an object during the flow's execution, but have no service available for that purpose. Additionally, you want to create a document from such an object. How you provide this functionality depends on what type of object you are dealing with:

  • Designer and Integration Server provide some degree of support for the basic Java types: java.lang.String, java.lang.Float, float, etc. If you only need to do transformations on these types of objects, the support typically already exists either natively within Designer or is provided in one of the WmPublic services.
  • If you need to operate on a third-party, user-defined object type, you can create a utility as a Java service or a coded service using C/C++. The utility gets or creates the object you need, manipulates its properties, and then puts it on the pipeline.
    Note: You can create a coded service from within Designer or by using an outside Integrated Development Environment (IDE). For more information about building coded services, see the IBM webMethods Service Development Help for your release.

Creating Java Services to Use with Objects

This section provides two examples that demonstrate how to write simple Java services to get or create the necessary objects, manipulate the properties, and then place the objects on the pipeline within a flow.

  • The first example illustrates how to write a service that creates an instance of a user-defined class and then inserts it into the pipeline where it can be passed to an adapter service instance in a flow.
  • The second example shows how to extract a user-defined object from the pipeline and transform it into a document.

The EJB and associated classes used in these examples are as follows:

  • The target bean is a stateless session EJB-ComplexTrader. Its home interface exposes a create method that takes no arguments and returns an instance of a Trader.
  • The Trader object itself exposes two public methods: buy and sell. Each of these has the same signature:
    public  TradeResult buy (StockOrder order) throws RemoteException;  
    public  TradeResult sell (StockOrder order) throws RemoteException;

Example 1

The goal of this example is to write a flow that executes the buy method. Note that the buy signature contains two user-defined classes: TradeResult and StockOrder. The first example focuses primarily on StockOrder. In the flow, you need to create an instance of StockOrder with the appropriate attributes and then pass it to an InvokeEJB 2.1 or InvokeEJB 3.0 adapter service instance configured to execute the buy method. The easiest way to do this is to create a Java coded service that is specifically designed to accept input from the user at run time, create a StockOrder with those inputs, and then insert the StockOrder into the pipeline.

When creating the Java service, Designer automatically generated the public signature of the Java service, and its lone input argument is an instance of com.wm.data.IData.This is the pipeline referred to earlier.

Now look at the public interface of the StockOrder class:

package examples.ejb20.testbeans.stateless.ComplexTrader;  
import java.io.Serializable;  
  
/**  
 * This class represents a buy/sell order for a single security.  
 */  
  
public final class StockOrder implements Serializable  
{  
  // Order duration constants...  
  public static final int DAY = 0;   // Good for the Day  
  public static final int GTC = 1;   // Good 'Til Cancelled  
   
  // Order type constants (only long positions allowed)...  
  public static final int MARKET = 0;      // Execute at current market price  
  public static final int LIMIT = 1;       // For buys; execute at limit price or 
lower  
  public static final int STOP = 2;        // For sells; execute at stop price or 
higher  
  public static final int STOPLIMIT = 3;   // For sells; execute at no less that 
stop, no more than limit  
  public static final int ALLORNONE = 4;   // Execute entire order at market or 
none of it  
  public static final int FILLORKILL = 5;  // Execute entire order immediately at 
market or none of it  
  
  // Constructors  
  /**  
   * This constructor used for STOPLIMIT order types.  
   */  
  public StockOrder(String symbol, int quantity, int duration, int type, double 
stop, double limit);  
  
  /**  
   * This constructor used for LIMIT, STOP order types.  
   */  
  public StockOrder(String symbol, int quantity, int duration, int type, double 
price);  
   
  /**  
   * This constructor used for MARKET, ALLORNONE, FILLORKILL orders.  
   */  
  public StockOrder(String symbol, int quantity, int duration, int type);  
  
  // "Getters"…   
  public String getSecurity();  
  public int getNumberUnits();  
  public int getOrderDuration();  
  public int getOrderType();  
  public double getPrice();  
  public double getStopPrice();  
  public double getLimitPrice();  
}

This class is used for both “buy” and “sell” orders. Essentially, you create and initialize a StockOrder object by calling one of its three constructors. The constructor used determines if the order is a market order, a stop order, a limit order, or a stop-limit order (see the embedded comments for descriptions of these terms in the public interface above).

For this example, assume that you want to execute a “buy” order, but do not want to pay more than a certain amount per share. In this case you would need to create the StockOrder object using the second constructor:

public StockOrder(String symbol, int quantity, int duration, int type, double 
price);

You can see how to construct the StockOrder, but where do the actual values come from? You could simply generate them within the BuildLimitOrder service itself, but this is not very flexible. A better approach is to define an input signature for the service that allows the caller to provide the values that will be passed to the StockOrder constructor.

In addition to the InVals structured document declared as the input signature of BuildLimitOrder, a single Object definition, order, is declared in the output signature. The content of InVals consists of four String types: symbol, quality, duration, and price. These types correspond to four of the five arguments required by the StockOrder constructor. (The missing constructor argument, type, will be provided within the implementation of BuildLimitOrder itself.) Note, however, that the respective types of these parameters do not correspond in all cases (for example, the ‘price' constructor type is a primitive double). The easiest approach is to treat all the inputs as strings and simply perform the necessary type transformations within the service's implementation.

The following code is a complete implementation of the BuildLimitOrder service:

 public static final void BuildLimitOrder (IData pipeline) throws 
ServiceException {  
      String _symbol = null;  
      int    _quantity = 0;  
      int    _duration = 99;  
      double _price = -99.99D;  
    
      // Get access to nested 'InVals' pipeline...  
      IDataCursor idc0 = pipeline.getCursor();  
      idc0.first();  
      IData inVals = (IData)idc0.getValue();  
     
      // Enumerate the contents of 'InVals'...  
      IDataCursor idc1 = inVals.getCursor();  
      while (idc1.next())  
      {  
          // Process the 'symbol' input value...  
          String key = idc1.getKey();  
          if (key.equals("symbol"))  
             _symbol = (String)idc1.getValue();  
       
          // Process the 'quantity' input value...  
          else if (key.equals("quantity"))  
          {  
             String tmp = (String)idc1.getValue();  
             try  
             {  
                 // Need to convert to int  
                 _quantity = Integer.parseInt(tmp);  
              }  
              catch (NumberFormatException e) {}  
           }  
       
           // Process the 'duration' input value...  
           else if (key.equals("duration"))  
           {  
               // Convert string value to its enumerated counterpart  
               String tmp = (String)idc1.getValue();  
               if (tmp.equalsIgnoreCase("DAY"))  
                   _duration = StockOrder.DAY;  
 else if (tmp.equalsIgnoreCase("GTC"))  
     _duration = StockOrder.GTC;  
               else  
                  _duration = 999;  
            }  
             
            // Process the 'price' input value...  
            else if (key.equals("price"))  
            {  
                String tmp = (String)idc1.getValue();  
                try  
                {  
                    // Need to convert to double  
                    _price = Double.parseDouble(tmp);  
                }  
                catch (NumberFormatException e){}  
            }  
    }  
    idc1.destroy();  
    
    // Create a new LIMIT StockOrder...  
    StockOrder so = new StockOrder(_symbol, _quantity, _duration, 
StockOrder.LIMIT, _price);  
  
    // Insert it in the outbound pipeline...  
    idc0.insertAfter("order", so);  
    idc0.destroy();  
}

In the code that deals with the pipeline, note how when accessing elements in the input or output signature, it refers explicitly to the configured names of these elements (for example, “quantity”, “price”, “order”). This is one approach that can be taken. It is also possible to access items in the pipeline without referring to them by name. For a complete explanation of how to use the com.wm.data.IData and com.wm.data.IDataCursor classes, see the Javadocs provided in Designer and the IBM webMethods Service Development Help for your release.

You can see the final, completed BuildLimitOrder service in the figures below.

When you save the BuildLimitOrder service, Designer will compile it and report any errors it finds. When you have addressed all errors you can run the service standalone and debug it in Designer. After the BuildLimitOrder service is in place and tested you can add it to a flow service.

The next figures show a simple flow service named LimitedBuy that combines the new coded service, BuildLimitOrder, with the previously configured CreateEJB 2.1 or FetchEJB 3.0 adapter service (Trader) and a corresponding InvokeEJB 2.1 or InvokeEJB 3.0 adapter service (BuyComplexStock) instances.

Note that the input signature for the LimitedBuy flow mimics that of the BuildLimitOrder coded service. This allows you to map the values provided to the flow service at run time to the input signature of BuildLimitOrder.

Because the GetComplexTrader adapter service has no input signature, its inbound pipeline is simple. The signature of the BuyComplexStock adapter service is more complex. To fulfill its input signature, the output of BuildLimitOrder (the order Object) is mapped to the OrderDetail input Object. The mapping of the first element of Results to EJB passes the actual EJB into the BuyComplexStock adapter service as discussed earlier. These mappings are shown in the figure below.

Example 2

This example builds on Example 1 and shows how to convert the output of the BuyComplexStock adapter service into a simple document. The signature of the remote EJB method that is executed by BuyComplexStock looks like this:

public  TradeResult buy (StockOrder order) throws RemoteException;

Note that this method returns an object of type TradeResult. Examine the public interface of this class:

 public final class TradeResult implements Serializable  
  {  
    public TradeResult(String symbol, int quantity, double price);  
   
    public String getStockSymbol();  
    public int getSharesTraded();  
    public double getExecutePrice();  
  }

Notice that the output signature of the BuyComplexStock adapter service does not mention any TradeResult objects. Rather, it contains a document that in turn contains a String status field and another instance of an Object list named Results. This is not the same Results Object list as was returned by the GetComplexTrader adapter service. It just has the same name.

In this example, you will take the contents of Results (which is a list of opaque Objects to Designer) and turn it into a document that contains the actual values in the underlying TradeResult object. As with all CreateEJB 2.1 or FetchEJB 3.0 adapter services, all corresponding InvokeEJB 2.1 or InvokeEJB 3.0 adapter services will fill their outbound Results array starting at the first element (that is, the “zero-th” element). For this example, assume that the BuyComplexStock adapter service always returns a single TradeResult object in its Results array.

To accomplish this task, you need to write another coded Java service. Its implementation could be something like the following:

 public static final void ResultsToDoc (IData pipeline) throws ServiceException {  
// Get the expected TradeResult object from the inbound pipeline...  
IDataCursor idc0 = pipeline.getCursor();  
if (idc0.next("ExecutionDetail"))  
{  
  Object obj = idc0.getValue();  
  try  
  {  
    // Cast it to an actual TradeResult object...  
    TradeResult tr = (TradeResult)obj;  
  
    // Extract the attributes of the TradeResult...  
    String symbol = tr.getStockSymbol();  
    String quantity = String.valueOf(tr.getSharesTraded());  
    String price = String.valueOf(tr.getExecutePrice());  
  
    // Create the outbound document object...  
    IData out = IDataFactory.create();  
    IDataCursor idc1 = out.getCursor();  
    idc1.first();  
  
    // Insert the extracted values into the out doc  
    idc1.insertAfter("StockSymbol", symbol);  
    idc1.insertAfter("QuantityTraded", quantity);  
    idc1.insertAfter("ExecutePrice", price);  
    
    // Relinquish the out doc cursor...  
    idc1.destroy();  
  
    // Insert the out doc into the pipeline...  
    idc0.insertAfter("TradeResultDoc", out);  
  }  
  catch (ClassCastException e) {}  
}  
  
// Relinquish the pipeline cursor...  
idc0.destroy();  
}

This coded service, ResultsToDoc, manipulates the content of the pipeline, performing the following tasks:

  • Retrieves an object labeled ExecutionDetail from the pipeline
  • Casts the ExecutionDetail object to the user-defined TradeResult type
  • Extracts the values of the TradeResult object into three Strings: StockSymbol, QuantityTraded, and ExecutePrice
  • Creates a new IData document called TradeResultDoc
  • Inserts the three String values (StockSymbol, QuantityTraded, and ExecutePrice) into the TradeResultDoc document
  • Adds the TradeResultDoc to the pipeline

In order for this service to work properly, you must declare its input (ExecutionDetail) and output (TradeResultDoc) signatures as shown below.

With the ResultsToDoc coded service complete, you can now add it to the LimitedBuy flow service, as shown in the figure below:

The LimitedBuy flow service is now complete. When you execute it with valid input values, you should see those values echoed in the TradeResultDoc document created by the final flow step.