Chapter 7. Purchase Order Example

This chapter presents an example of a process for handling a purchase order.

The operation of the process is represented in the following figure.

Purchase order process

Figure 7.1. Purchase order process

Upon receiving the purchase order from a customer, the process initiates three tasks concurrently: calculate the final price for the order, select a shipper, and schedule the production and shipment for the order. While some of the processing can proceed concurrently, there are control and data dependencies between the three tasks. The shipping price is required to finalize the price calculation, and the shipping date is required for the complete fulfillment schedule. When the three tasks are completed, invoice processing can proceed and the invoice is sent to the customer.

This is the initial example from section 6.1 of the BPEL4WS specification, version 1.1. The specification document can be found at any of the following locations:

7.1. Define the BPEL process

7.1.1. Create the BPEL document

The purchase order process is, in fact, the initial example from section 6.1 of the BPEL4WS specification version 1.1. We simply copy the example from the specification text and paste it in an XML document called purchase.bpel.

<process name="PurchaseOrder" targetNamespace="http://acme.com/ws-bp/purchase"
  xmlns:lns="http://manufacturing.org/wsdl/purchase"
  xmlns="http://schemas.xmlsoap.org/ws/2003/03/business-process/">

  <partnerLinks>
    <partnerLink name="purchasing" partnerLinkType="lns:purchasingLT" 
      myRole="purchaseService" />
    <partnerLink name="invoicing" partnerLinkType="lns:invoicingLT" 
      myRole="invoiceRequester" partnerRole="invoiceService" />
    <partnerLink name="shipping" partnerLinkType="lns:shippingLT" 
      myRole="shippingRequester" partnerRole="shippingService" />
    <partnerLink name="scheduling" partnerLinkType="lns:schedulingLT"
      partnerRole="schedulingService" />
  </partnerLinks>

  <variables>
    <variable name="PO" messageType="lns:POMessage" />
    <variable name="Invoice" messageType="lns:InvMessage" />
    <variable name="POFault" messageType="lns:orderFaultType" />
    <variable name="shippingRequest" messageType="lns:shippingRequestMessage" />
    <variable name="shippingInfo" messageType="lns:shippingInfoMessage" />
    <variable name="shippingSchedule" messageType="lns:scheduleMessage" />
  </variables>

  <faultHandlers>

    <catch faultName="lns:cannotCompleteOrder" faultVariable="POFault">

      <reply partnerLink="purchasing" portType="lns:purchaseOrderPT" 
        operation="sendPurchaseOrder" variable="POFault" faultName="cannotCompleteOrder" />

    </catch>

  </faultHandlers>

  <sequence name="Main">

    <receive name="ReceivePurchaseOrder" partnerLink="purchasing"
      portType="lns:purchaseOrderPT" operation="sendPurchaseOrder"
      variable="PO" createInstance="yes" />

    <flow>

      <links>
        <link name="ship-to-invoice" />
        <link name="ship-to-scheduling" />
      </links>

      <sequence name="Shipping">

        <assign name="PrepareShipping">
          <copy>
            <from variable="PO" part="customerInfo" />
            <to variable="shippingRequest" part="customerInfo" />
          </copy>
        </assign>

        <invoke name="RequestShipping" partnerLink="shipping" portType="lns:shippingPT"
          operation="requestShipping" inputVariable="shippingRequest"
          outputVariable="shippingInfo">
          <source linkName="ship-to-invoice" />
        </invoke>

        <receive name="ReceiveSchedule" partnerLink="shipping" operation="sendSchedule"
          portType="lns:shippingCallbackPT" variable="shippingSchedule">
          <source linkName="ship-to-scheduling" />
        </receive>

      </sequence>

      <sequence name="Invoicing">

        <invoke name="InitiatePriceCalculation" partnerLink="invoicing"
          portType="lns:computePricePT" operation="initiatePriceCalculation" 
          inputVariable="PO" />

        <invoke name="SendShippingPrice" partnerLink="invoicing" 
          portType="lns:computePricePT" operation="sendShippingPrice" 
          inputVariable="shippingInfo">
          <target linkName="ship-to-invoice" />
        </invoke>

        <receive name="ReceiveInvoice" partnerLink="invoicing" 
          portType="lns:invoiceCallbackPT" operation="sendInvoice" variable="Invoice" />

      </sequence>

      <sequence name="Scheduling">

        <invoke name="RequestScheduling" partnerLink="scheduling" 
          portType="lns:schedulingPT" operation="requestProductionScheduling"
          inputVariable="PO" />

        <invoke name="SendShippingSchedule" partnerLink="scheduling"
          portType="lns:schedulingPT" operation="sendShippingSchedule"
          inputVariable="shippingSchedule">
          <target linkName="ship-to-scheduling" />
        </invoke>

      </sequence>

    </flow>

    <reply name="SendPurchaseOrder" partnerLink="purchasing" portType="lns:purchaseOrderPT"
      operation="sendPurchaseOrder" variable="Invoice" />

  </sequence>

</process>

This example has several remarkable features:

  • The presence of the flow construct to specify multiple activities to be performed concurrently. Links introduce arbitrary control dependencies.

  • The faultHandlers section lists activities to be performed in response to faults resulting from the invocation of the partner services.

  • Multiple partnerLink elements bearing the myRole attribute result in publishing just as many web service endpoints, all served by the same process.

7.1.2. Create/obtain the WSDL interface documents

BPEL4WS 1.1 section 6.1 provides the WSDL interface definitions referenced from the process presented earlier. The content of the purchase.wsdl file is a reproduction of text from the specification except for a few minor ammendments commented below.

  • The <import> statement that references the external XML Schema document purchase.xsd was replaced with a <types> section comprising an XML Schema snippet that simply imports the external schema document.

  • In the orderFaultType message, an XML Schema simple type describes the problemInfo part. The sns:problemInfo element replaced the original xsd:string type.

<definitions targetNamespace="http://manufacturing.org/wsdl/purchase"
  xmlns:sns="http://manufacturing.org/xsd/purchase"
  xmlns:pos="http://manufacturing.org/wsdl/purchase"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:plnk="http://schemas.xmlsoap.org/ws/2003/05/partner-link/"
  xmlns="http://schemas.xmlsoap.org/wsdl/">

  <plnk:partnerLinkType name="purchasingLT">
    <plnk:role name="purchaseService">
      <plnk:portType name="pos:purchaseOrderPT" />
    </plnk:role>
  </plnk:partnerLinkType>

  <plnk:partnerLinkType name="invoicingLT">
    <plnk:role name="invoiceService">
      <plnk:portType name="pos:computePricePT" />
    </plnk:role>
    <plnk:role name="invoiceRequester">
      <plnk:portType name="pos:invoiceCallbackPT" />
    </plnk:role>
  </plnk:partnerLinkType>

  <plnk:partnerLinkType name="shippingLT">
    <plnk:role name="shippingService">
      <plnk:portType name="pos:shippingPT" />
    </plnk:role>
    <plnk:role name="shippingRequester">
      <plnk:portType name="pos:shippingCallbackPT" />
    </plnk:role>
  </plnk:partnerLinkType>

  <plnk:partnerLinkType name="schedulingLT">
    <plnk:role name="schedulingService">
      <plnk:portType name="pos:schedulingPT" />
    </plnk:role>
  </plnk:partnerLinkType>

  <types>
    <xsd:schema>
      <xsd:import namespace="http://manufacturing.org/xsd/purchase"
        schemaLocation="purchase.xsd" />
    </xsd:schema>
  </types>

  <message name="POMessage">
    <part name="customerInfo" type="sns:customerInfo" />
    <part name="purchaseOrder" type="sns:purchaseOrder" />
  </message>

  <message name="InvMessage">
    <part name="IVC" type="sns:Invoice" />
  </message>

  <message name="orderFaultType">
    <part name="problemInfo" element="sns:problemInfo" />
  </message>

  <message name="shippingRequestMessage">
    <part name="customerInfo" type="sns:customerInfo" />
  </message>

  <message name="shippingInfoMessage">
    <part name="shippingInfo" type="sns:shippingInfo" />
  </message>

  <message name="scheduleMessage">
    <part name="schedule" type="sns:scheduleInfo" />
  </message>

  <!-- portTypes supported by the purchase order process -->

  <portType name="purchaseOrderPT">
    <operation name="sendPurchaseOrder">
      <input message="pos:POMessage" />
      <output message="pos:InvMessage" />
      <fault name="cannotCompleteOrder" message="pos:orderFaultType" />
    </operation>
  </portType>

  <portType name="invoiceCallbackPT">
    <operation name="sendInvoice">
      <input message="pos:InvMessage" />
    </operation>
  </portType>

  <portType name="shippingCallbackPT">
    <operation name="sendSchedule">
      <input message="pos:scheduleMessage" />
    </operation>
  </portType>

  <!-- portType supported by the invoice services -->

  <portType name="computePricePT">
    <operation name="initiatePriceCalculation">
      <input message="pos:POMessage" />
    </operation>
    <operation name="sendShippingPrice">
      <input message="pos:shippingInfoMessage" />
    </operation>
  </portType>

  <!-- portType supported by the shipping service -->

  <portType name="shippingPT">
    <operation name="requestShipping">
      <input message="pos:shippingRequestMessage" />
      <output message="pos:shippingInfoMessage" />
      <fault name="cannotCompleteOrder" message="pos:orderFaultType" />
    </operation>
  </portType>

  <!-- portType supported by the production scheduling process -->

  <portType name="schedulingPT">
    <operation name="requestProductionScheduling">
      <input message="pos:POMessage" />
    </operation>
    <operation name="sendShippingSchedule">
      <input message="pos:scheduleMessage" />
    </operation>
  </portType>

</definitions>

WS-I is an industry organization that promotes Web services interoperability across platforms. On April 16, 2004 the organization published the Basic Profile (BP) 1.0. The BP contains guidelines and recommendations to fill gaps and ambiguities in core Web services specifications such as SOAP and WSDL that led to compatibility issues.

BPEL4WS 1.1, published May 5, 2003, predates BP 1.0. Not surprisingly, the sample WSDL document does not adhere to the profile. The changes above are required to make the document compliant with BP 1.0.

7.1.3. Deploy the process definition

Our master WSDL document purchase.wsdl supplies all the WSDL elements required by the process. There are no other WSDL documents.

If supplied, bpel-definition.xml would point to the purchase.bpel and purchase.wsdl files from the previous two sections.

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

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

</bpelDefinition>

To deploy the process definition to the jBPM database, call:

ant deploy.process

The above target creates a file named purchase.zip and submits it to the jBPM service. The server console should read:

13:27:34,921 INFO  [DeploymentServlet] deployed process definition: PurchaseOrder
13:27:35,640 INFO  [WebModuleBuilder] packaged web module: purchase.war
13:27:35,640 INFO  [DeploymentServlet] deployed web module: purchase.war
13:27:36,734 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=purchase, «
endpoint=invoiceRequesterServlet
13:27:36,734 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=purchase, «
endpoint=purchaseServiceServlet
13:27:36,750 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=purchase, «
endpoint=shippingRequesterServlet
13:27:36,765 INFO  [TomcatDeployer] deploy, ctxPath=/purchase, warUrl=...
13:27:36,937 INFO  [IntegrationConfigurator] message reception enabled for process: «
PurchaseOrder
13:27:37,140 INFO  [WSDLFilePublisher] WSDL published to: .../purchase-service.wsdl

The purchase order process requires the collaboration of three different partner services to complete: invoice, shipping and scheduling. To deploy them, change to the partner directory and run the next target.

ant deploy.webservice

The screen below confirms the correct deployment of the partner services.

16:09:10,390 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=invoice, «
endpoint=invoiceServlet
16:09:10,421 INFO  [TomcatDeployer] deploy, ctxPath=/invoice, warUrl=...
16:09:10,859 INFO  [WSDLFilePublisher] WSDL published to: .../invoice.wsdl
--
16:09:36,312 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=shipping, «
endpoint=shippingServlet
16:09:36,343 INFO  [TomcatDeployer] deploy, ctxPath=/shipping, warUrl=...
16:09:36,671 INFO  [WSDLFilePublisher] WSDL published to: .../shipping.wsdl
--
16:09:47,328 INFO  [DefaultEndpointRegistry] register: jboss.ws:context=scheduling, «
endpoint=schedulingServlet
16:09:47,390 INFO  [TomcatDeployer] deploy, ctxPath=/scheduling, warUrl=...
16:09:47,734 INFO  [WSDLFilePublisher] WSDL published to: .../scheduling.wsdl

You should have the following endpoints listed in the JBossWS service endpoints page.

Table 7.1. Partner endpoints

Partner linkEndpoint
invoicinghttp://127.0.0.1:8080/invoice/invoiceService
shippinghttp://127.0.0.1:8080/shipping/shippingService
schedulinghttp://127.0.0.1:8080/scheduling/schedulingProcess

Table 7.2. My endpoints

Partner linkEndpoint
purchasinghttp://127.0.0.1:8080/purchase/purchaseService
invoicinghttp://127.0.0.1:8080/purchase/invoiceRequester
shippinghttp://127.0.0.1:8080/purchase/shippingRequester

Do not forget to register the partner services in the catalog. Use this command to do it.

ant register.partners

You can verify the partner services are visible in the catalog using the web console.

Table 7.3. Service catalog

Base locationServices
http://localhost:8080/invoice/invoiceService?wsdlinvoiceService
http://localhost:8080/shipping/shippingService?wsdlshippingService
http://localhost:8080/scheduling/schedulingProcess?wsdlschedulingService

7.2. Build the WSEE application client

7.2.1. Application client deployment descriptor

Reference your WSDL definitions 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>Purchase Order Client</display-name>

  <service-ref>

    <service-ref-name>service/PurchaseOrder</service-ref-name>
    <service-interface>
      org.jbpm.bpel.tutorial.purchase.PurchaseOrderService
    </service-interface>
    <wsdl-file>META-INF/wsdl/purchase-service.wsdl</wsdl-file>
    <jaxrpc-mapping-file>META-INF/purchase-mapping.xml</jaxrpc-mapping-file>

  </service-ref>

</application-client>

7.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.

7.3. Test the process

Once our process is up and running, we need to make sure that it works as expected. Here we create a JUnit test case called PurchaseOrderTest and exercise different usage scenarios.

7.3.1. Remote web service access

This is the setup code for establishing a connection with the purchase order process:

private PurchaseOrderPT purchaseOrderPT;

  protected void setUp() throws Exception {
    InitialContext ctx = new InitialContext();
    /*
     * "service/PurchaseOrder" 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
     */
    PurchaseOrderProcessService service = (PurchaseOrderProcessService) ctx.lookup(
        "java:comp/env/service/PurchaseOrder");
    purchaseOrderPT = service.getPurchaseServicePort();
  }

The descriptions of the test scenarios come next.

  1. testSendPurchaseOrderAvailable: fill in a purchase order with details about the customer, the shipping address and the requested items. The shipper serves the given address. Hence, we expect to place this order and get the corresponding invoice back without any problem.

    public void testSendPurchaseOrderAvailable() throws RemoteException {
      CustomerInfo customerInfo = new CustomerInfo();
      customerInfo.setCustomerId("manager");
      customerInfo.setAddress("123 Main St");
    
      PurchaseOrder purchaseOrder = new PurchaseOrder();
      purchaseOrder.setOrderId(10);
      purchaseOrder.setPartNumber(23);
      purchaseOrder.setQuantity(4);
    
      try {
        Invoice invoice = purchaseOrderPT.sendPurchaseOrder(customerInfo,
            purchaseOrder);
        /*
         * In our system, the part number is also the unit price! 
         * The shipper charges a flat fare of $10.95.
         */
        assertEquals(purchaseOrder.getPartNumber()
            * purchaseOrder.getQuantity()
            + 10.95, invoice.getAmount(), 0.001);
        assertEquals(purchaseOrder.getOrderId(), invoice.getOrderId());
      }
      catch (ProblemInfo e) {
        fail("shipping to available address should complete");
      }
    }
  2. testSendPurchaseOrderNotAvailable: fill in a purchase order with details about the customer, the shipping address and the requested items. The shipper does not serve the given address. Hence, we expect a problem when placing this order.

    public void testSendPurchaseOrderNotAvailable() throws RemoteException {
      CustomerInfo customerInfo = new CustomerInfo();
      customerInfo.setCustomerId("freddy");
      customerInfo.setAddress("666 Elm St");
    
      PurchaseOrder purchaseOrder = new PurchaseOrder();
      purchaseOrder.setOrderId(20);
      purchaseOrder.setPartNumber(13);
      purchaseOrder.setQuantity(7);
    
      try {
        purchaseOrderPT.sendPurchaseOrder(customerInfo, purchaseOrder);
        fail("shipping to unavailable address should not complete");
      }
      catch (ProblemInfo e) {
        assertTrue(e.getDetail().indexOf(customerInfo.getAddress()) != -1);
      }
    }

7.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.

7.3.3. Test execution

The following target runs the test case:

ant test

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

test.test:
    [junit] Running org.jbpm.bpel.tutorial.purchase.PurchaseOrderTest
    [junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 8.781 sec