Chapter 8. ATM Example

In this tutorial, we will develop a process that manages the interaction between an automated teller machine and the information system of a bank. The process drives ATMs in performing the operations listed below.

  1. Connect to the server

  2. Log a customer on

  3. Query the state of the session

  4. Obtain the account balance

  5. Withdraw and deposit funds

  6. Log the customer off

  7. Disconnect from the server

Not all operations are available at the same time. Most require another operation to complete for becoming available.

Four different modules participate in this orchestration. The picture below shows the relationships between modules plus the deployment configuration.

Participants of the ATM process

Figure 8.1. Participants of the ATM process

Initially, the teller machine connects to the front end service. Inside the bank, the front end contacts the ticket issuer module to generate a number that uniquely identifies the teller. Subsequent message exchanges with the bank indicate the ticket number.

When an account holder comes and authenticates him or herself, the teller asks the front end to initiate a customer session. The front end resorts to the account system for checking access rights.

Once access is granted, the account holder looks at the account balance, deposits/withdraws funds or terminates the session. Because a given customer is not supposed to use multiple ATM at the same time, these exchanges carry the customer credentials instead of the ticket.

The front end contacts the account system as required to ensure the balance is accurate. Even tough the account system allows negative balances for the sake of other credit operations, ATMs do not dispense cash on credit. The front end must ensure enough funds exist and reject withdrawals that would result in a negative balance.

8.1. Define the BPEL process

8.1.1. Create the BPEL document

First of all, an explanation of the top level elements. The partner link atm represents the relationship between a teller machine and the process. The process plays the FrontEnd role, as the attribute myRole indicates. Similarly, ticket links the process to the ticket issuer service, which assumes the TicketIssuer role. Account operations are available through the account partner link. Neither ticket nor account place any responsibility on the process, hence they specify partnerRole but not myRole.

The variables connected and logged are status flags. The atm correlation set distinguishes ATMs from each other based on the ticket number property.

<process name="AtmFrontEnd" targetNamespace="http://jbpm.org/examples/atm"
  xmlns:tns="http://jbpm.org/examples/atm" xmlns:atm="http://jbpm.org/examples/atm"
  xmlns:tic="http://jbpm.org/examples/ticket" xmlns:acc="http://jbpm.org/examples/account"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://schemas.xmlsoap.org/ws/2003/03/business-process/">

  <partnerLinks>
    <!-- relationship with the ATM -->
    <partnerLink name="atm" partnerLinkType="tns:Atm-Front" myRole="FrontEnd" />
    <!-- relationship with the ticket issuer -->
    <partnerLink name="ticket" partnerLinkType="tns:Front-Ticket" 
      partnerRole="TicketIssuer" />
    <!-- relationship with the account system -->
    <partnerLink name="account" partnerLinkType="tns:Front-Account" 
      partnerRole="AccountSystem" />
  </partnerLinks>

  <variables>
    <!-- ticket number wrapper -->
    <variable name="ticketMsg" messageType="tic:ticketMessage" />
    <!-- ATM connection flag -->
    <variable name="connected" type="xsd:boolean" />
    <!-- customer access flag -->
    <variable name="logged" type="xsd:boolean" />
  </variables>

  <correlationSets>
    <!-- conversation with a connected ATM -->
    <correlationSet name="atmInteraction" properties="tns:ticketId" />
  </correlationSets>

  ...

</process>

Let's move on to the control flow. The next figure is the outlook of the ATM front end process.

ATM main sequence

Figure 8.2. ATM main sequence

We define a main sequence for handling the life cycle of an ATM connection. It consists of these activities: create a ticket, initialize the status flags and handle the new connection.

<sequence name="MainSeq">

  <scope name="TicketCreationUnit">
    ...
  </scope>

  <!-- initialize the status flags -->
  <assign name="InitializeStatus">
    <copy>
      <from expression="true()" />
      <to variable="connected" />
    </copy>
    <copy>
      <from expression="false()" />
      <to variable="logged" />
    </copy>
  </assign>

  <!-- handle the ATM connection -->
  <scope name="ConnectionUnit">
    ...
  </scope>

</sequence>

Each scope delimits a nested unit of work, with its own variables, correlation sets and fault/event handlers. They help break a long and complex process into manageable pieces. Let us take a closer look at the TicketCreationUnit

.
Ticket creation unit

Figure 8.3. Ticket creation unit

The start point is to accept a connection from some ATM, which results in the creation of a new process instance. Next, the process contacts a partner service to create a new ticket, and then returns the ticket number to the ATM. Observe that the activity CreateTicket initiates the correlation set atmInteraction. Future incoming messages containing this ticket number will be delivered to the newly created process instance.

<scope name="TicketCreationUnit">

  <variables>
    <!-- ATM connection request -->
    <variable name="connectReq" messageType="atm:connectRequest" />
    <!-- ticket creation request -->
    <variable name="ticketReq" messageType="tic:ticketRequest" />
  </variables>

  <sequence name="TicketCreationSeq">

    <!-- receive a connection request -->
    <receive name="AcceptConnection" partnerLink="atm" portType="atm:FrontEnd"
      operation="connect" variable="connectReq" createInstance="yes" />

    <!-- generate a ticket number -->
    <invoke name="CreateTicket" partnerLink="ticket" portType="tic:TicketIssuer"
      operation="createTicket" inputVariable="ticketReq" outputVariable="ticketMsg">
      <correlations>
        <correlation set="atmInteraction" pattern="in" initiate="yes" />
      </correlations>
    </invoke>

    <!-- send the ticket number back to the ATM -->
    <reply name="SendTicketNumber" operation="connect" partnerLink="atm" 
      portType="atm:FrontEnd" variable="ticketMsg">
      <correlations>
        <correlation set="atmInteraction" />
      </correlations>
    </reply>

  </sequence>

</scope>

The diagram that follows is a close look at the control flow of the connectionUnit:

Connection unit

Figure 8.4. Connection unit

The local variables logOnReq and statusRsp are placeholders for message exchanges.

Connection handling consists of listening for ATM requests and processing them one at a time. This is an iterative behavior. The connectionLoop activity causes the front end to keep taking requests as long as the connected flag stays turned on.

At this point, the process accepts any of the following two requests: initiate a customer session or terminate the connection. The connectionMenu structure performs the activity associated with the first request to arrive.

<scope name="ConnectionUnit">

  <variables>
    <!-- customer log on request -->
    <variable name="logOnReq" messageType="atm:logOnRequest" />
    <!-- connection status response -->
    <variable name="statusRsp" messageType="atm:statusResponse" />
  </variables>

  <!-- accept ATM requests, one at a time -->
  <while name="ConnectionLoop" condition="bpel:getVariableData('connected')">

    <!-- listen for either disconnect or log on request -->
    <pick name="ConnectionMenu">

      <onMessage operation="disconnect" partnerLink="atm" portType="atm:FrontEnd"
        variable="ticketMsg">
        ...
      </onMessage>

      <onMessage operation="logOn" partnerLink="atm" portType="atm:FrontEnd"
        variable="logOnReq">
        ...
      </onMessage>
    
    </pick>

  </while>

</scope>
  • logOn: the AccountUnit scope encapsulates the access to the account belonging to a registered customer.

    <onMessage operation="logOn" partnerLink="atm" 
      portType="atm:FrontEnd" variable="logOnReq">
    
      <correlations>
        <correlation set="atmInteraction" />
      </correlations>
    
      <!-- handle account access -->
      <scope name="AccountUnit">          
        ...
      </scope>
    
    </onMessage>
  • disconnect: setDisconnected turns off the connected flag, causing the connectionLoop to break shortly after.

    <onMessage operation="disconnect" partnerLink="atm" 
      portType="atm:FrontEnd" variable="ticketMsg">
    
      <correlations>
        <correlation set="atmInteraction" />
      </correlations>
    
      <!-- turn off connected flag -->
      <assign name="SetDisconnected">
        <copy>
          <from expression="false()" />
          <to variable="connected" />
        </copy>
      </assign>
    
    </onMessage>

To spice up the process, ConnectionUnit defines an event for handling status requests on par with the primary activity. The status event lets the ATM query the connection status as long as the scope is active.

Status event

Figure 8.5. Status event

The following snippet shows the event handling code. The status flags are queried to determine the status of the connection.

<eventHandlers>

  <!-- listen for connection status requests -->
  <onMessage operation="status" partnerLink="atm" portType="atm:FrontEnd"
    variable="ticketMsg">

    <correlations>
      <correlation set="atmInteraction" />
    </correlations>

    <!-- report the connection status -->
    <sequence name="StatusSeq">

      <!-- set a status string depending on the flag values -->
      <switch name="StatusDecision">

        <case condition="bpel:getVariableData('logged')">

          <assign name="setStatusLogged">
            <copy>
              <from expression="'logged'" />
              <to variable="statusRsp" part="status" />
            </copy>
          </assign>

        </case>

        <case condition="bpel:getVariableData('connected')">

          <assign name="setStatusConnected">
            <copy>
              <from expression="'connected'" />
              <to variable="statusRsp" part="status" />
            </copy>
          </assign>

        </case>

        <otherwise>

          <assign name="setStatusDisconnected">
            <copy>
              <from expression="'disconnected'" />
              <to variable="statusRsp" part="status" />
            </copy>
          </assign>

        </otherwise>

      </switch>

      <!-- send the status back to the ATM -->
      <reply name="SendStatus" operation="status" partnerLink="atm" 
        portType="atm:FrontEnd" variable="statusRsp" />

    </sequence>

  </onMessage>

</eventHandlers>

The AccountUnit scope lies at the core of the ATM front end process. It encapsulates the logic to serve account holder requests. The next picture summarizes its control flow.

Account unit

Figure 8.6. Account unit

The scope declares a number of local variables for incoming and outgoing messages. Apart from them, one variable, newBalance, stores the result of evaluating the remaining amount after a withdrawal.

One correlation set, customerInteraction, distinguishes logged account holders from each other through the customer name property. One feature of correlation sets opens a potential pitfall. In order to ensure consistency constraints, correlation sets are immutable. However, the ATM most likely will serve a different customer at each iteration. For this reason, the customerInteraction declaration appears inside the loop rather than outside. In this way, the set can assume different values in every new session.

Account handling works as follows. The front end must verify the customer actually holds an account. Verification is outside the responsibilities of the process; it is a function of the bank account system. Therefore, the front end invokes the account system to check the customer access privilege. If the system grants access, the front end turns on the logged flag and acknowledges the log on request. Conversely, when the system denies access, the front end sends a unauthorizedAccess back to the ATM. It leaves the logged flag off so that the account access ends immediately.

Note that the aforementioned fault appears in the WSDL definition of the operation. If it did not appear, jBPM BPEL would report an error at deployment time.

<portType name="FrontEnd">
  ...
  <operation name="logOn">
    <input message="tns:logOnRequest" />
    <output message="tns:logOnResponse" />
    <fault name="unauthorizedAccess" message="tns:unauthorizedAccess" />
  </operation>
  ...
</portType>

After completing the logOn operation either way, the process enters a loop that accepts account requests one at a time. The next section will describe the logic inside accountLoop.

<scope name="AccountUnit">

  <variables>
    <!-- customer name wrapper -->
    <variable name="customerMsg" messageType="acc:customerMessage" />
    <!-- access check response -->
    <variable name="accessMsg" messageType="acc:accessMessage" />
    <!-- customer access acknowledgement -->
    <variable name="logOnRsp" messageType="atm:logOnResponse" />
    <!-- account balance wrapper -->
    <variable name="balanceMsg" messageType="acc:balanceMessage" />
    <!-- balance change request -->
    <variable name="balanceChange" messageType="atm:balanceChange" />
    <!-- account system operation request -->
    <variable name="accountOperation" messageType="acc:accountOperation" />
    <!-- customer access fault -->
    <variable name="unauthorizedAccess" messageType="atm:unauthorizedAccess" />
    <!-- withdraw fault -->
    <variable name="insufficientFunds" messageType="atm:insufficientFunds" />
    <!-- resulting balance after withdrawal -->
    <variable name="newBalance" type="xsd:double" />
  </variables>

  <correlationSets>
    <!-- conversation with a logged customer -->
    <correlationSet name="customerInteraction" properties="tns:customerId" />
  </correlationSets>

  <sequence name="AccountSeq">

    <!-- populate access check request -->
    <assign name="PrepareAccessCheck">
      <copy>
        <from variable="logOnReq" part="customerName" />
        <to variable="customerMsg" part="customerName" />
      </copy>
    </assign>

    <!-- check account access privilege -->
    <invoke name="CheckAccess" operation="checkAccess" partnerLink="account"
      portType="acc:AccountSystem" inputVariable="customerMsg"
      outputVariable="accessMsg">
      <correlations>
        <correlation set="customerInteraction" pattern="out" initiate="yes" />
      </correlations>
    </invoke>

    <!-- decide outcome of customer access request -->
    <switch name="AccessDecision">

      <case condition="bpel:getVariableData('accessMsg', 'granted')">

        <!-- grant customer access -->
        <sequence name="AccessGrantedSeq">

          <!-- turn on logged flag -->
          <assign name="SetLoggedOn">
            <copy>
              <from expression="true()" />
              <to variable="logged" />
            </copy>
          </assign>

          <!-- send acknowledgement back to ATM -->
          <reply name="GrantAccess" operation="logOn" partnerLink="atm"
            portType="atm:FrontEnd" variable="logOnRsp" />

        </sequence>

      </case>

      <otherwise>

        <!-- deny customer access -->
        <sequence name="AccessDeniedSeq">

          <!-- populate access fault -->
          <assign name="PrepareAccessDenial">
            <copy>
              <from variable="logOnReq" part="customerName" />
              <to variable="unauthorizedAccess" part="detail"
                query="/atm:unauthorizedAccess/customerName" />
            </copy>
          </assign>

          <!-- send fault back to ATM -->
          <reply name="DenyAccess" operation="logOn" partnerLink="atm"
            portType="atm:FrontEnd" variable="unauthorizedAccess"
            faultName="atm:unauthorizedAccess" />

        </sequence>

      </otherwise>

    </switch>

    <!-- accept account requests, one at a time -->
    <while name="AccountLoop" condition="bpel:getVariableData('logged')">
      ...
    </while>

  </sequence>

</scope>

Inside AccountLoop, the process waits for one of four possible requests. These requests appear as onMessage branches of AccountMenu.

Account menu

Figure 8.7. Account menu

The above diagram represents the following structure.

<while name="AccountLoop" condition="bpel:getVariableData('logged')">

  <pick name="AccountMenu">
    
    <onMessage operation="logOff">
      ...
    </onMessage>
    
    <onMessage operation="getBalance">
      ...
    </onMessage>
    
    <onMessage operation="deposit">
      ...
    </onMessage>
    
    <onMessage operation="withdraw">
      ...
    </onMessage>
    
    <onAlarm for="'PT2M'">
      ...
    </onAlarm>
    
  </pick>

</while>
  • logOff: setLoggedOff turns off the logged flag to break the customerLoop and terminate the customer access.

    <onMessage operation="logOff" partnerLink="atm" 
      portType="atm:FrontEnd" variable="customerMsg">
    
      <correlations>
        <correlation set="customerInteraction" />
      </correlations>
    
      <!-- turn off logged flag -->
      <assign name="SetLoggedOff">
        <copy>
          <from expression="false()" />
          <to variable="logged" />
        </copy>
      </assign>
    
    </onMessage>
  • getBalance: the BalanceSeq queries the account system for the current balance and hands that back to the ATM.

    <onMessage operation="getBalance" partnerLink="atm" 
      portType="atm:FrontEnd" variable="customerMsg">
    
      <correlations>
        <correlation set="customerInteraction" />
      </correlations>
    
      <sequence name="BalanceSeq">
    
        <!-- get current account balance -->
        <invoke name="QueryBalance" operation="queryBalance" partnerLink="account"
          portType="acc:AccountSystem" inputVariable="customerMsg"
          outputVariable="balanceMsg">
          <correlations>
            <correlation set="customerInteraction" pattern="out" />
          </correlations>
        </invoke>
    
        <!-- return balance to ATM -->
        <reply name="TellBalance" operation="getBalance" partnerLink="atm"
          portType="atm:FrontEnd" variable="balanceMsg" />
    
      </sequence>
    
    </onMessage>
  • deposit: the DepositSeq posts the positive update to the account system. The front end process gets the new balance in return and makes it available to the ATM.

    <onMessage operation="deposit" partnerLink="atm"
      portType="atm:FrontEnd" variable="balanceChange">
    
      <correlations>
        <correlation set="customerInteraction" />
      </correlations>
    
      <sequence name="DepositSeq">
    
        <!-- populate balance update request -->
        <assign name="PrepareDeposit">
          <copy>
            <from variable="balanceChange" part="customerName" />
            <to variable="accountOperation" part="body" query="/body/customerName" />
          </copy>
          <copy>
            <from variable="balanceChange" part="amount" />
            <to variable="accountOperation" part="body" query="/body/amount" />
          </copy>
        </assign>
    
        <!-- post positive balance update -->
        <invoke name="UpdateBalance" operation="updateBalance" partnerLink="account"
          portType="acc:AccountSystem" inputVariable="accountOperation"
          outputVariable="balanceMsg">
          <correlations>
            <correlation set="customerInteraction" pattern="out" />
          </correlations>
        </invoke>
    
        <!-- make new balance available to ATM -->
        <reply name="TellNewBalance" operation="deposit" partnerLink="atm"
          portType="atm:FrontEnd" variable="balanceMsg" />
    
      </sequence>
    
    </onMessage>
  • withdraw: because withdraw is most involved account operation, it appears in a separate diagram.

    Withdraw sequence

    Figure 8.8. Withdraw sequence

    WithdrawSeq first queries the account system for the current balance. Later, it evaluates the amount that would remain in the account after the negative update.

    <onMessage operation="withdraw" partnerLink="atm"
      portType="atm:FrontEnd" variable="balanceChange">
    
      <correlations>
        <correlation set="customerInteraction" />
      </correlations>
    
      <sequence name="WithdrawSeq">
    
        <!-- populate balance query request -->
        <assign name="PrepareBalanceQuery">
          <copy>
            <from variable="balanceChange" part="customerName" />
            <to variable="customerMsg" part="customerName" />
          </copy>
        </assign>
    
        <!-- get current account balance -->
        <invoke name="QueryBalance" operation="queryBalance" partnerLink="account"
          portType="acc:AccountSystem" inputVariable="customerMsg"
          outputVariable="balanceMsg">
          <correlations>
            <correlation set="customerInteraction" pattern="out" />
          </correlations>
        </invoke>
    
        <!-- evaluate amount that would remain in account -->
        <assign name="EvaluateNewBalance">
          <copy>
            <from
              expression="bpel:getVariableData('balanceMsg', 'balance') -
                bpel:getVariableData('balanceChange', 'amount')" />
            <to variable="newBalance" />
          </copy>
        </assign>
    
        <!-- decide outcome of withdraw request -->
        <switch name="BalanceDecision">
        
          <case condition="bpel:getVariableData('newBalance') &gt;= 0.0">
            ...
          </case>
          
          <otherwise>
            ...
          </otherwise>
        
        </switch>
    
      </sequence>
    
    </onMessage>

    If there are enough funds, the PositiveBalanceSeq posts the negative update to the account system, gets the new balance and returns that to the ATM.

    <case condition="bpel:getVariableData('newBalance') &gt;= 0.0">
    
      <!-- accept withdrawal -->
      <sequence name="PositiveBalanceSeq">
    
        <!-- populate balance update request -->
        <assign name="PrepareWithdraw">
          <copy>
            <from variable="balanceChange" part="customerName" />
            <to variable="accountOperation" part="body"
              query="/body/customerName" />
          </copy>
          <copy>
            <from
              expression="-bpel:getVariableData('balanceChange', 'amount')" />
            <to variable="accountOperation" part="body" query="/body/amount" />
          </copy>
        </assign>
    
        <!-- post negative balance update -->
        <invoke name="UpdateBalance" operation="updateBalance"
          partnerLink="account" portType="acc:AccountSystem"
          inputVariable="accountOperation" outputVariable="balanceMsg">
          <correlations>
            <correlation set="customerInteraction" pattern="out" />
          </correlations>
        </invoke>
    
        <!-- return new balance to ATM -->
        <reply name="TellNewBalance" operation="withdraw" partnerLink="atm"
          portType="atm:FrontEnd" variable="balanceMsg" />
    
      </sequence>
    
    </case>

    Otherwise, the negativeBalanceSequence rejects the withdraw by returning a fault to the ATM. The update is not posted.

    <otherwise>
    
      <!-- reject withdrawal -->
      <sequence name="NegativeBalanceSeq">
    
        <!-- populate withdraw fault -->
        <assign name="PrepareRejection">
          <copy>
            <from variable="balanceChange" part="customerName" />
            <to variable="insufficientFunds" part="detail"
              query="/atm:insufficientFunds/customerName" />
          </copy>
          <copy>
            <from variable="balanceMsg" part="balance" />
            <to variable="insufficientFunds" part="detail"
              query="/atm:insufficientFunds/amount" />
          </copy>
        </assign>
    
        <!-- return fault to ATM -->
        <reply name="RejectWithdraw" operation="withdraw" partnerLink="atm"
          portType="atm:FrontEnd" variable="insufficientFunds"
          faultName="atm:insufficientFunds" />
    
      </sequence>
    
    </otherwise>
  • The final onAlarm branch terminates the customer session after two minutes, if no account request arrives earlier.

    <onAlarm for="'PT2M'">
    
      <!-- turn off logged flag after a period of inactivity -->
      <assign name="setLoggedOff">
        <copy>
          <from expression="false()" />
          <to variable="logged" />
        </copy>
      </assign>
    
    </onAlarm>

8.1.2. Create/obtain the WSDL interface documents

To better organize WSDL definitions, the process uses four interface documents for the ATM service.

The first document, ticket.wsdl contains the interface of the ticket issuer service. Here we assume this service is already deployed somewhere and the WSDL definitions came from there.

<definitions targetNamespace="http://jbpm.org/examples/ticket"
  xmlns:tns="http://jbpm.org/examples/ticket" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://schemas.xmlsoap.org/wsdl/">

  <message name="ticketRequest">
    <documentation>ticket creation request</documentation>
  </message>

  <message name="ticketMessage">
    <documentation>ticket number wrapper</documentation>
    <part name="ticketNo" type="xsd:int" />
  </message>

  <portType name="TicketIssuer">
    <documentation>interface to ticket issuer service</documentation>

    <operation name="createTicket">
      <documentation>generate a ticket number, distinct from previous calls</documentation>
      <input message="tns:ticketRequest" />
      <output message="tns:ticketMessage" />
    </operation>

  </portType>

</definitions>

Another document, account.wsdl describes the published functions of the account system. One custom XML Schema definition, AccountOperation, introduces a data transfer type for account operations.

<definitions targetNamespace="http://jbpm.org/examples/account"
  xmlns:tns="http://jbpm.org/examples/account" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns="http://schemas.xmlsoap.org/wsdl/">

  <types>

    <schema targetNamespace="http://jbpm.org/examples/account"
      xmlns="http://www.w3.org/2001/XMLSchema">

      <complexType name="AccountOperation">
        <annotation>
          <documentation>account data transfer type</documentation>
        </annotation>
        <sequence>
          <element name="customerName" type="xsd:string" />
          <element name="amount" type="xsd:double" />
        </sequence>
      </complexType>

    </schema>

  </types>

  <message name="customerMessage">
    <documentation>customer name wrapper</documentation>
    <part name="customerName" type="xsd:string" />
  </message>

  <message name="accessMessage">
    <documentation>access check response</documentation>
    <part name="granted" type="xsd:boolean" />
  </message>

  <message name="balanceMessage">
    <documentation>account balance wrapper</documentation>
    <part name="balance" type="xsd:double" />
  </message>

  <message name="accountOperation">
    <documentation>account operation request</documentation>
    <part name="body" type="tns:AccountOperation" />
  </message>

  <portType name="AccountSystem">
    <documentation>published account functions</documentation>

    <operation name="checkAccess">
      <documentation>tell whether a customer has an active account</documentation>
      <input message="tns:customerMessage" />
      <output message="tns:accessMessage" />
    </operation>

    <operation name="queryBalance">
      <documentation>retrieve the balance of an account</documentation>
      <input message="tns:customerMessage" />
      <output message="tns:balanceMessage" />
    </operation>

    <operation name="updateBalance">
      <documentation>increase/decrease the balance of an account</documentation>
      <input message="tns:accountOperation" />
      <output message="tns:balanceMessage" />
    </operation>

  </portType>

</definitions>

The third document, frontend.wsdl, contains the interface the process presents to ATMs. Because it reuses a number of messages from the ticket issuer and the account system, it imports the WSDL documents that describe these services.

Some custom XML schema definitions appear in the types section. They define the elements that the front end interface uses to inform ATMs of business logic errors and the types that characterize those elements.

WSDL messages, in terms of the foregoing definitions and predefined schema types, define the exchange format between the ATM and the bank front end. Finally, the FrontEnd port type lists the bank functions available to ATMs.

<definitions targetNamespace="http://jbpm.org/examples/atm" 
  xmlns:tns="http://jbpm.org/examples/atm"
  xmlns:tic="http://jbpm.org/examples/ticket" xmlns:acc="http://jbpm.org/examples/account"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/">

  <import namespace="http://jbpm.org/examples/ticket" location="ticket.wsdl" />
  <import namespace="http://jbpm.org/examples/account" location="account.wsdl" />

  <types>

    <schema targetNamespace="http://jbpm.org/examples/atm"
      xmlns="http://www.w3.org/2001/XMLSchema">

      <complexType name="UnauthorizedAccess">
        <sequence>
          <element name="customerName" type="xsd:string" />
        </sequence>
      </complexType>

      <element name="unauthorizedAccess" type="tns:UnauthorizedAccess" />

      <complexType name="InsufficientFunds">
        <sequence>
          <element name="customerName" type="xsd:string" />
          <element name="amount" type="xsd:double" />
        </sequence>
      </complexType>

      <element name="insufficientFunds" type="tns:InsufficientFunds" />

    </schema>

  </types>

  <message name="connectRequest" />

  <message name="logOnRequest">
    <part name="ticketNo" type="xsd:int" />
    <part name="customerName" type="xsd:string" />
  </message>

  <message name="logOnResponse" />

  <message name="statusResponse">
    <part name="status" type="xsd:string" />
  </message>

  <message name="balanceChange">
    <part name="customerName" type="xsd:string" />
    <part name="amount" type="xsd:double" />
  </message>

  <message name="unauthorizedAccess">
    <part name="detail" element="tns:unauthorizedAccess" />
  </message>

  <message name="insufficientFunds">
    <part name="detail" element="tns:insufficientFunds" />
  </message>

  <portType name="FrontEnd">
    <documentation>bank functions available to ATMs</documentation>

    <operation name="connect">
      <documentation>initiate bank connection</documentation>
      <input message="tns:connectRequest" />
      <output message="tic:ticketMessage" />
    </operation>

    <operation name="disconnect">
      <documentation>terminate bank connection</documentation>
      <input message="tic:ticketMessage" />
    </operation>

    <operation name="status">
      <documentation>retrieve bank connection status</documentation>
      <input message="tic:ticketMessage" />
      <output message="tns:statusResponse" />
    </operation>

    <operation name="logOn">
      <documentation>initiate customer access</documentation>
      <input message="tns:logOnRequest" />
      <output message="tns:logOnResponse" />
      <fault name="unauthorizedAccess" message="tns:unauthorizedAccess" />
    </operation>

    <operation name="logOff">
      <documentation>terminate customer access</documentation>
      <input message="acc:customerMessage" />
    </operation>

    <operation name="getBalance">
      <documentation>retrieve account balance</documentation>
      <input message="acc:customerMessage" />
      <output message="acc:balanceMessage" />
    </operation>

    <operation name="deposit">
      <documentation>increase account balance</documentation>
      <input message="tns:balanceChange" />
      <output message="acc:balanceMessage" />
    </operation>

    <operation name="withdraw">
      <documentation>decrease account balance</documentation>
      <input message="tns:balanceChange" />
      <output message="acc:balanceMessage" />
      <fault name="insufficientFunds" message="tns:insufficientFunds" />
    </operation>

  </portType>

</definitions>

The last document, atm.wsdl, contains extensibility elements that glue together the BPEL process and the WSDL definitions. At the beginning, the document imports the previous three documents to reference their definitions. Later, it defines some properties for correlation purposes. ticketId distinguishes ticket numbers in messages exchanged within an ATM connection, while customerId represents customer names in messages exchanged during a customer session. The property aliases adjacent to these property definitions map these properties to key information items inside messages.

Partner link types characterize the relationship between ATMs and the process (Atm-Front), the process and the ticket issuer (Front-Ticket) as well as the process and the account system (Front-Account). They define the roles these services play and specify the interface they present to each other. The coordinator does not call back the ATM. The ticket issuer or the account system do not call back the coordinator either. Therefore, all partner link types have a single role.

<definitions targetNamespace="http://jbpm.org/examples/atm" 
  xmlns:tns="http://jbpm.org/examples/atm"
  xmlns:atm="http://jbpm.org/examples/atm" 
  xmlns:tic="http://jbpm.org/examples/ticket"
  xmlns:acc="http://jbpm.org/examples/account"
  xmlns:plt="http://schemas.xmlsoap.org/ws/2003/05/partner-link/"
  xmlns:bpel="http://schemas.xmlsoap.org/ws/2003/03/business-process/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns="http://schemas.xmlsoap.org/wsdl/">

  <import namespace="http://jbpm.org/examples/atm" location="interface/frontend.wsdl" />
  <import namespace="http://jbpm.org/examples/ticket" location="interface/ticket.wsdl" />
  <import namespace="http://jbpm.org/examples/account" location="interface/account.wsdl" />

  <!-- customer name property -->
  <bpel:property name="customerId" type="xsd:string" />

  <!-- location of costumerId inside messages -->
  <bpel:propertyAlias propertyName="tns:customerId" messageType="atm:logOnRequest"
    part="customerName" />
  <bpel:propertyAlias propertyName="tns:customerId" messageType="atm:balanceChange"
    part="customerName" />
  <bpel:propertyAlias propertyName="tns:customerId" messageType="acc:customerMessage"
    part="customerName" />
  <bpel:propertyAlias propertyName="tns:customerId" messageType="acc:accountOperation"
    part="body" query="/body/customerName" />

  <!-- ticket number property -->
  <bpel:property name="ticketId" type="xsd:int" />

  <!-- location of ticketId inside messages -->
  <bpel:propertyAlias propertyName="tns:ticketId" messageType="tic:ticketMessage"
    part="ticketNo" />
  <bpel:propertyAlias propertyName="tns:ticketId" messageType="atm:logOnRequest"
    part="ticketNo" />

  <!-- relationship between the ATM and the process -->
  <plt:partnerLinkType name="Atm-Front">
    <plt:role name="FrontEnd">
      <plt:portType name="atm:FrontEnd" />
    </plt:role>
  </plt:partnerLinkType>

  <!-- relationship between the process and the ticket issuer -->
  <plt:partnerLinkType name="Front-Ticket">
    <plt:role name="TicketIssuer">
      <plt:portType name="tic:TicketIssuer" />
    </plt:role>
  </plt:partnerLinkType>

  <!-- relationship between the process and the account system -->
  <plt:partnerLinkType name="Front-Account">
    <plt:role name="AccountSystem">
      <plt:portType name="acc:AccountSystem" />
    </plt:role>
  </plt:partnerLinkType>

</definitions>

8.1.3. Deploy the process definition

The master WSDL document atm.wsdl (as explained in the process deployment section of the Hello World chapter) imports the remaining WSDL documents from the previous block of this chapter.

If a definition descriptor was provided, it would point to the atm.bpel and atm.wsdl documents in the src/main/bpel directory.

<bpelDefinition location="atm.bpel" xmlns="urn:jbpm.org:bpel-1.1:definition">

  <imports>
    <wsdl location="atm.wsdl" />
  </imports>

</bpelDefinition>

To deploy the process definition, call:

ant deploy.process

The above target creates a file named atm.zip and submits it to the deployment servlet. The server console should read:

13:25:52,000 INFO  [DeploymentServlet] deployed process definition: AtmFrontEnd
13:25:52,781 INFO  [WebModuleBuilder] packaged web module: atm.war
13:25:52,781 INFO  [DeploymentServlet] deployed web module: atm.war
13:25:55,390 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=atm, «
endpoint=FrontEndServlet
13:25:55,421 INFO  [TomcatDeployer] deploy, ctxPath=/atm, warUrl=...
13:25:55,640 INFO  [IntegrationConfigurator] message reception enabled for process: «
AtmFrontEnd
13:25:55,812 INFO  [WSDLFilePublisher] WSDL published to: .../atm-service.wsdl

The ATM front end relies on two partner services: account and ticket. Be sure to deploy them before any ATM connects. In order to deploy a partner service, change to its directory of each partner and execute the command below.

ant deploy.webservice

As a result, the server console will display these lines:

02:01:13,078 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=account, «
endpoint=accountSystemServlet
02:01:13,109 INFO  [TomcatDeployer] deploy, ctxPath=/account, warUrl=...
02:01:14,578 INFO  [WSDLFilePublisher] WSDL published to: .../account-impl.wsdl
--
02:01:30,140 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=ticket, «
endpoint=ticketIssuerServlet
02:01:30,171 INFO  [TomcatDeployer] deploy, ctxPath=/ticket, warUrl=...
02:01:30,328 INFO  [WSDLFilePublisher] WSDL published to: .../ticket-impl.wsdl

The following endpoints should be present in the JBossWS service endpoints page.

Table 8.1. Partner endpoints

Partner linkEndpoint
tickethttp://127.0.0.1:8080/ticket/ticketIssuer
accounthttp://127.0.0.1:8080/account/accountSystem

Table 8.2. My endpoints

Partner linkEndpoint
atmhttp://127.0.0.1:8080/atm/FrontEnd?wsdl

The partner services need to be registered in the catalog. The target below takes care of it.

ant register.partners

The web console lets you confirm the registration.

Table 8.3. Service catalog

Base locationServices
http://localhost:8080/ticket/ticketIssuer?wsdlTicketService
http://localhost:8080/account/accountSystem?wsdlAccountService

8.2. Build the WSEE application client

8.2.1. Application client deployment descriptor

Reference your full WSDL description and Java mapping artifacts from the application-client.xml descriptor.

<application-client version="1.4" xmlns="http://java.sun.com/xml/ns/j2ee">

  <display-name>ATM Front End Client</display-name>

  <service-ref>

    <!-- JNDI name of service interface in client environment context -->
    <service-ref-name>service/ATM</service-ref-name>
    <!-- service interface class -->
    <service-interface>org.jbpm.bpel.tutorial.atm.AtmFrontEndService</service-interface>
    <!-- published WSDL document -->
    <wsdl-file>META-INF/wsdl/atm-service.wsdl</wsdl-file>
    <!-- Java<->XML mapping file -->
    <jaxrpc-mapping-file>META-INF/atm-mapping.xml</jaxrpc-mapping-file>

  </service-ref>

</application-client>

8.2.2. Environment context

Allocate a JNDI name for the client environment context in jboss-client.xml .

<jboss-client>
  <jndi-name>jbpmbpel-client</jndi-name>
</jboss-client>

Tip

The jndi-name above is shared among all examples. You can share a single JNDI name among multiple application clients to keep them organized and reduce the number of top-level entries in the global JNDI context of the server. Just make sure you give different service-ref-names to each client in the respective application-client.xml file.

8.3. Test the process

Once our process is up and running, we need to make sure that it is working as expected. Here we create a JUnit test case called AtmFrontEndTest and exercise several scenarios.

8.3.1. Remote web service access

This is the setup code for establishing a connection with the ATM front end:

private FrontEnd frontEnd;

protected void setUp() throws Exception {
  InitialContext iniCtx = new InitialContext();
  /*
   * "service/ATM" is the JNDI name of the service interface instance
   * relative to the client environment context. This name matches the
   * <service-ref-name> in application-client.xml
   */
  AtmFrontEndService frontEndService = 
    (AtmFrontEndService) iniCtx.lookup("java:comp/env/service/ATM");
  
  // obtain dynamic proxy for web service port
  frontEnd = frontEndService.getFrontEndPort();
}

The test scenarios are described next.

  1. testConnect: establish a connection to the bank.

    public void testConnect() throws RemoteException {
      // connect to bank
      int ticketNumber = frontEnd.connect();
      assertTrue(ticketNumber > 0);
    
      // check atm is connected
      String status = frontEnd.status(ticketNumber);
      assertEquals("connected", status);
    
      // disconnect from bank
      frontEnd.disconnect(ticketNumber);
    }
  2. testLogOnAuthorized: initiate a session as an authorized customer.

    public void testLogOnAuthorized() throws RemoteException {
      // connect to bank
      int ticketNumber = frontEnd.connect();
    
      // begin customer session
      final String customerName = "admin";
      try {
        frontEnd.logOn(ticketNumber, customerName);
      }
      catch (UnauthorizedAccess e) {
        fail("log on of authorized customer should succeed");
      }
    
      // end customer session
      frontEnd.logOff(customerName);
    
      // disconnect from bank
      frontEnd.disconnect(ticketNumber);
    }
  3. testLogOnUnauthorized: initiate a session as an unauthorized customer.

    public void testLogOnUnauthorized() throws RemoteException {
      // connect to bank
      int ticketNumber = frontEnd.connect();
    
      // begin customer session
      final String customerName = "misterx";
      try {
        frontEnd.logOn(ticketNumber, customerName);
        fail("log on of unauthorized customer should fail");
      }
      catch (UnauthorizedAccess e) {
        assertEquals(customerName, e.getCustomerName());
      }
    
      // disconnect from bank
      frontEnd.disconnect(ticketNumber);
    }
  4. testDeposit: deposit funds

    public void testDeposit() throws RemoteException, UnauthorizedAccess {
      // connect to bank
      int ticketNumber = frontEnd.connect();
    
      // begin customer session
      final String customerName = "manager";
      frontEnd.logOn(ticketNumber, customerName);
    
      // get current balance
      double previousBalance = frontEnd.getBalance(customerName);
    
      // deposit some funds
      double newBalance = frontEnd.deposit(customerName, 10);
      // check the new balance is correct
      assertEquals(previousBalance + 10, newBalance, 0);
    
      // end customer session
      frontEnd.logOff(customerName);
    
      // disconnect from bank
      frontEnd.disconnect(ticketNumber);
    }
  5. testWithdrawUnderBalance: withdraw funds not exceeding account balance.

    public void testWithdrawUnderBalance() throws RemoteException,
        UnauthorizedAccess {
      // connect to bank
      int ticketNumber = frontEnd.connect();
    
      // begin customer session
      final String customerName = "manager";
      frontEnd.logOn(ticketNumber, customerName);
    
      // get current balance
      double previousBalance = frontEnd.getBalance(customerName);
    
      // withdraw some funds
      try {
        double newBalance = frontEnd.withdraw(customerName, 10);
        // check new balance is correct
        assertEquals(previousBalance - 10, newBalance, 0);
      }
      catch (InsufficientFunds e) {
        fail("withdraw under balance should succeed");
      }
    
      // end customer session
      frontEnd.logOff(customerName);
    
      // disconnect from bank
      frontEnd.disconnect(ticketNumber);
    }
  6. testWithdrawOverBalance: withdraw funds exceeding account balance.

    public void testWithdrawOverBalance() throws RemoteException,
        UnauthorizedAccess {
      // connect to bank
      int ticketNumber = frontEnd.connect();
    
      // begin customer session
      final String customerName = "shipper";
      frontEnd.logOn(ticketNumber, customerName);
    
      // get current balance
      double previousBalance = frontEnd.getBalance(customerName);
    
      // try to withdraw an amount greater than current balance
      try {
        frontEnd.withdraw(customerName, previousBalance + 1);
        fail("withdraw over balance should fail");
      }
      catch (InsufficientFunds e) {
        assertEquals(customerName, e.getCustomerName());
        // check account balance has not changed
        assertEquals(previousBalance, e.getAmount(), 0);
      }
    
      // end customer session
      frontEnd.logOff(customerName);
    
      // disconnect from bank
      frontEnd.disconnect(ticketNumber);
    }

8.3.2. Client JNDI properties

The JNDI properties are exactly the same for all examples. In particular, the j2ee.clientName property does not change because all examples share the <jndi-name> in their respective jboss-client.xml descriptors.

Refer to the Client JNDI properties in the first example for a listing of the properties.

8.3.3. Test execution

The following target executes the junit test case:

ant test

If everything goes well the target output should look like this:

test:
    [junit] Running org.jbpm.bpel.tutorial.atm.AtmFrontEndTest
    [junit] Tests run: 6, Failures: 0, Errors: 0, Time elapsed: 6.109 sec

8.3.4. Interactive execution

Last, but not least, the ATM example offers a rich client built from Swing components. This program resembles an actual teller machine. It has a double goal:

  • Let you click your way through the process instead of writing unit tests to explore new scenarios.
  • Assist you in demonstrating the BPEL technology to your customer, manager or colleague using an easy-to-follow interface.

To bring up the interactive terminal, call:

ant launch.terminal

After a brief message exchange to connect to the front end service, the frame below appears.

Welcome screen

Figure 8.9. Welcome screen

Click Log On to access an account. The terminal prompts for a customer name. Type in any name from the accounts.xml file in the account example.

Customer name prompt

Figure 8.10. Customer name prompt

The top-level frame presents the available account operations.

Account operations

Figure 8.11. Account operations

Enjoy!