ATM Example

This example is about 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 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.

BPEL process

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:acc="http://jbpm.org/examples/account" xmlns:atm="http://jbpm.org/examples/atm"
  xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
  xmlns:tic="http://jbpm.org/examples/ticket" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

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

  <partnerLinks>
    <partnerLink myRole="FrontEnd" name="atm" partnerLinkType="atm:Atm-Front">
      <documentation>relationship with the ATM</documentation>
    </partnerLink>
  </partnerLinks>

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

  <correlationSets>
    <correlationSet name="atmInteraction" properties="atm:ticketId">
      <documentation>conversation with a connected ATM</documentation>
    </correlationSet>
  </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 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>

  <assign name="InitializeStatus" validate="no">
    <documentation>initialize the status flags</documentation>
    <copy>
      <from>true()</from>
      <to variable="connected" />
    </copy>
    <copy>
      <from>false()</from>
      <to variable="logged" />
    </copy>
  </assign>

  <scope name="ConnectionUnit">
    <documentation>handle the ATM connection</documentation>
    ...
  </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 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">

  <partnerLinks>
    <partnerLink name="ticket" partnerLinkType="atm:Front-Ticket" partnerRole="TicketIssuer">
      <documentation>relationship with the ticket issuer</documentation>
    </partnerLink>
  </partnerLinks>

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

  <sequence name="TicketCreationSeq">

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

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

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

  </sequence>

</scope>

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

Connection unit

Figure 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">
  <documentation>handle the ATM connection</documentation>

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

  <while name="ConnectionLoop">
    <documentation>accept ATM requests, one at a time</documentation>
    <condition>$connected</condition>

    <pick name="ConnectionMenu">
      <documentation>listen for either disconnect or log on request</documentation>

      <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 initiate="no" set="atmInteraction" />
      </correlations>
    
      <scope name="AccountUnit">
        <documentation>handle account access</documentation>
        ...
      </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 initiate="no" set="atmInteraction" />
      </correlations>
    
      <assign name="SetDisconnected" validate="no">
        <documentation>turn off connected flag</documentation>
        <copy>
          <from>false()</from>
          <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 5. Status event

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

<eventHandlers>
  <onEvent messageType="tic:ticketMessage" operation="status" partnerLink="atm"
    portType="atm:FrontEnd" variable="ticketMsg">

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

    <scope name="StatusUnit">

      <sequence name="StatusSeq">

        <if name="StatusDecision">

          <condition>$logged</condition>
          <assign name="SetStatusLogged" validate="no">
            <copy>
              <from>'logged'</from>
              <to part="status" variable="statusRsp" />
            </copy>
          </assign>

          <elseif>
            <condition>$connected</condition>
            <assign name="SetStatusConnected" validate="no">
              <copy>
                <from>'connected'</from>
                <to part="status" variable="statusRsp" />
              </copy>
            </assign>
          </elseif>

          <else>
            <assign name="Assign" validate="no">
              <copy>
                <from>'disconnected'</from>
                <to part="status" variable="statusRsp" />
              </copy>
            </assign>
          </else>

        </if>

        <reply name="SendStatus" operation="status" partnerLink="atm" portType="atm:FrontEnd"
          variable="statusRsp" />

      </sequence>

    </scope>

  </onEvent>

</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 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">
  <documentation>handle account access</documentation>

  <partnerLinks>
    <partnerLink name="account" partnerLinkType="atm:Front-Account"
      partnerRole="AccountSystem">
      <documentation>relationship with the account system</documentation>
    </partnerLink>
  </partnerLinks>

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

  <correlationSets>
    <correlationSet name="customerInteraction" properties="atm:customerId">
      <documentation>conversation with a logged customer</documentation>
    </correlationSet>
  </correlationSets>

  <sequence name="AccountSeq">

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

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

    <if name="AccessDecision">
      <documentation>decide outcome of customer access request</documentation>
      <condition>$accessMsg.granted</condition>

      <sequence name="AccessGrantedSeq">
        <documentation>grant customer access</documentation>

        <assign name="SetLoggedOn" validate="no">
          <documentation>turn on logged flag</documentation>
          <copy>
            <from>true()</from>
            <to variable="logged" />
          </copy>
        </assign>

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

      </sequence>

      <else>

        <sequence name="AccessDeniedSeq">
          <documentation>deny customer access</documentation>

          <assign name="PrepareAccessDenial" validate="no">
            <documentation>populate access fault</documentation>
            <copy>
              <from part="customerName" variable="logOnReq" />
              <to part="detail" variable="unauthorizedAccess">
                <query>customerName</query>
              </to>
            </copy>
          </assign>

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

        </sequence>

      </else>

    </if>

    <while name="AccountLoop">
      ...
    </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 7. Account menu

The above diagram represents the following structure.

<while name="AccountLoop">
  <documentation>accept account requests, one at a time</documentation>
  <condition>$logged</condition>

  <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>
    
      <assign name="SetLoggedOff" validate="no">
        <documentation>turn off logged flag</documentation>
        <copy>
          <from>false()</from>
          <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 initiate="no" set="customerInteraction" />
      </correlations>
    
      <sequence name="BalanceSeq">
    
        <invoke inputVariable="customerMsg" name="QueryBalance"
          operation="queryBalance" outputVariable="balanceMsg" partnerLink="account"
          portType="acc:AccountSystem">
          <documentation>get current account balance</documentation>
        </invoke>
    
        <reply name="TellBalance" operation="getBalance" partnerLink="atm"
          portType="atm:FrontEnd" variable="balanceMsg">
          <documentation>return balance to ATM</documentation>
        </reply>
    
      </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 initiate="no" set="customerInteraction" />
      </correlations>
    
      <sequence name="DepositSeq">
    
        <assign name="PrepareDeposit" validate="no">
          <documentation>populate balance update request</documentation>
          <copy>
            <from part="customerName" variable="balanceChange" />
            <to part="body" variable="accountOperation">
              <query>customerName</query>
            </to>
          </copy>
          <copy>
            <from part="amount" variable="balanceChange" />
            <to part="body" variable="accountOperation">
              <query>amount</query>
            </to>
          </copy>
        </assign>
    
        <invoke inputVariable="accountOperation" name="UpdateBalance"
          operation="updateBalance" outputVariable="balanceMsg"
          partnerLink="account" portType="acc:AccountSystem">
          <documentation>post positive balance update</documentation>
          <correlations>
            <correlation initiate="no" pattern="out" set="customerInteraction" />
          </correlations>
        </invoke>
    
        <reply name="TellNewBalance" operation="deposit" partnerLink="atm"
          portType="atm:FrontEnd" variable="balanceMsg">
          <documentation>make new balance available to ATM</documentation>
        </reply>
    
      </sequence>
    
    </onMessage>
  • withdraw: because withdraw is most involved account operation, it appears in a separate diagram.

    Withdraw sequence

    Figure 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 initiate="no" set="customerInteraction" />
      </correlations>
    
      <sequence name="WithdrawSeq">
    
        <assign name="PrepareBalanceQuery" validate="no">
          <documentation>populate balance query request</documentation>
          <copy>
            <from part="customerName" variable="balanceChange" />
            <to part="customerName" variable="customerMsg" />
          </copy>
        </assign>
    
        <invoke inputVariable="customerMsg" name="QueryBalance"
          operation="queryBalance" outputVariable="balanceMsg" partnerLink="account"
          portType="acc:AccountSystem">
          <documentation>get current account balance</documentation>
          <correlations>
            <correlation initiate="no" pattern="out" set="customerInteraction" />
          </correlations>
        </invoke>
    
        <assign name="EvaluateNewBalance" validate="no">
          <documentation>
            evaluate amount that would remain in account
          </documentation>
          <copy>
            <from>$balanceMsg.balance - $balanceChange.amount</from>
            <to variable="newBalance" />
          </copy>
        </assign>
    
        <if name="BalanceDecision">
          ...
        </if>
    
      </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.

    <if name="BalanceDecision">
      <documentation>decide outcome of withdraw request</documentation>
      <condition>$newBalance >= 0.0</condition>
    
      <sequence name="PositiveBalanceSeq">
        <documentation>accept withdrawal</documentation>
    
        <assign name="PrepareWithdraw" validate="no">
          <documentation>populate balance update request</documentation>
          <copy>
            <from part="customerName" variable="balanceChange" />
            <to part="body" variable="accountOperation">
              <query>customerName</query>
            </to>
          </copy>
          <copy>
            <from>-$balanceChange.amount</from>
            <to part="body" variable="accountOperation">
              <query>amount</query>
            </to>
          </copy>
        </assign>
    
        <invoke inputVariable="accountOperation" name="UpdateBalance"
          operation="updateBalance" outputVariable="balanceMsg"
          partnerLink="account" portType="acc:AccountSystem">
          <documentation>post negative balance update</documentation>
          <correlations>
            <correlation initiate="no" pattern="out" set="customerInteraction" />
          </correlations>
        </invoke>
    
        <reply name="TellNewBalance" operation="withdraw" partnerLink="atm"
          portType="atm:FrontEnd" variable="balanceMsg">
          <documentation>return new balance to ATM</documentation>
        </reply>
    
      </sequence>
    
      <else>
        ...
      </else>
    
    </if>

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

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

    <onAlarm>
      <for>'PT2M'</for>
    
      <assign name="SetLoggedOff" validate="no">
        <documentation>
          turn off logged flag after a period of inactivity
        </documentation>
        <copy>
          <from>false()</from>
          <to variable="logged" />
        </copy>
      </assign>
    
    </onAlarm>

WSDL interfaces

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:acc="http://jbpm.org/examples/account"
  xmlns:tic="http://jbpm.org/examples/ticket"
  xmlns:plt="http://docs.oasis-open.org/wsbpel/2.0/plnktype"
  xmlns:vprop="http://docs.oasis-open.org/wsbpel/2.0/varprop"
  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/account"
    location="interface/account.wsdl" />
  <import namespace="http://jbpm.org/examples/ticket"
    location="interface/ticket.wsdl" />

  <vprop:property name="customerId" type="xsd:string">
    <vprop:documentation>customer name property</vprop:documentation>
  </vprop:property>

  <vprop:propertyAlias propertyName="tns:customerId"
    messageType="tns:logOnRequest" part="customerName">
  </vprop:propertyAlias>
  <vprop:propertyAlias propertyName="tns:customerId"
    messageType="tns:balanceChange" part="customerName" />
  <vprop:propertyAlias propertyName="tns:customerId"
    messageType="acc:customerMessage" part="customerName" />
  <vprop:propertyAlias propertyName="tns:customerId"
    messageType="acc:accountOperation" part="body">
    <vprop:query>/body/customerName</vprop:query>
  </vprop:propertyAlias>

  <vprop:property name="ticketId" type="xsd:int">
    <vprop:documentation>ticket number property</vprop:documentation>
  </vprop:property>

  <vprop:propertyAlias propertyName="tns:ticketId"
    messageType="tic:ticketMessage" part="ticketNo" />
  <vprop:propertyAlias propertyName="tns:ticketId"
    messageType="tns:logOnRequest" part="ticketNo" />

  <plt:partnerLinkType name="Atm-Front">
    <plt:documentation>
      relationship between the ATM and the process
    </plt:documentation>
    <plt:role name="FrontEnd" portType="tns:FrontEnd" />
  </plt:partnerLinkType>
  <plt:partnerLinkType name="Front-Ticket">
    <vprop:documentation>
      relationship between the process and the ticket issuer
    </vprop:documentation>
    <plt:role name="TicketIssuer" portType="tic:TicketIssuer" />
  </plt:partnerLinkType>
  <plt:partnerLinkType name="Front-Account">
    <plt:documentation>
      relationship between the process and the account system
    </plt:documentation>
    <plt:role name="AccountSystem" portType="acc:AccountSystem" />
  </plt:partnerLinkType>

</definitions>

Deployment

TODO

Integration tests

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 and exercise several scenarios.

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 {
  AtmFrontEndService frontEndService = new AtmFrontEndService();
  
  // 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);
    }

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 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 10. Customer name prompt

The top-level frame presents the available account operations.

Account operations

Figure 11. Account operations

Enjoy!