Of course in enterprise procurement, sometimes creativity is required to obtain the right materials at the right time. While this creativity often rests in the heads of the staff involved in the procurement process, the computing systems that support those roles must also be able to deal with the logistics of the kinds of situations where staff order numerous partial shipments of parts from a number of suppliers to fulfill a single order. In this situation, we need a mechanism that permits orders to execute seamlessly across a number of Web services as though it was a single order sent to a single provider. In short, we need transactional support.
Adding transaction support into a Web services-based application is a multistage process that must be implemented by the software agents of all parties intending to participate in transactions. Though each party's responsibilities are generally different, they can be categorized into one of two roles:
In addition, the transacting Web services also need to agree on a mutually trusted transaction coordinator to control the execution of the transaction. While the nature of this trust varies (it can be based on physical location, or implemented using a WS-Security-based approach), Web services administrators may refuse to allow transactions involving their services to be driven by an unknown or untrusted coordinator. Such a refusal is usually on the basis that a rogue coordinator may attempt to interrupt the enterprise by holding up any resources involved with the transaction in a denial of service attack. For each of the vendor's Web services involved in our EPS application, we need to perform the following tasks:
The alternate part Web service is slightly different. Since it is effectively stateless and so does not require transactional support per se (transactions are for supporting consistent state changes across services), we don't need to register a header processor in the SOAP stack for the alternate part service, nor do we require that the service enrolls a participant since it has no interest in the outcome of the transaction and it knows that none of its state will be changed either way by its outcome. Once the additional infrastructure to support transactionality has been added into the application, the architecture looks a little different. The architecture of the transaction-aware application is shown in Figure 13-27, where we see transaction-aware Vendor A and Vendor B services with their transaction protocol handlers that deal with SOAP header blocks containing transaction contexts, collocated with participants which deal with the transaction protocol messages. The business-level services and the participants are both able to invoke operations on the back end, which provides the implementation for the service. In a typical enterprise application, the transaction protocol messages will be mapped onto back-end database systems to determine whether the business-level changes made to the data will be made persistent when the transaction has finished. Figure 13-27. The transaction-aware procurement application architecture.Now that we have considered the additional non-functional requirements that adding transactions places on the application as a whole and designed an architecture that can accommodate those requirements, the next step in the engineering process is to implement and integrate the new system components.
Changes to the Back End SystemsTransactions are a mechanism that helps to safeguard the consistency of state across Web services. However, in our previous back end implementation there was no mechanism available to allow changes to the back end state to be reversed. While we could order parts from the vendor services, there was no way of canceling that order once it had been made. Clearly for a transactional system, we need to be dealing with data structures with which we can back out changes if needed i.e., if a transaction fails. Therefore in this example, we chose to extend the original back end implementation so it supported a rudimentary cancel operation which, given an order number, will cancel the order and return parts to stock.
The implementation of the enhanced back end system is shown in Figure 13-28. Figure 13-28. Back end system implementation.package com.backend; import java.util.Hashtable; /** * A more complex model of an enterprise back-end, designed * to simulate some scenarios where databases, queues and * so forth are used to implement a Web service. It's not a * "true" enterprise back-end because it is necessarily * simplified, but the overall concepts are very similar. */ public class ComplexBackend extends SimpleBackend { public ComplexBackend(){} /** * The orderParts method picks items from stock and * readies them for shipping. * It gives the caller a unique order number which * references the shipping items. * * @param int partNo The part number of the part being * ordered. * @param int quantity The number of those parts that * are required. * @return The order number for the ordered parts. Will * return -1 if the order could not be honored * (e.g. not enough in stock). */ public int orderParts(int partNo, int quantity) { if(_inventory.getQuantityInStock(partNo) >= quantity) { _orderNumber++; // Place parts onto shipping queue InventoryEntry shippingParts = new InventoryEntry (_inventory.getPartFromPartNumber(partNo), quantity); _shippingOrders.put(new Integer(_orderNumber), shippingParts ); // Remove parts from stock. _inventory.removeItem (_inventory.getPartFromPartNumber(partNo), quantity); // Give a visual indicator that parts are shipping System.out.println("Shipping: \n" + shippingParts); System.out.println (shippingParts.getPart().getPartNumber() + " left in stock: " + _inventory.getQuantityInStock (shippingParts.getPart().getPartNumber())); return _orderNumber; } return -1; // Could not honor order } /** * The cancel order method can be used to send an order * from shipping back into inventory. While this method * could be consumed directly by, Web services clients, * in this example we use it as an example of * application level compensation which is invoked by the * underlying transaction system. * * @param int orderNo The order of the number that is * to be cancelled. This will be the same int * value that was returned when the order was * placed. */ public void cancelOrder(int orderNo) { if(_shippingOrders.containsKey(new Integer(orderNo))) { // If there is an order in shipping, stop and return // the parts to the inventory InventoryEntry entry = (InventoryEntry) _shippingOrders.remove(new Integer(orderNo)); _inventory.addItem(entry.getPart(), entry.getQuantity(), entry.getLeadtime()); // Alert user System.out.println("Returned to inventory: "); System.out.println(entry); } } private Hashtable _shippingOrders = new Hashtable(); } The back end now consists of a more fully featured orderParts method that not only removes items from stock, but also immediately places them into a second structure that holds shipping orders. The back end is said to behave optimistically because it commits resources for shipping immediately. The cancelOrder method provides an application-level logical reverse of the orderParts method that removes the order from the set of shipping orders, and increments the inventory with the number of parts that have been removed from shipping.
At this point, we now have the back end system in place for each vendor service, and we know that the back ends are now suitable for running transactions across. The next step is to expose our new "transactional" resource to the network through its associated business Web service. Transaction-Aware Service ImplementationNow that we understand the architectural changes that have taken place and seen how the back end has been remodeled to support transactional behavior, we can develop our strategy for supporting transactions at the Web services level. There are two distinct aspects to this support, the first being the construction of a service that is transaction-context aware and the second being the integration of this context-aware service with the underlying transaction toolkit. To make the business-level Web service transaction-context aware, we need to integrate it with the underlying transaction toolkit and SOAP server. In the transaction toolkit we use in this example, this is a matter of registering a handler for transaction contexts at the same time that the service itself is deployed onto the SOAP server. Since we use the standard Apache Axis toolkit to deploy our sample application, we enhance our deployment configuration file (deploy.wsdd) and specify the name of the handler class that we want to support our service. This is shown in Figure 13-29. The Axis deployment description shown in Figure 13-29 registers both the transactional services and handlers for transaction contexts for those services that require transactional support (i.e., those services that rely on transactions to guarantee consistent state). In fact, for each transactional service, we need to register three items with the underlying SOAP engine. These are:
With the transaction toolkit that we use in this example, incoming and outgoing context management are both handled by the same implementing class, com.hp.mw.xts.platforms.java.axis.service.BTPContextProcessor, and so the element <handler type="BTPContextProcessor"/> appears in both the <requestFlow> and <responseFlow> elements. Since we intend for our service to be invoked via SOAP-RPC, we register it with the RPC provider using provider="java:RPC" in the <service> element. Finally, the implementing class for the service is specified as a parameter <parameter name="className" value="com.altpart.AlternatePartService"/>. Once we have our deployment file written, we use the Axis administrative features to register the service with a running SOAP server. This is easily achieved on the command line with the command: java org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services/AdminService where the URL specified as the l switch is the location where your Axis installation is hosted. Once this command is executed, the Axis SOAP server will alter its configuration in accordance with the configuration file, as shown in Figure 13-30, and we are ready to invoke it and include it in business transactions. Figure 13-30. The Web service-side infrastructure after deployment.In addition to the changes at the back end and the introduction of transaction-processing infrastructure, we have also enhanced the Web service itself such that it exposes information about the business level operations. This enhancement consists of an additional static method and hash table, which maintains a mapping between transaction contexts (provided by the underlying transaction toolkit) and order numbers (generated by the back end). Thus, given a transaction context, it is possible to discover the order numbers associated with that context, so that should the associated transaction fail, the orders (via their order numbers) can be readily cancelled.
The service implementation for the vendor services is shown in Figure 13-31. Although we show only the Vendor A service here, the Vendor B service is similar enough (though with Vendor B-specific method names) that it has been omitted for the sake of brevity. Figure 13-31. The transaction-aware Vendor A service implementation.package com.vendora; import java.util.Hashtable; /** * The TransactionalVendorAService provides additional * infrastructure to support transactionality for the * VendorAService. */ public class TransactionalVendorAService extends VendorAService { /** * The placeOrder method has the same functionality as the * method from the parent class, aside from it stores * an in-memory mapping between the order number and the * transaction context that it was created within. * * Note: in a true enterprise system, this in-memory * mapping would have to be persistent (e.g. serialized to * disk or database) in case the service crashed. It is * not made persistent here for the sake of clarity. * * @param int partNumber The part number of the * requested parts. * @param int quantity The quantity of the parts * requested. */ public int placeOrder(int partNumber, int quantity) { int orderNo = super.placeOrder(partNumber, quantity); try { // Do the mapping thing with the TX context // and order ID com.hp.mw.ts.arjuna.common.Uid currentTxUid = com.hp.mw.xts.management.ServiceManagement.getUidFromContext(); if(currentTxUid != null) { if(_txContextOrderMappings.containsKey(currentTxUid)) { Integer[] existingOrders = (Integer[]) _txContextOrderMappings.get(currentTxUid); Integer[] newOrders = new Integer[existingOrders.length + 1]; for(int i = 0; i < existingOrders.length; i++) { newOrders[i] = existingOrders[i]; } newOrders[newOrders.length-1] = new Integer(orderNo); for(int i = 0; i < newOrders.length; i++) { System.out.println(newOrders[i]); } } } } catch(Exception e) { // If we get here, there's no transaction to // worry about. } return orderNo; } /** * The getOrderID method returns the orderID(s) associated * with the current transaction context. * * @return int[] The order ID's associated with the * current transaction context. */ static int[] getOrderID() { try { // Retrieve the array of orders that are associated // with this context com.hp.mw.ts.arjuna.common.Uid currentTxUid = com.hp.mw.xts.management.ServiceManagement.getUidFromContext(); Integer[] orderNumbers = (Integer[])_txContextOrderMappings.get(currentTxUid); if(orderNumbers == null) { return null; } else { int[] result = new int[orderNumbers.length]; for(int i = 0; i < result.length; i++) { result[i] = orderNumbers[i].intValue(); } return result; } } catch(Exception e) { return null; } } /** * This hashtable is keyed on transaction context, and * stores arrays of integers which are themselves order * numbers. */ private static Hashtable _txContextOrderMappings = new Hashtable(); } The final piece of work to complete this service is the configuration of the transaction infrastructure from the chosen transaction toolkit, so that when the service is invoked within the scope of a transaction (that is, it receives a message with a transaction context in the SOAP header) an appropriate participant is enrolled into the transaction. If we recall the explanation offered in Chapter 6, the participant is the entity that deals with the transaction protocol on behalf of the associated business-level Web service. However, the enrollment of participants by a service is more of an administrative task than an architectural or development task, and as such the underlying transaction toolkit allows us to administratively specify the location of a participant to enroll whenever a transactional invocation occurs. This is achieved through a configuration file, which is shown in Figure 13-32. The default-service-rule-engine-config.xml configuration file is used to declare a set of simple rules that the underlying transaction toolkit can use to make choices about which participants to enroll in response to certain invocations, based on what is being invoked and within the scope of what kind of transaction the invocation occurred. Figure 13-32. default-service-rule-engine-config.xml configuration file.<sre:default-service-rule-engine xmlns:sre= "http://www.arjuna.com/schemas/xts/service-rule-engine/2002/10/"> <sre:rule> <sre:transaction-type>any</sre:transaction-type> <sre:service-endpoint> http://localhost:8080/axis/services/TransactionalVendorAService </sre:service-endpoint> <sre:participant> http://localhost:8080/axis/services/VendorAParticipant </sre:participant> </sre:rule> <sre:rule> <sre:transaction-type>any</sre:transaction-type> <sre:service-endpoint> http://localhost:8080/axis/services/TransactionalVendorBService </sre:service-endpoint> <sre:participant> http://localhost:8080/axis/services/VendorBParticipant </sre:participant> </sre:rule> </sre:default-service-rule-engine> A rule consists of a triplet of values for transaction-type, service-endpoint, and participant. That is, a rule allows the administrative specification of which participant to enroll when a particular service is invoked within the scope of a particular type of transaction. For instance, one of the rules in Figure 13-32 states that whenever any kind of transaction context arrives with an invocation on the TransactionalVendorAService, that VendorAParticipant will be enrolled. This can be refined as necessary such that different participant implementations can be enrolled based on specific transaction types, or even a combination of transaction type and service invoked without having to resort to code.
From this point the business-level service is complete, and we have constructed a service-side architecture that looks like the diagram shown in Figure 13-33. Figure 13-33. The service-side infrastructure up close.As Figure 13-33 shows, although we had to upgrade our service to expose order numbers associated with particular transactions, most of this work is handled by the underlying transaction toolkit. The Web services aspects of the application have not forced changes onto the actual business logic back end, if we ignore that we had to emulate transactionality with additional cancellation methods in the back end (since that functionality is usually provide by default by a database or other transactional resource). At this point we are ready to join together the transaction-aware service with the underlying transaction protocol, which is done through the development and deployment of a participant. Implementing ParticipantsThe participant implementation is the piece of logic that determines what back end transactional behavior occurs in response to transaction protocol messages out on the Web services network. Using our toolkit, integrating participants into an application is a two-stage process which involves developing the participant logic, and specifying rules for its enrollment (which are complimentary to the rules that the service uses to enroll participants) into a transaction. However, let's begin our discussion of the participant looking at the implementation for the Vendor A participant, as shown in Figure 13-34. Note that the implementation for Vendor B's participant is once again similar enough to Vendor A's that we can safely omit a full discussion of its implementation here. The methods that the VendorAParticipant supports in Figure 13-34 directly correspond to the messages exchanges that a participant is involved in within the scope of a BTP transaction. The most important of these methods are the three that are involved in the two-phase confirm aspects of BTP: prepare, confirm, and cancel. The prepare method returns a Vote that indicates to the calling transaction coordinator whether it will proceed to make the work done in the scope of the transaction durable to indicate whether state changes in the service will remain or be deleted when the transaction finishes. In this case, the method is hardwired to always return a VoteConfirm object, which indicates to the invoking transaction manager that state changes associated with this participant will be persisted after the transaction is finished. Since we have chosen this optimistic approach, only in those (presumably minor number of) cases where something goes wrong will the participant have to take restorative action. Figure 13-34. The VendorAParticipant implementation.package com.vendora; import com.hp.mw.xts.participant.*; import com.hp.mw.ts.arjuna.common.Uid; import com.hp.mw.xts.core.exceptions.*; import com.hp.mw.xts.core.common.Vote; import com.hp.mw.xts.core.common.VoteConfirm; import com.hp.mw.xts.core.common.VoteCancel; import com.hp.mw.xts.qualifiers.Qualifier; import com.hp.mw.xts.management.ParticipantManagement; import com.hp.mw.ts.arjuna.state.InputObjectState; import com.hp.mw.ts.arjuna.state.OutputObjectState; /** * The VendorAParticipant provides the transaction * participant (the actor that understands the transaction * protocol) for the TransactionalVendorAService. */ public class VendorAParticipant implements Participant { /** * Implements the Participant.prepare operation. * * @param id The identifier of the entity that is to be * prepared * @param qualifiers ignored in this implementation * @return Vote to CONFIRM or CANCEL the transaction */ public Vote prepare(Uid id, Qualifier[] qualifiers) throws GeneralException, InvalidInferiorException, WrongStateException, HeuristicHazardException, HeuristicMixedException { // VendorA takes an optimistic strategy and always // internally commits resources and votes to confirm. return new VoteConfirm(); } /** * Implements the Participant.confirm operation. * * @param id The inferior which is to have <i>confirm</i> * invoked on it * @param qualifiers ignored in this implementation */ public void confirm(Uid id, Qualifier[] qualifiers) throws GeneralException, InvalidInferiorException, WrongStateException, HeuristicHazardException, HeuristicMixedException { // Do nothing on confirm because VendorA's strategy // is to confirm immediately. } /** * Implements the Participant.cancel operation. * * @param id The inferior which is to have <i>cancel</i> * invoked on it * @param qualifiers ignored in this implementation */ public void cancel(Uid id, Qualifier[] qualifiers) throws GeneralException, InvalidInferiorException, WrongStateException, HeuristicHazardException, HeuristicMixedException { // The resources have already been committed. Must // now perform compensating action. // Get the orderNos from the // TrasactionalVendorAService int[] ordersToCancel = TransactionalVendorAService.getOrderID(); if(ordersToCancel != null) { for(int i = 0; i < ordersToCancel.length; i++) { // Invoke compensating action at the back end TransactionalVendorAService. _backend.cancelOrder(ordersToCancel[i]); } } } /** * Implements the Participant.contradiction operation. * * @param id The inferior to inform * @param qualifiers ignored in this implementation */ public void contradiction(Uid id, Qualifier[] qualifiers) throws GeneralException, InvalidInferiorException, WrongStateException { // There has been a contradiction, shout for help! System.err.println( "\t\tVendorAParticipant.contradiction(" + id.stringForm() + ")"); } /** * Implements the Participant.superiorState operation. * * Not required for this example * * @param id * @param qualifiers * @return null in all cases */ public String superiorState(Uid id, Qualifier[] qualifiers) throws GeneralException, InvalidInferiorException { return null; } /** * Implements the Participant.defaultIsCancel operation. * * The underlying business logic does not support * timeouts. * * @param id The inferior to query * @return false in all cases because default action is * to confirm. */ public boolean defaultIsCancel(Uid id) { return false; } /** * Implements the Participant.confirmedReceived * operation. * * This implementation does not acknowledge receipt of * confirm messages. * * @param id The inferior to query * @return false in all cases */ public boolean confirmedReceived(Uid id) { return false; } public int status(Uid id, Qualifier[] qualifiers) throws GeneralException, InvalidInferiorException { System.err.println( "\t\tVendorAParticipant.superiorState(" + id.stringForm() + ")"); return com.hp.mw.xts.core.common.TwoPhaseStatus.UNKNOWN; } public String name() { return "VendorAParticipant implementation"; } // These are left empty because the VendorAParticipant // does not need to be recoverable. public boolean packState (OutputObjectState os) { return true; } public boolean unpackState (InputObjectState os) { return true; } } Since we have chosen to commit state changes in the first phases of the two-phase confirm process (during the prepare phase), there is nothing to do if the transaction gets to the confirm phase and so the confirm method is left empty. Conversely, should the transaction coordinator decide to cancel the transaction, the cancel method will be invoked, which will perform a compensating action to reverse the work done by the service. In this case that compensation will be to invoke the cancelOrder method on the back end system for each part that was ordered within the scope of the transaction. Like the business-level Web services, the participant is also deployed into the SOAP server. The only difference here is that the consumer of the participant service is always another Web service (the transaction manager service) and not client applications. Deploying the service is straightforward and relies on a simple deployment descriptor file as shown in Figure 13-35. Figure 13-35. Deploying a participant with an Axis deployment file.<deployment name="ParticipantDeployment" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <service name="VendorAParticipant" style="message"> <parameter name="className" value= "com.hp.mw.xts.platforms.java.axis.documentHandlers.ParticipantDocumentHandler"/> <parameter name="allowedMethods" value="invoke"/> </service> <service name="VendorBParticipant" style="message"> <parameter name="className" value= "com.hp.mw.xts.platforms.java.axis.documentHandlers.ParticipantDocumentHandler"/> <parameter name="allowedMethods" value="invoke"/> </service> </deployment> Like business Web services, participants must also be deployed into a SOAP server to be accessible. However, the participant services don't require any additional SOAP actors to be registered as handlers in their service stacks, since they only deal with transaction protocol messages and not application-plus-context messages. Thus, the deployment configuration files are correspondingly simpler, as we see in Figure 13-35. There are, however, a few remarkable differences between the participant deployment configuration file and the transactional service deployment configuration file that we saw in Figure 13-29. First, the participant service uses document-style SOAP to exchange messages (it exchanges arbitrary XML documents in its SOAP payload, not SOAP-schema encoded payloads) and so the style attribute in the service element is set to style="message". Furthermore, because the service now needs to understand arbitrary SOAP message encodings, it needs to be configured with an implementation class that can decode those messages and dispatch them appropriately. This is handled by the className parameter, which specifies a particular document handler to which incoming messages will be routed. The ParticipantDocumentHandler class is a component from the underlying transaction toolkit that understands the transaction protocol messages and is able to dispatch them appropriately at the back end. The final parameter, allowedMethods, specifies a list of the methods from the ParticipantDocumentHandler class that will be exposed as a Web service. In this case, we only require that the invoke method is exposed since this is the only necessary conduit to enable BTP messages to be routed through to the back end. The last piece in the puzzle for service-side transactionality is the administration of the participant. For this we complete the participant-side rule engine configuration, from the underlying transaction engine toolkit, where we specify a mapping from endpoints to particular implementations. This is in effect the reciprocal feature of the service side rule engine configuration, and allows a particular participant implementation to be used based on the endpoint of the ultimateReceiver so that many different participant implementations can be hosted and routed by a single SOAP server instance. Figure 13-36. default-participant-rule-engine-config.xml configuration file.<?xml version="1.0" encoding="UTF-8"?> <pre:default-participant-rule-engine xmlns:pre="http://www.arjuna.com/schemas/xts/participant-rule-engine/2002/10/"> <pre:rule> <pre:participant-endpoint> http://localhost:8080/axis/services/VendorAParticipant </pre:participant-endpoint> <pre:participant-implementation> com.vendora.VendorAParticipant </pre:participant-implementation> </pre:rule> <pre:rule> <pre:participant-endpoint> http://localhost:8080/axis/services/VendorBParticipant </pre:participant-endpoint> <pre:participant-implementation> com.vendorb.VendorBParticipant </pre:participant-implementation> </pre:rule> </pre:default-participant-rule-engine> In Figure 13-36, we map the participant endpoint onto the implementing Java class. Now when a BTP message arrives and is processed by the ParticipantDocumentHandler, it will automatically be dispatched to the correct participant implementation. Like the equivalent service-side rules, this means that a system can be evolved over time without having to resort to coding. As we see in Figure 13-37, at this point the transactional service is ready for consumption. The participant implementation can cooperate with the service to provide the additional transactional quality of service that enterprises require. All that is left now is to consume the functionality offered by the service, and see how we can drive its transactional aspects. Figure 13-37. Participant and service-side infrastructure.Consuming Transactional Web ServicesWriting transactional applications involves not only developing business logic, but also controlling the underlying transactional behavior of the system. The key to developing such applications is to partition the work into suitable transactions so that work can be reversed, replayed and so forth as necessary. In this example, we try to purchase components from both suppliers where we will only proceed with the order if both suppliers commit to providing the necessary parts. Where either one of the suppliers is unable to meet its commitment, or where there is a failure that prevents the order from making progress, we will cancel the transaction. Canceling the transaction causes the transaction manager to instruct all enrolled participants to perform the appropriate restorative action at the back end of their associated service. As we saw in Figure 13-34, this ultimately has the effect of reversing any work done so that the transaction appears to have never run. A sample client application is shown in Figure 13-38. Figure 13-38. Client application implementation.package client; import com.hp.mw.xts.*; import com.hp.mw.xts.core.common.StatusItem; import com.hp.mw.xts.core.common.Vote; import com.hp.mw.xts.core.common.VoteConfirm; import com.hp.mwlabs.xts.portability.xml.XMLProvider; import com.vendora.VendorAStub; import com.vendorb.VendorBStub; import com.altpart.AlternatePartStub; /** * Runs a simple transactional invocation on each service. */ public class TransactionalClient { public static void main(String[] args) { try { TransactionManager tm = TransactionFactory.getTransaction( XMLProvider.getDOMDocument(CONFIG)); UserTransaction ut = (UserTransaction)tm; // We're going to repeat this order until someone is // out of stock to show transactions cancelling... // We happen to know that there are only 250 spark // plugs available in VendorB, so 7 times round will // do it for(int i = 0; i < 6; i++) { // Start the transaction - in this case an atom ut.begin(TxTypes.ATOM); // Business logic VendorAStub vas = new VendorAStub(new java.net.URL( "http://localhost:8080/axis/services/TransactionalVendorAService")); VendorBStub vbs = new VendorBStub(new java.net.URL( "http://localhost:8080/axis/services/TransactionalVendorBService")); // Going to order 2 Head Gaskets from VendorA and // 45 spark plugs from B, or nothing at all. int orderA = vas.placeOrder(101, 2); int orderB = vbs.orderComponent(45, 888); // Terminate the transaction Vote prepareResult = ut.prepare(); if(prepareResult instanceof VoteConfirm && orderA >= 0 && orderB >= 0) { System.out.println("Orders: " + orderA + " and " + orderB + " have successfully been placed."); ut.confirm(); } else { System.out.println("Orders: " + orderA + " and " + orderB + " have been cancelled."); ut.cancel(null); } } System.exit(0); } catch(Exception e) { e.printStackTrace(System.err); System.exit(1); } } private static final String CONFIG = "<proxy-config xmlns=\"http://www.arjuna.com/schemas/xts/proxy/2002/10/\">" +"\n<properties>" +"\n<property name=\"usertransaction_implementation\" " +"value=com.hp.mwlabs.xts.api.proxy.UserTransactionProxy\"/>" +"\n<property name=\"transactionmanager_implementation\" " +"value=\"com.hp.mwlabs.xts.api.proxy.TransactionManagerProxy\" />" +"\n</properties>" +"\n<transaction-manager-url>" +"http://localhost:8080/axis/services/WebTransactionManager" +"</transaction-manager-url>" +"\n</proxy-config>"; } This application can be broken down into four broad phases. The first of these phases is where the client binds to the transaction manager that will support the subsequent transactional behavior. This is realized by the opening lines of the main method that create and initialize a TransactionManager and UserTransaction instance (which is achieved through an XML configuration file which the application encodes as a static string). It is the UserTransaction instance that is then subsequently used to delimit the transactional behavior of the application. The begin method of the UserTransaction instance is called to instruct the remote transaction manager to create a new BTP atom. Once this call completes any further work, including Web services invocations (where the Web services support transactions), is implicitly within the scope of this transaction. At the client application side, the current thread is associated with the context. Whenever a Web services invocation is made, the XML form of the context travels in a SOAP header block to the invoked service where, as we have seen, the service-side infrastructure handles thread-context association and enrollment of participants. The real work of this application is contained within its business logic. In this case the application simply orders parts from the two vendor services. Supported by the transaction, the orders continue until such point that either there is an underlying fault or until the order process fails which causes the transaction to cancel, and any orders and parts currently unsettled to be cancelled are returned to stock. It is important to understand that this is possible because of the open-top nature of the underlying transaction protocol. Since both the prepare and confirm/cancel phases of the two-phase confirm are driven by the client application, it is possible for the application to affect the transaction directly and assert that the transaction confirm or cancel, just like the transaction manager. Application-level rules can be used to complete or abort transactions in addition to the standard semantic where faults are the cause of transactional aborts. Once the client application is developed and built, the final stage is to deploy and execute. Since we have chosen the Apache Axis platform for building this demo application, we need to configure our application to use the Axis client-side infrastructure to propagate transaction contexts on our behalf. The configuration for this is shown in Figure 13-39. Like the service-side configuration, the client configuration registers handlers that deal with thread-context association at the client side. Once this configuration is in place, the application is ready to execute. Figure 13-39. An Axis client-config.wsdd configuration file.<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <globalConfiguration> <parameter name="sendXsiTypes" value="true"/> <parameter name="sendMultiRefs" value="true"/> <parameter name="sendXMLDeclaration" value="true"/> <requestFlow> <handler type= "java:com.hp.mw.xts.platforms.java.axis.client.OutgoingContextHandler"/> </requestFlow> <responseFlow> <handler type= "java:com.hp.mw.xts.platforms.java.axis.client.IncomingContextHandler"/> </responseFlow> </globalConfiguration> <transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/> </deployment> |