The Online Banking Application

To illustrate the PTP programming model, let us implement an online banking application. Before we start, please be aware that the intention here is to present as many JMS features as possible in order to demonstrate the array of options available to you when developing JMS applications. A lot of error checking has been removed to present relevant information and keep the discussions pertinent to JMS, so that we don't digress from the topic at hand.

Specification

eCommWare Corporation provides online banking services to its customers. This service allows its online customers to connect to any of their accounts in other banks and perform day-to-day banking operations. We will be looking at three different operations that customers can perform on their bank accounts online:

  • Transactional transfer of funds from one account to another

  • Stopping payment instructions from an account

  • Browsing pending instructions for a particular account

The figure below illustrates our online banking solution:

click to expand

The teller application is the front-end GUI that the users of the application will see. The user is initially asked to logon to the system when they request the services of the bank teller application. The user is prompted to enter the bank they want to connect to (the bank's routing number has to be specified), their name, their account number, and their password. The system then authenticates and authorizes the user to perform operations on their accounts through their bank's accounts department.

Transferring Funds

To transfer funds from one account to another, the user will have to specify the payee bank's routing number, the payee information like their name, account number, and the amount of money they are going to transfer and click on the Submit button.

As soon as the user has specified the instruction, a transactional message is sent to the bank:accounts queue to transfer the amount. The bank's accounts department module that is listening to this queue gets this message.

This module in turn checks its books to verify that the user has sufficient funds in their account, debits the specified amount, and sends a message to the bank:clearing_house queue stating the amount involved in the transaction. The payee accounts department's liability to the account holder goes down by that much. Both operations are part of one transactional grouping. The payee bank's account department module that is listening to the bank:clearing_house queue receives the message transactionally and credits the payee's account.

The sequence of operations of what we just discussed above is shown diagrammatically in the figure below:

click to expand

Stop Payments

Similarly, when the account holder needs to send a stop payment instruction to their bank, they construct the stop payment message by filling in the check number, the payee's name, and the amount of the check and send it off to their bank transactionally as a highest priority message.

The scenario diagram for sending stop payment messages from the teller to the bank's accounts department as discussed above is shown diagrammatically in the figure below:

click to expand

Browse Pending Instructions

It is also possible for the account holder to browse all their pending operations from the various queues. When that option is selected, they are returned a vector of all their operations that are pending to be executed. A temporary queue is created and is used to pass this information along between the accounts department and the teller module.

The figure below diagrammatically illustrates a scenario where the pending messages for a particular account are browsed and passed on to the teller module through the temporary queue that the teller has created dynamically. Once again note that the accounts department's send and receive operations are part of one transactional group:

click to expand

Let us now examine how to program the major modules.

The teller module consists of the Teller class, the TellerPanel class, and the Login class. The accounts department module consists of the AccountsDepartment class, the AccountingPanel class and the DisplayHandler interface. All the modules of this application to exchange instructions use the Constants class and the Instruction class. At the end of each module, we will show how to compile and execute the samples.

The Constants Class

All the modules in the system use the Constants class to retrieve static constant values:

     package ptp.onlinebanking;     public class Constants {       static final String PROVIDER_URL =         "http://localhost:2001";          // For FioranoMQ. version 4.6     //  "http://localhost:1856";         // For FioranoMQ version 5.0       static final String USER_NAME = "athul";       static final String PASSWORD = "raj";       static final String FACTORY =         "fiorano.jms.rtl.FioranoInitialContextFactory";            // for FMQ 4.6     //  "fiorano.jms.runtime.naming.FioranoInitialContextFactory"; // for FMQ 5.0       static final String QCF = "bank:qcf";       static final String ACCOUNTS_QUEUE = "bank:accounts";       static final String CLEARING_QUEUE = "bank: clearing_house";       static final String TRANSFER_FUNDS = "Transfer Funds";       static final String STOP_PAYMENT = "Stop Payment";       static final String BROWSE_PENDING_REQUESTS = "Browse Pending Requests";       static final String ECOMMWARE_BANK_NO = "BANK1000";       static final String HERITAGE_BANK_NO = "BANK2000";       static final String RAJ_BANK_NO = "BANK3000";       //A static string denoting a FAILURE of instruction       static final int FAILED = -1;       //A static string denoting a REJECTED instruction       static final int REJECTED = 0;       // A static string denoting an APPROVED instruction       static final int APPROVED = 1;       static final int HIGHEST_PRIORITY_MESSAGE = 9;     } 

The Instruction Class

All the modules in the system use the Instruction class to exchange instructional information. The Instruction class implements the Java.io.Serializable interface and has getter/setter methods to retrieve its elements:

     package ptp.onlinebanking;     public class Instruction implements Java.io.Serializable {       protected String customerBank;       protected String accountNumber;       protected String customerName;       protected String date;       protected String checkNumber;       protected String routingNumber;       protected String payeeAccount;       protected String payeeName;       protected double amount;       protected String action;       public Instruction(){}       public Instruction (String bank, String account, String name,                           String time, String check, String routing,                           String payeeAcct, String payee, double money,                           String command) {         this.customerBank = bank;         this.accountNumber = account;         this.customerName = name;         this. date = time;         this.checkNumber = check;         this.routingNumber = routing;         this.payeeAccount = payeeAcct;         this.payeeName = payee;         this.amount = money;         this.action = command;       )       public String getCustomerBank() {         return customerBank;       }       public String getAccountNumber() {         return accountNumber;       }       public String getCustomerName() {         return customerName;       }       public String getDate() {         return date;       }       public String getCheckNumber() {         return checkNumber;       )       public String getRoutingNumber() {         return routingNumber;       }       public String getPayeeAccount() {         return payeeAccount;       }       public String getPayeeName() {         return payeeName;       }       public double getAmount() {         return amount;       }       public String getAction() {         return action;       }       public void setCustomerBank(String bank) {         customerBank = bank;       }       public void setAccountNumber (String accountNo) {         accountNumber = accountNo;       }       public void setCustomerName (String name) {         customerName = name;       }       public void setDate (String time) {         date = time;       }       public void setCheckNumber (String orderNo) {         checkNumber = orderNo;       }       public void setRoutingNumber (String routingNo) {         routingNumber = routingNo;       }       public void set PayeeAccount (String accountNo) {         payeeAccount = accountNo;       }       public void setPayeeName (String name) {         payeeName = name;       }       public void setAmount (double money) {         amount = money;       }       public void setAction (String command) {         action = command;       }       public void print() {         System.out.print In("\nAccount Number : " + accountNumber);         System.out.print In("Customer Name : " + customerName);         System.out.print In("Date : " + date);         System.out.print In("Check Number : " + checkNumber);         System.out.print In("Routing Number : " + routingNumber);         System.out.print In("Payee Account : " + payeeAccount);         System.out.print In("Payee Name : " + payeeName);         System.out.print In("Amount : $" + amount);         System.out.print In("Action : " + action + "\n");       }     ) 

The Teller Class

The Teller class is used to send out account information on the bank:accounts queue:

     package ptp.onlinebanking:     import javax.jms.*";     import javax:naming. * ;     import common.*;     import java.ut.i1. * ;     public class Teller (       private QueueConnectionFactory factory;       private QueueConnection connection;       private Queue queue;       private QueueSession session, transactSession;       private QueueSender sender;       private QueueRequestor requestor;       private String hostName;       private String userID;       private String password;       private static final String CONTEXT_FACTORY = Constants.FACTORY;       private static final String CONNECTION_FACTORY = Constants.QCF;       private static final String SENDER_QUEUE = Constants.ACCOUNTS_QUEUE; 

There are a couple of constructors for the Teller class. One of them is the default constructor that does not take any parameters. The other constructor takes in multiple parameters. In these are the hostname or IP:Port of the remote JMS server to connect to, and the username and password of a valid user for the JMS server:

       public Teller (String host. String user, String passwd) {         this.hostName = host;         this.userID = user;         this.password = passwd;       }       public Teller() {         this.hostName = Constants.PROVIDER_URL;         this.userID = Constants.USER_NAME;         this.password = Constants.PASSWORD;       } 

User Identification and Authentication

The createConnections() method creates and starts connections and sessions. The sample also demonstrates how to connect to JMS messaging servers that run remotely. Typically, this is done by specifying the hostname or the IP:Port address combination on the JNDI server with the PROVIDER_URL property. The JNDIService.init (CONTEXT_FACTORY, hostName) method of our common.JNDIService class does this for us. If you want to examine the relevant code for the JNDIService class, go to Chapter 3 where it was introduced. However, please update JNDIService.java to specify that it is in the package common:

       public void createConnections() {         try (           JNDIService.init (CONTEXT_FACTORY, hostName);           // Retrieve the connection factory object           factory = (QueueConnectionFactory) JNDIService                        .lookup(CONNECTION_FACTORY);           queue = (Queue) JNDIService.lookup (Teller.SENDER_QUEUE); 

A JMS client can supply its credentials as a name and password combination when creating a connection with the JMS messaging server. If no credentials are supplied, the JMS specification says that the current client thread's credentials are used.

Important 

At the time of writing, the JDK does not define the concept of a thread's default credentials (in this case we're talking about the client thread trying to connect to the JMS server). However, this concept may be defined in the near future. Until then, if a client supplies no credentials, the identity of the user should be taken as the one under which the JMS client is running.

User identification and authentication in JMS-based messaging servers generally works as follows. The system administrator initially sets up all the users in the system using an administration tool or other configuration system that comes with the provider.

A client application connects to the JMS server with the following code:

         QueueConnection connection = null;         connection = connectionFactory.createQueueConnection("athul", "raj"); 

The provider runtime on the client machine sends a connection request with the username and password to the messaging server. If the authentication succeeds, the connection requested is established and a valid connection object reference is returned to the client. However, if it does not match, the connection is rejected and this causes the provider runtime present on the client to throw a javax.jms.JMSSecurityException exception.

Similarly, if the user name sent in the login packet is not found in the repository, the messaging server will reject the connection, and cause the client run-time to throw a javax.jms.JMSSecurityException exception. The following code creates the connection in createConnections() using identification and authentication:

           connection = factory.createQueueConnection (userID, password);           connection.setExceptionListener (new ExceptionHandler());           session =             connection.createQueueSession(false,                                           Session.AUTO_ACKNOWLEDGE) ;           transactSession = connection.createQueueSession(true,                   Session.AUTO_ACKNOWLEDGE);           connection.start();         ) catch (Exception e) {           e.printStackTrace();         }       } 

Releasing Resources

It is very important to clean up resources as soon as they are no longer needed. This improves both the scalability and the efficiency of the application. The sender, sessions, and connection are closed and all resources are released before the program terminates. JMS messaging providers typically allocate significant resources outside the JVM on behalf of a connection. Therefore, clients should close connections when they are no longer required.

According to the JMS specification, "Relying on the garbage collector to reclaim these resources is not timely enough". A close() terminates all pending messages received on the connection's receivers. At the time of closing, these receivers may return with a message or not depending on whether there were any more messages available for them.

The closeConnections() method cleans up all the resources:

       public void closeConnections() {         try (           if (sender 1= null) {             sender.close();           }           if (session != null) {             session.close();           }           if (transactSession != null) {             transactSession.close();           }           if (connection != null) {             connection.close();           }         ) catch (Exception e) {           e.printStackTrace();         }       } 

There are three operations that the user can perform. They are:

  • Transfer funds from one account to another

  • Stop payments from an account

  • Browse pending instructions on an account

Depending on the choice that the user makes, the appropriate method is invoked. If the action specified in the instruction object is Constants.BROWSE_PENDING_REQUESTS, then a request is sent to the bank:accounts queue to browse pending instructions of the user's account by invoking the browsePendingRequests() method. For anything else, the sendInstructions() method is invoked:

       public int execute(Instruction instruction) {         int result = Constants.APPROVED;         try (           if (instruction.getAction()                   .equals(Constants.BROWSE_PENDING_REQUESTS)) {             result = browsePendingRequests(instruction);           ) else (             result = sendInstructions(instruction);           }         } catch (Exception e) {           e.printStackTrace();         }         return result;       } 

The figure below shows the Teller class sending out a message to the bank:accounts queue transactionally:

click to expand

A map message is created and populated with the information that needs to be sent (see Chapter 3). This message is then sent to its destination using a message sender (or producer). The sender object is then used to send out the message by invoking its send() method with a delivery mode of PERSISTENT, a message priority of Message. DEFAULT_PRIORITY, and a time-to-live of Message.DEFAULT_TIME_TO_LIVE:

       int sendInstructions(Instruction instruction) throws JMSException {         int result = Constants.APPROVED;         try {           // Create a Sender if one does not exist           if (sender == null) {             sender = transactSession.createSender(queue);           }           // Create a Map Message           MapMessage mapMessage = transactSession.createMapMessage();           // set message properties           mapMessage.setString("customerBank",                                instruction.getCustomerBank());           mapMessage.setString("accountNumber",                                instruction.getAccountNumber());           mapMessage.setString("customerName",                                instruction.getCustomerName());           mapMessage.setString("date", instruction.getDate());           mapMessage.setString("checkNumber",                                instruction.getCheckNumber());           mapMessage.setString("payeeName", instruction.getPayeeName());           mapMessage.setDouble("amount", instruction.getAmount()) ;           mapMessage.setString("action", instruction.getAction());           mapMessage.setJMSCorrelationID(instruction.getCustomerBank()) ; 

Message Priorities and Expiration

When there are several messages awaiting delivery at the receiver end, it is useful if there is a mechanism available that specifies which messages are more important than the others. This way the highest prioritized messages can be moved to the top of the FIFO list. While there are circumstances when this is desirable, there are circumstances when keeping the FIFO list flowing is more preferable.

An integer value is used to set the JMSPriority property field in the message header. JMS defines a ten-level priority value. Messages with priorities from 0 to 4 are normal priority messages. However, messages with priorities set from 5-9 should be expedited. As is illustrated in the code snippet below, message priorities can be assigned when the message is sent as follows:

     public static final int PRIORITY = 2;     public static final int TIMETOLIVE = 360000;     sender.send(textMessage, DeliveryMode.PERSISTENT, PRIORITY, TIMETOLIVE); 

The TIMETOLIVE integer value is added to the GMT time at the client when the message is sent to determine the JMSExpiration date and time of the message. If the TIMETOLIVE is 0, the expiration time is also 0, an indication that the message is intended never to expire. Servers should therefore all have the correct time, and have some sort of mechanism to synchronize their clocks. If time-to-live is quite short, then clock jitter and network latency become problems.

The time-to-live feature ensures that the message is eventually delivered, but can result in out-of-date deliveries when queues are not purged. The JMS specification states that even though clients should not receive messages that have expired, there is no guarantee that this will not happen:

           // Send the message           if (instruction.getAction(). equals(Constants.STOP_PAYMENT)) {             // If it's a Stop Payment, send message of highest priority             sender.send(mapMessage, DeliveryMode.PERSISTENT,                         Constants.HIGHEST_PRIORITY_MESSAGE,   // value of 9             Message. DEFAULT_TIME_TO_LIVE);           } else {             mapMessage.setString("routingNumber",                                  instruction.getRoutingNumber());             mapMessage.setString("payeeAccount",                                  instruction.getPayeeAccount());             // If it's a Fund Transfer, send message of normal priority             sender.send(mapMessage, DeliveryMode.PERSISTENT,                         Message.DEFAULT_PRIORITY,                         Message.DEFAULT_TIME_TO_LIVE);           }           // Commit the transaction           transactSession.commit();           System.out.println(instruction.getAction() + " for Check No: "                              + instruction.getCheckNumber()                              + " message, sent for "                              + instruction.getCustomerBank()                              + " through the Queue "                              + queue.getQueueName());         } catch (Exception e) {           transactSession.rollback();           System.out.println(instruction.getAction() + " for Check No: "                              + instruction.getCheckNumber()                              + " message, could not be sent for "                              + instruction.getCustomerBank() ) ;           e.printStackTrace();           result = Constants.FAILED;         }         return result;       } 

Message Delivery Modes

As was discussed in earlier chapters, JMS supports two different modes of message delivery, DeliveryMode.NON_PERSISTENT and DeliveryMode.PERSISTENT:

  • The DeliveryMode.NON_PERSISTENT mode has the lowest overhead, as it does not require that a message be logged into the messaging server's persistent store. Therefore, any failure at the JMS provider end can cause the message to get lost. A provider must deliver this type of message at-most-once. This means, that a JMS server can lose the message, but it must not deliver it twice. For example, in an application that tracks the location of a radio beacon, it's acceptable to miss the occasional message.

  • The DeliveryMode.PERSISTENT mode instructs the JMS provider to take care to insure that a message is not lost in transit due to a fault at the JMS provider end. The JMS broker should deliver this type of message once-and-only-once. This means that any failure at the JMS broker end should not cause it to lose the message and the message should not be delivered twice. However, you should note that the use of this flag does not automatically guarantee that all messages are delivered to every eligible consumer. A good example of this is in an application that posts sell orders on a trading floor.

Request/Reply

When an application sends a message and expects to receive a message in return, request/reply messaging can be used. This is essentially standard synchronous messaging. This messaging model is often defined as a subset of one of the other two messaging models. JMS does not support request/reply messaging as a model, though it allows it in the context of the other two messaging models.

JMS Request/Reply

The browsePendingRequests() method below illustrates how to simulate blocking request/reply messaging in a MOM scenario. The requestor sends a message to the replier and remains blocked until it receives a reply from the other end. This demonstrates the use of the request() API and the QueueRequestor class found in the javax.jms package.

The browsePendingRequests() method sets up a requestor object if one does not already exist. The method then creates a temporary queue. The method creates a stream message as a request, specifies the temporary queue reference in the replyTo property of the message, sets the JMSCorrelationID property with the customer's bank routing number, populates the body, sends it off on the bank:accounts queue, and waits for a response from the other end. The program stays blocked until a response comes back. As soon as it receives a reply from the other end, it retrieves the message from the reply.

The figure below shows the Teller class requesting and receiving request/reply messages on the temporary queue:

click to expand

The JMSCorrelationID Field

JMS provides the JMSCorrelationID message property field so that any replier can reference the original message request. The JMSCorrelationID field can be set by the requestor using the message's setJMSCorrelationID() method. Similarly, repliers can access this header field using the message's getJMSCorrelationID() method as shown in the code sample below. A messaging client can use this field to link one message with the other. It is generally used to link a response message with its request message. This field can hold any of the following values:

  • A provider-specific message ID

  • An application-specific string

  • A provider-native byte[] value

As each message sent by a JMS provider is assigned a JMSMessageID, it is convenient to link messages through their IDs. All message ID values must start with the ID: prefix:

       private int browsePendingRequests(Instruction instruction) { int size = 0;         try {           // Create a requestor object if one does not exist           if (requestor == null) {             requestor = new QueueRequestor(session, queue);           }           // Create Stream Message           StreamMessage streamMessage = session.createStreamMessage();           // Create a Temporary Queue           TemporaryQueue temporaryQueue = session.createTemporaryQueue();           // Set message properties           streamMessage.setJMSReplyTo(temporaryQueue);           String name = instruction.getCustomerBank();           // Set message properties and message body           streamMessage.setJMSCorrelationID(name);           streamMessage.writeString(instruction.getAccountNumber() );           streamMessage.writeString(instruction.getAction());           // Request and receive a Object Message           ObjectMessage objectMessage =             (ObjectMessage) requestor.request(streamMessage);           // Get the JMSCorrelationID property           String correlationID = objectMessage.getJMSCorrelationID();           // Read the message body           Vector list = (Vector) objectMessage.getObject();           size = list.size();           System.out.println("Received a list of " + size                              + " messages from Accounting ...");           // Delete the Temporary Queue           temporaryQueue.delete();         } catch (Exception e) {           e.printStackTrace();         }         return size;       } 

Temporary Queues

Sessions are used to create temporary destinations for convenience. The scope of a temporary queue holds good for the entire connection. According to the JMS specification, "A temporary queue is a unique queue object created for the duration of a queue connection. It is a system defined queue that can only be consumed by the queue connection that created it". Therefore, by implication, its lifetime persists for the duration of the connection if it is not deleted before that. Any of the connection's sessions are allowed to create message consumers for them.

Temporary destinations can be used in conjunction with the JMSReplyTo message property as shown in the code below. The JMSReplyTo message property makes it possible for a requestor to create a temporary queue, and send a message to the replier with the queue reference set in the message using the JMSReplyTo property. The replier uses the reference and sends back the reply on that destination. It is therefore possible for the original requestor to get back a reply to the message, on the temporary queue that it had originally created:

         Queue temporaryQueue = (Queue) textMessage.getJMSReplyTo();         TextMessage reply = session.createTextMessage();         reply.setText("Re: " + request.getText());         replier.send(temporaryQueue, reply); 

The temporary queue here is used to service requests. Each temporary destination is unique and cannot be copied. Since temporary destinations may allocate resources outside the JVM, they should be deleted if they are no longer needed. Even if they are not explicitly deleted, they will however, be killed when a connection is closed. Another way they can be deleted is during garbage collection.

The ExceptionHandler Class

The ExceptionHandler class is the handler installed with the Teller class to take care of un-handled JMS errors.

Installing Exception Handers

The Teller class above sets an exception listener. Just like message listeners, exception listeners have a callback mechanism. It has an onException() method that is called by the provider with the JMSException passed in. The exception listener is required to implement the onException() method of the ExceptionListener interface. The provider invokes this method when a JMSException that has nowhere else to go is thrown on the connection with the JMS server. Note that the exception listener is registered with the provider through the Connection object by a call to its setExceptionListener() method.

When a JMS provider detects a problem with a connection, it will inform the connection's exception listener if one has been registered. The JMSException object that is passed to the onException() method contains a description of the problem thus allowing the client to be asynchronously notified of the problem.

The connection serializes executions of its exception listener. All exceptions delivered to the exception listener are exceptions that have no other place to go to be reported. According to the JMS specification, if an exception is thrown on a JMS call it must not be delivered to an exception listener by default. In essence, an exception listener is not for the purpose of monitoring all exceptions thrown by a connection - only for those that aren't handled elsewhere.

The class ExceptionHandler below, implements the onException() callback method:

       class ExceptionHandler implements ExceptionListener {         public void onException(JMSException exception) {           exception.printStackTrace();          }       }     } 

The TellerPanel Class

Now that we have the Teller class ready, we need to write a GUI component that will allow the user to interact with our system. The GUI component that we're going to write will be generic enough that it could be used both as an applet and a standalone Java application.

Since the Applet class extends the Panel class, we will create our TellerPanel class to extend Applet. That way we can use the same front-end component both as an applet and as a standalone Java application:

     package ptp.onlinebanking;     import java.awt.*;     import java.awt.event.*;     import java.applet.*;     import java.util.*;     import java.text.*;     public class TellerPanel extends Applet implements ItemListener,             ActionListener {       Teller teller;       String customerBankNumber;       String customerAccountNumber;       String customerUserName;       Label customerBankField;       Label customerUserField;       Label customerAccountField;       Label dateFieldLabel;       long orderNumber;       Label actionLabel;       Label checkLabel;       Label routingLabel;       Label accountLabel;       Label nameLabel;       Label amountLabel;       Choice actionChoice;       TextField checkNumberField;       Choice routingNumberChoice;       TextField accountField;       TextField nameField;       TextField amountField;       Button submitButton;       Button closeButton;       Instruction instruction; 

The getLoginInfo() method is used to retrieve information about a user's name, their password, the bank that they want to connect to, their account number with the bank, etc. To do this it uses the services of the Login class, which we will see later:

       void getLoginInfo() {         Login loginFrame = new Login(this);         loginFrame.setSize(250, 250);         loginFrame.setLocation(300, 200);         loginFrame.pack();         loginFrame.show();       } 

The init() method is used to create a new instance of the Teller class and also layout the GUI for our application:

       public void init() {         teller = new Teller (Constants.PROVIDER_URL, Constants.USER_NAME,                              Constants.PASSWORD);         GridLayout layout = new GridLayout(0, 2, 10, 10);         layout.setRows(10);         this.setLayout(layout);         customerBankField = new Label ("Customer Bank: ");         customerUserField = new Label ("Customer Name: ");         customerAccountField = new Label ("Account Number");         dateFieldLabel = new Label ("Date :");         actionLabel = new Label ("Select Action to be performed");         checkLabel = new Label ("Enter Check Number");         routingLabel = new Label ("Select Payee Routing Number");         accountLabel = new Label ("Enter Payee Account Number");         nameLabel = new Label ("Enter Payee's Name");         amountLabel = new Label ("Enter amount in US Dollars");         actionChoice = new Choice();         actionChoice.addItem (Constants.TRANSFER_FUNDS);         actionChoice.addItem (Constants.STOP_PAYMENT);         actionChoice. addItem (Constants. BROWSE_PENDING_REQUESTS);         checkNumberField =           new TextField ((new Long(++orderNumber)).toString());         routingNumberChoice = new Choice();         routingNumberChoice.addItem (Constants.ECOMMWARE_BANK_NO);         routingNumberChoice.addItem (Constants.HERITAGE_BANK_NO);         routingNumberChoice.addItem (Constants.RAJ_BANK_NO);         accountField = new TextField ("S200089820");         nameField = new TextField ("Gopalan");         amountField = new TextField ("1234567.89");         submitButton = new Button ("Submit");         closeButton = new Button ("Close Connections");         add(customerUserField);         add(customerBankField);         add(customerAccountField);         add(dateFieldLabel);         add(actionLabel);         add(actionChoice);         add(checkLabel);         add(checkNumberField);         add(routingLabel);         add(routingNumberChoice);         add(accountLabel);         add(accountField);         add(nameLabel);         add(nameField);         add(amountLabel);         add(amountField);         add(submitButton);         add(closeButton);         actionChoice.addItemListener(this);         routingNumberChoice.addItemListener(this);         submitButton.addActionListener(this);         closeButton.addActionListener(this);         disableCheckField();         this.setEnabled(false);       } 

The getDate() method retrieves the current date in a displayable format to display the current date on screen:

       String getDate() {         Date date = new GregorianCalendar().getTime();         String time = new SimpleDateFormat ("EEE, MMM dd, yyyy"). format(date);         return time;       } 

The updateFields() method is invoked by the Login class with the customer's name, account number and their bank's routing number:

       synchronized void updateFields (String bank, String name,                                       String account) {         customerBankNumber = bank;         customerAccountNumber = account;         customerUserName = name;         customerBankField.setText ("Customer Bank: " + customerBankNumber);         customerUserField.setText ("Customer Name: " + customerUserName);         customerAccountField.setText ("Account Number: "                                       + customerAccountNumber);         dateFieldLabel.setText (getDate());         this.setEnabled (true);         invalidate();       } 

The start() method is called to retrieve initial information from the user and also initiate the Teller object to create its connections. When running as an applet, the browser invokes this method initially to start off the applet:

       public void start() {         getLoginInfo();         teller.createConnections();       } 

The stop() method actually makes the teller object close any open connections that it has and invokes the object's closeConnections() method. When running as an applet, the browser invokes this method to stop the applet:

       public void stop() {         teller.closeConnections();       } 

The TellerPanel class implements the ItemListener interface and its itemStateChanged() method so that whenever the user changes the options between transferring funds, stopping payments, and browsing queues, the system responds by recording the choice made and disabling or enabling the appropriate fields:

       public void itemStateChanged(ItemEvent itemEvent) {         String choice = (String) itemEvent.getItem();         if ((choice.equalsIgnoreCase(Constants.STOP_PAYMENT) == true)                 || (actionChoice.getSelectedItem()                   .equalsIgnoreCase (Constants.STOP_PAYMENT))) {           enableCheckField();         } else {           disableCheckField();         }       } 

The TellerPanel class implements the ActionListener interface and its actionPerformed() method so that whenever the user clicks on the Submit button, the application responds by invoking the teller object's execute() method with the instruction object as a parameter populated with instructions given out by the user. Similarly, when the Close Connections button is pressed, the system closes all connections and exits the program. The call to System.exit() has no effect when running as an applet inside a browser:

       public void actionPerformed(ActionEvent actionEvent) {         Instruction instruction = null ;         if (actionEvent.getSource() == submitButton) {           instruction = new Instruction();           instruction.setCustomerBank(customerBankNumber);           instruction.setAccountNumber(customerAccountNumber);           instruction.setCustomerName(customerUserName);           instruction.setDate(dateFieldLabel.getText());           instruction.setCheckNumber (customerBankNumber + ":"                                       + customerAccountNumber + ":"                                       + checkNumberField.getText());           instruction.setAction(actionChoice.getSelectedItem());           instruction             .setAmount ((new Double(amountField.getText())).doubleValue());           instruction.setPayeeName(nameField.getText());           if (actionChoice.getSelectedItem()                   .equalsIgnoreCase(Constants.TRANSFER_FUNDS) == true) {             instruction               .setRoutingNumber(routingNumberChoice.getSelectedItem());             instruction.setPayeeAccount(accountField.getText());             checkNumberField               .setText((new Long(++orderNumber)).toString());           }           instruction.print();           teller.execute(instruction);         }         if (actionEvent.getSource() == closeButton) {           stop();           System.exit(0);         }       } 

Check numbers are automatically generated by the system instead of requiring the user to enter them when transferring funds. The disableCheckField() method is used to disable the check field so that the user does not edit the check values:

       void disableCheckField() {         routingLabel.setVisible(true);         accountLabel.setVisible(true);         routingNumberChoice.setVisible(true);         accountField.setVisible(true);         checkNumberField.setText((new Long(orderNumber)).toString());         checkNumberField.setEnabled(false);         invalidate();       } 

While performing stop payment operations, the check number has to be keyed in to identify the check. Therefore we use the enableCheckField() method to enable this field:

       void enableCheckField() {         routingLabel.setVisible(false);         accountLabel.setVisible(false);         routingNumberChoice.setVisible(false);         accountField.setVisible(false);         checkNumberField.setEnabled(true);         invalidate();       } 

The main() method is used when trying to run TellerPanel as a standalone Java application:

       public static void main(String[] args) {         TellerPanel panel = new TellerPanel();         panel.init();         Frame frame = new Frame("Bank Teller");         frame.add(panel, BorderLayout.CENTER);         frame.setSize(250, 250);         frame.setLocation(300, 200);         frame.pack();         frame.show();         panel.start();       }     } 

The Login Class

The TellerPanel class for authorization and authentication uses the Login class. The Login class is also used to obtain information such as the client's name, the bank they're connecting to, their account number, and other such information and update the TellerPanel with it.

     package ptp.onlinebanking;     import java.awt.*;     import java.awt.event.*;     public class Login extends Frame implements ActionListener {       TellerPanel parent;       Choice routingNumberChoice;       TextField accountField;       TextField nameField;       TextField passwordField;       Button submitButton;       public Login(TellerPanel panel) {         super("Bank Teller Login Screen ...");         parent = panel;         Label bankLabel = new Label("Select Bank Number");         Label accountLabel = new Label("Enter Account Number");         Label customerLabel = new Label("Enter Customer User name");         Label passwordLabel = new Label("Enter Password");         routingNumberChoice = new Choice();         routingNumberChoice.addItem (Constants.ECOMMWARE_BANK_NO);         routingNumberChoice.addItem (Constants.HERITAGE_BANK_NO);         routingNumberChoice.addItem (Constants.RAJ_BANK_NO);         accountField = new TextField("C10003365");         nameField = new TextField("athul");         passwordField = new TextField("raj");         passwordField.setEchoChar('*');         submitButton = new Button("Submit");         GridLayout layout = new GridLayout(0, 2, 10, 10);         layout.setRows(5);         this.setLayout(layout);         add(bankLabel);         add(routingNumberChoice);         add(accountLabel);         add(accountField);         add(customerLabel);         add(nameField);         add(passwordLabel);         add(passwordField);         add(submitButton);         submitButton.addActionListener(this);       }       public void actionPerformed(ActionEvent actionEvent) {         if (actionEvent.getSource() == submitButton) { 

Update the TellerPanel with the information obtained from the user through the login pop-up box:

           parent.updateFields(routingNumberChoice.getSelectedItem(),                               nameField.getText(),                               accountField.getText());           setVisible(false);           dispose();         }       }     } 

The screenshot below shows the Login class while in execution:

click to expand

Using the Teller Module

Before doing anything else, make sure the FioranoMQ server (or other messaging server) is up and is ready to send and receive messages. Realize that if the server is not up, we may not be able to either send or receive messages. We can now compile all the files and run the teller module.

Important 

For FioranoMQ users, the CLASSPATH should contain fmprtl.zip, and jndi.jar. These files can be found in the %FMP_DIR%\lib directory, where %FMP_DIR% refers to directory file path where you have installed your FioranoMQ Server.

The screenshot below shows the command prompt console while compiling and executing the teller module:

click to expand

The login screen is the first GUI frame that appears as the teller panel is executed. You cannot do anything without clearing this screen. Therefore, enter any values in the boxes or use the defaults and press the Submit button.

As soon as the login screen is cleared, the teller panel frame will be enabled. You can change the values and click on the Submit button to perform the operation or click on the Close Connections button to close all open resources and exit the program.

The screenshot below shows the TellerPanel class while executing the teller module:

click to expand

Exercise the Transfer Funds and the Stop Payment choices by entering different values for the different fields. While Stop Payment is selected, you will be able to change the check number, but this will be disabled at other times. All the operations you perform on the GUI can be seen executing by looking at the information printed on the console box.

However, please do not execute the Browse Pending Requests option as the sender will be blocked on the request call and the receiver, the accounts department module, is only developed in the next section.

The AccountsDepartment Class

The AccountsDepartment class is used to receive account information on the bank:accounts queue and send out instructions on either the bank:clearing_house queue, or any other temporary queue on which the requestor wants it to reply:

     package ptp.onlinebanking;     import javax.jms.*;     import common.*;     import java.util.*;     public class AccountsDepartment {       private String bankNumber;       private QueueConnectionFactory factory;       private QueueConnection connection;       private Queue requestQueue, orderQueue;       private QueueSession session, transactionalSession;       private QueueReceiver receiver, queueReceiver       private QueueSender sender;       private String hostName;       private String userID;       private String password;       private DisplayHandler parent;       static final String CONTEXT_FACTORY = Constants.FACTORY;       static final String CONNECTION_FACTORY = Constants.QCF;       static final String REQUEST_RECEIVER_QUEUE = Constants.ACCOUNTS_QUEUE;       static final String ORDER_RECEIVER_QUEUE = Constants.CLEARING_QUEUE; 

There are a couple of constructors for the AccountsDepartment class. One of them is the default constructor that does not take any parameters. The other constructor takes in multiple parameters. In these are the hostname or IP:Port of the remote running JMS server to connect to, and the username and password of a valid user for the JMS server:

       public AccountsDepartment(String host, String ID, String passwd) {         this.hostName = host;         this.userID = ID;         this.password = passwd;       }       private AccountsDepartment() {         this.hostName = Constants.PROVIDER_URL;         this.userID = Constants.USER_NAME;         this.password = Constants.PASSWORD;       } 

The initialize() method demonstrates how to connect to JMS messaging servers that run remotely. Typically, this is done by specifying the hostname or the IP:Port address combination on the JNDI server with the PROVIDER_URL property. The JNDIService.init(CONTEXT_FACTORY, hostName) method of our common.JNDIService class does this for us. If you want to examine the relevant code for the JNDIService class, go to Chapter 3 where it was first introduced:

       public void initialize() {         try {           JNDIService.init(CONTEXT_FACTORY, hostName);           factory = (QueueConnectionFactory) JNDIService               .lookup(CONNECTION_FACTORY);           requestQueue =             (Queue) JNDIService.lookup(REQUEST_RECEIVER_QUEUE);           orderQueue = (Queue) JNDIService.lookup(ORDER_RECEIVER_QUEUE);         } catch (Exception e) {           e.printStackTrace();         }       } 

The createConnections() method creates and starts connections and sessions.

Concurrent Delivery of Message

JMS clients that require concurrent delivery of messages can use multiple sessions. This implies that each session's listener thread runs concurrently. In effect, while the listener on one session is executing, a listener on another session may also be executing concurrently.

In the following createConnections() method, a couple of sessions - session and transactionalSession - are created to illustrate concurrent delivery of messages. In this example, all the listeners created on both the sessions will run concurrently. Each of them may be receiving messages at the same time without affecting the operation of the other:

       public void createConnections() {         try {           connection = factory.createQueueConnection();           connection.setClientID(bankNumber);           session =             connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);           transactionalSession = connection.createQueueSession(true,                   Session.AUTO_ACKNOWLEDGE);         } catch (Exception e) {           e.printStackTrace();         }       } 

The closeConnections() method closes senders, receivers, sessions, and the connection, thus releasing all the resources:

       public void closeConnections() {         try {           if (sender != null) {             sender.close();           }           if (receiver != null) {             receiver.close();           }           if (queueReceiver != null) {             queueReceiver.close();           }           if (session != null) {             session.close();           }           if (transactionalSession != null) {             transactionalSession.close();           }           if (connection != null) {             connection.close();           }         } catch (Exception e) {           e.printStackTrace();         }       } 

Message Selectors

While most messaging applications would prefer to get every message sent from a queue, it is possible to reduce the flow of irrelevant messages to a message receiver by using message filters. The JMS specification defines the syntax to allow a receiver to filter and categorize messages by the message header and properties based on specific criteria.

The syntax for creating this filter is based on a subset of the SQL-92 conditional expressions. This may imply that there is an underlying data store that uses SQL-92, but the JMS specification leaves the implementation to JMS vendors. Most vendors use a data store to implement PERSISTENT functionality for messages, while using a temporary cache to store NON_PERSISTENT messages, and it is therefore easy for the vendors to implement a query based retrieval of the messages based on the SQL-92 syntax.

As discussed in Chapter 3, these message filter statements set in the message selector only operate on the content found in the property fields of a message and not on the body of the message. The message receivers to a queue specify the filter criteria, and receivers can use some mechanism to discover what queue attributes are sent (in other words, the names of the properties).

Since the JMS server handles all this work, the application and its communication links are more efficient and consume less bandwidth. It is important to remember that these message selectors do not access the body of the message. They only operate on the content found in the property fields of a message. Also, when using message selectors in JMS, please be aware that although SQL supports arithmetic operations, JMS message selectors do not. Similarly, the JMS specification for message selectors does not support SQL comments either.

For example, the following selector may be placed on a report queue to pull out details of an employee:

     designation LIKE 'CEO' AND yearlySalary > 10000 

The fields that you see, like designation, and yearlySalary, are user-defined properties set in the message's property fields by the message sender.

The above message selector specifies that the receiver will only process messages that satisfy the following conditions:

  • The designation attribute should be the string CEO

  • The yearlySalary should be greater than 10000

As was mentioned earlier, the syntax of the message selector string is based on the SELECT statement of SQL-92 conditional expression syntax.

It is very simple to install a message selector to a queue receiver so that you can receive only the messages that you're looking for. Assuming that we have a valid reference to a queue session, a queue and a string that contains a SQL-92 based conditional expression, installing a message selector while creating the queue receiver is typically done as follows:

     String messageSelector = "designation LIKE 'CEO' AND yearlySalary > 10000";     QueueReceiver receiver = session.createReceiver(queue, messageSelector); 

It is also easy for the message sender to set the property fields of the message before sending it off as shown in the following code snippet:

     TextMessage textMessage = session.createTextMessage();     textMessage.setStringProperty ("employeeName", "Athul S. Raj");     textMessage.setStringProperty ("employeeNumber", "E000000001");     textMessage.setStringProperty ("designation", "CEO");     textMessage.setIntProperty ("yearlySalary", 987654321);     sender.send(textMessage,                 DeliveryMode.PERSISTENT,                 Message.DEFAULT_PRIORITY,                 Message.DEFAULT_TIME_TO_LIVE); 

The piece of code above illustrates how a message can be set up to hold a set of key-value pairs by the message sender. For example, while employeeName, employeeNumber, and designation are read in as strings, yearlySalary can be read in as an integer. The programmer can refer to these fields by their names such as employeeName, employeeNumber, etc. As the programmer is not required to explicitly format the message, this simplifies code development a lot. Also, the receiving program can access these fields randomly using the name of the appropriate field.

A message selector string can be composed of the following:

  • Literals
    These can be strings or numeric or of Boolean type.

  • Identifiers
    These can be message header field references, JMSX defined properties, JMS provider-specific properties, and application-specific property names.

  • Operators
    These can be logical, comparison, or arithmetic operators.

  • Parenthesis
    These control the evaluation of an expression and can be nested.

  • Expressions
    These can be selector, arithmetic, or conditional expressions.

  • Comparison tests
    These can be of type IN, LIKE, or null.

  • White space
    Horizontals tabs, form feeds, and line terminators are evaluated in the same way as in Java.

Note 

Please refer to the JMS specification documentation or Appendix B for more on this.

According to the JMS specification, JMS messaging server providers are required to verify the syntactic correctness of a message selector at the time that it is presented. If the selector presented were wrong a JMS InvalidSelectorException would be thrown.

In the following process() method, a message selector is installed while creating all the queue receivers. The message selector string is as follows:

     "JMSCorrelationID = '"+ bankNumber + "'" 

This ensures that these receivers will process only messages with the JMSCorrelationID corresponding to the bank with which the accounts department is associated.

The process() method calls the createConnections() method to establish connections, and then creates the receivers and message listeners for both the requestQueue (bank:accounts) and the orderQueue (bank:clearing_house). It also sets up an exception listener for the AccountsDepartment class:

       public void process() {         try {           createConnections();           // Create a Message Selector           String messageSelector = "JMSCorrelationID = "' + bankNumber + ""';           // Create a Queue Receiver           queueReceiver =             transactionalSession.createReceiver(requestQueue, messageSelector);           queueReceiver.setMessageListener(new RequestMessageListener(this,                   requestQueue, transactionalSession));           receiver = transactionalSession.createReceiver(orderQueue,                   messageSelector);           receiver.setMessageListener(new TransferFundsListener(this,                   orderQueue, transactionalSession));           connection.setExceptionListener(new ExceptionHandler());           connection.start();           System.out.println("Bank No. :" + bankNumber                              + " up and ready for eCommerce ...");         } catch (Exception e) {           e.printStackTrace();         }       } 

The sendTransferFundsMessage() method is used to send a bytes message containing a TRANSFER_FUNDS instruction over to the bank:clearing_house queue transactionally.

The figure below shows the sendTransferFundsMessage() method sending the TRANSFER_FUNDS instruction to the bank:clearing_house queue:

click to expand

This method is invoked by the RequestMessageListener class's onMessage() method, which is installed as a message handler for messages arriving on the bank:accounts queue. We will be looking at the RequestMessageListener class later:

       boolean sendTransferFundsMessage(Instruction instruction)               throws Exception {         boolean result = true;         if (null == instruction) {           return false;         }         try {           // Create a Sender if one does not exist           if (sender == null) {             sender = transactionalSession.createSender(orderQueue);           }           // Create a Bytes Message           BytesMessage bytesMessage =             transactionalSession.createBytesMessage();           // Set message properties           bytesMessage.setStringProperty("customerBank",                                          instruction.getCustomerBank());           bytesMessage.setStringProperty("accountNumber",                                          instruction.getAccountNumber());           bytesMessage.setStringProperty("customerName",                                          instruction.getCustomerName());           bytesMessage.setStringProperty("date", instruction.getDate());           bytesMessage.setStringProperty("checkNumber",                                          instruction.getCheckNumber());           bytesMessage.setStringProperty("payeeName",                                          instruction.getPayeeName());           bytesMessage.setDoubleProperty("amount",                                          instruction.getAmount());           bytesMessage.setStringProperty("action",                                          instruction.getAction());           bytesMessage.setStringProperty("routingNumber",                                          instruction.getRoutingNumber());           bytesMessage.setStringProperty("payeeAccount",                                          instruction.getPayeeAccount());           bytesMessage             .setJMSCorrelationID(instruction.getRoutingNumber());           // Send the message           sender.send(bytesMessage, DeliveryMode.PERSISTENT,                       Message.DEFAULT_PRIORITY,                       Message.DEFAULT_TIME_TO_LIVE);           String display = instruction.getAction() + " for Check No: "                            + instruction.getCheckNumber()                            + " message, sent for "                            + instruction.getRoutingNumber()                            + " through the Queue "                            + orderQueue.getQueueName();           System.out.println(display);           parent.appendData(display);         } catch (Exception e) {           String display = instruction.getAction() + " for Check No: "                            + instruction.getCheckNumber()                            + " message, could not be sent for "                            + instruction.getRoutingNumber();           System.out.println(display);           parent.appendData(display);           e.printStackTrace();           result = false;           throw e;         }         return result;       } 

Browsing Queue Messages

The QueueBrowser interface defines methods to help client programs browse messages in a queue without destroying or removing them. This is achieved by retrieving a cursor in the queue at the current, location, forward or backward to the currently adjacent message. As queues are loaded and unloaded very quickly, browsing is really useful in assessing queue sizes and rates of message growth in a queue. While browsing queues using a queue browser, instead of retrieving actual data, an enumeration method is used to return an integer count of the messages in the queue:

       Vector browseQueueMessages(String accountNumber) {         QueueBrowser browser = null;         Enumeration enum = null;         int count = 0;         Instruction instruction = null;         Vector browserList = new Vector();         try {           browser = session.createBrowser (orderQueue);           enum = browser.getEnumeration();           while (enum.hasMoreElements() == true) {             System.out.println("Browser getting Message No. " + ++count);             BytesMessage msg = (BytesMessage) enum.nextElement();             if (msg != null) {               instruction =                 new Instruction(msg.getStringProperty("customerBank"),                                 msg.getStringProperty("accountNumber"),                                 msg.getStringProperty("customerName"),                                 msg.getStringProperty("date"),                                 msg.getStringProperty("checkNumber"),                                 msg.getStringProperty("routingNumber"),                                 msg.getStringProperty("payeeAccount"),                                 msg.getStringProperty("payeeName"),                                 msg.getDoubleProperty("amount"),                                 msg.getStringProperty("action"));               if (instruction.getCustomerBank().equals(bankNumber)                       && instruction.getAccountNumber()                       .equals(accountNumber)) {                 instruction.print();                 browserList.addElement(instruction);               }             }           }           if (browser != null) {             browser.close();           }         } catch (Exception e) {           e.printStackTrace();         }         return browserList;       } 

Other methods like setRoutingNumber() to set the bank associated with this AccountsDepartment class, setParent() to identify the creator of this object, and appendData() that's used to update information in the creator of this class are also defined in the following code snippet:

       public void setRoutingNumber(String routingNumber) {         bankNumber = routingNumber;       }       public void setParent(DisplayHandler applet) {         parent = applet;       }       public void appendData(String data) {         if (parent != null) {           parent.appendData(data);         }       }       public static void main (String [] args) {         AccountsDepartment accounting = new AccountsDepartment();         accounting.setRoutingNumber(args[0]);         accounting.initialize();         accounting.createConnections();         accounting.process();         System.out.println("Accounts Department of Bank " + args[0]                            + " ... Ready to receive messages.");       } 

The ExceptionHandler Class

The AccountsDepartment class also sets an exception listener. Note that the exception listener is registered with the provider through the connection object by a call to its setExceptionListener() method:

       class ExceptionHandler implements ExceptionListener {         public void onException(JMSException exception) {           exception.printStackTrace();         }       }     } 

The RequestMessageListener Class

The RequestMessageListener class implements the javax.jms.MessageListener interface. This is the first requirement for any class aspiring to become an asynchronous message consumer. By implementing the MessageListener interface, the class is forced to provide an implementation for the onMessage() method. This method is the registered callback method that the provider invokes, when there is a new message available for consumption at the queue.

The onMessage() method contains the code for processing the message. Also note that the message listener is registered with the provider through the Receiver object as soon as it is created by a call to its setMessageListener() method using the following code snippet:

         queueReceiver = transactionalSession.createReceiver(requestQueue,                                                             messageSelector);         queueReceiver.setMessageListener(new RequestMessageListener(this,                                           requestQueue, transactionalSession)); 

The RequestMessageListener class serves as the message handler for receiving messages from the bank:accounts queue. Also recall that since there is a message selector installed, only messages intended for this receiver will reach this receiver:

     package ptp.onlinebanking;     import javax.jms.*;     import java.util.*;     class RequestMessageListener implements MessageListener {       private QueueSender replier;       AccountsDepartment parent;       Queue queue;       QueueSession transactionalSession;       public RequestMessageListener(AccountsDepartment accounting,                                     Queue requestQueue,                                     QueueSession queueSession) {         this.parent = accounting;         this.transactionalSession = queueSession;         this.queue = requestQueue;       } 

The onMessage() method of the RequestMessageListener class receives all messages intended for a particular bank (as specified in the message selector) from the bank:accounts queue. Depending on the message type of the action field in the instruction received, the accounts department either sends it off to the bank:clearing_house queue, or replies to the message.

The onMessage() method first checks to see if the message received is a stream message. If it is, it invokes the replyToRequest() method. It then checks to see if the message received is a map message. If that is the case, it invokes the processTransactionalMessage() method.

There is really no need to use multiple message types in this application. However they are included to demonstrate their usage:

       public void onMessage(Message message) {         try {           if (message instanceof StreamMessage) {             replyToRequest((StreamMessage) message);           }           if (message instanceof MapMessage) {             processTransactionalMessage((MapMessage) message);           }         } catch (Exception e) {           e.printStackTrace();         }       } 

The figure opposite shows the accounts department receiving a TRANSFER_FUNDS message and sending it off to the bank:clearing_house queue transactionally. Note that the receive and send operations are part of one transactional grouping:

click to expand

The processTransactionalMessage() method is used to process both the TRANSFER_FUNDS and the STOP_PAYMENT instructions. There are two options here. If the message contained a STOP_PAYMENT instruction, then the accounts department would process it by itself. On the other hand, if the message contained a TRANSFER_FUNDS instruction, the class would have to invoke its creator's sendTransferFundsMessage() method to send off the instruction to the bank:clearing_house queue. The processTransactionalMessage() method is also used to commit or rollback the transaction for the transactional group:

       void processTransactionalMessage(MapMessage message)               throws Exception {         try {           Instruction instruction = receiveNotification(message);           if ((instruction != null)                   && (instruction.getAction()                     .equals(Constants.TRANSFER_FUNDS))) {             // Send a message to Clearing house to execute this             parent.sendTransferFundsMessage(instruction);           }           if ((instruction != null)                   && (instruction.getAction()                     .equals(Constants.STOP_PAYMENT))) {             // It's a STOP_PAYMENT Notification. Process it here ...           }           // Commit the transaction           transactionalSession.commit();         } catch (Exception e) {           // Rollback the transaction           transactionalSession.rollback();           e.printStackTrace();         }       } 

The receiveNotification() method receives the map message and informs everyone concerned that a TRANSFER_FUNDS or STOP_PAYMENT instruction has arrived:

       Instruction receiveNotification(MapMessage message)               throws Exception {         Instruction instruction = null;         try {           instruction =             new Instruction(message.getString("customerBank"),                             message.getString("accountNumber"),                             message.getString("customerName"),                             message.getString("date"),                             message.getString("checkNumber"),                             message.getString("routingNumber"),                             message.getString("payeeAccount"),                             message.getString("payeeName"),                             message.getDouble("amount"),                             message.getString("action"));           String display = "Received " + instruction.getAction()                            + " request for Check No. :"                            + instruction.getCheckNumber() + " at bank "                            + instruction.getCustomerBank() + " on "                            + queue.getQueueName();           parent.appendData(display);           System.out.println(display);         } catch (Exception e) {           instruction = null;           e.printStackTrace();           throw e;         }         return instruction;       } 

If the message received at the onMessage() method is a stream message, the message handler invokes the replyToRequest() method.

The figure below shows the replyToRequest() method replying on a temporary queue as a transactional group:

click to expand

The replyToRequest() method informs everyone of the Browse Pending Instructions message, and then invokes its creator's browseQueueMessages() method and retrieves a list of pending messages from the bank:clearing_house queue. It creates an object message and populates its body with the Vector object obtained. It then creates a sender on the queue that it was supposed to use (based on the reference obtained from the ReplyTo property of the request message) and sends off the message on the specified queue. It then commits the transaction.

If something goes wrong in the process, the transaction is rolled-back and the system goes back to the state it was in before receiving the message. After everything is over, the sender object is closed and resources are released:

       void replyToRequest(StreamMessage streamMessage)               throws JMSException {         try {           StreamMessage request = (StreamMessage) streamMessage;           String correlationID = request.getJMSCorrelationID();           // When a message is received, inform everyone about it           String display = "Re: Bank No. :" + correlationID;           parent.appendData(display);           System.out.println(display);           // Retrieve a temporary Queue and send back a reply to           // the message, so that the original requestor receives           // the reply.           Queue temporaryQueue = (Queue) streamMessage.getJMSReplyTo();           ObjectMessage reply =             transactionalSession.createObjectMessage();           reply.setJMSCorrelationID(correlationID);           String accountNo = streamMessage.readString();           String command = streamMessage.readString();           Vector list = null;           if (command.equals(Constants.BROWSE_PENDING_REQUESTS)) {             list = parent.browseQueueMessages(accountNo);           }           reply.setObject(list);           if (replier == null) {             replier = transactionalSession.createSender(queue);           }           // Send the reply to the temporary queue           replier.send(temporaryQueue, reply);           // commit the transaction           transactionalSession.commit();         } catch (Exception exception) {           // rollback the transaction           transactionalSession.rollback();           exception.printStackTrace();         }         if (replier != null) {           replier.close();           replier = null;         }       }     } 

The TransferFundsListener Class

The TransferFundsListener class implements the MessageListener interface and is the message listener installed for all messages received from the bank:clearing_house queue. This means that this handler handles all the TRANSFER_FUNDS messages transactionally.

The figure below shows the accounts department receiving TRANSFER_FUNDS messages transactionally:

click to expand

The onMessage() method invokes the receiveTransferFundsNotification() method, which in turn informs everyone of the TRANSFER_FUNDS instruction and processes it. This method also commits the transaction. If something were to go wrong in the processing, the transaction is rolled back and the system goes back to the state that it was before receiving the message:

     package ptp.onlinebanking;     import javax.jms.*;     class TransferFundsListener implements MessageListener {       AccountsDepartment parent;       Queue queue;       QueueSession transactionalSession;       public TransferFundsListener(AccountsDepartment accounting,                                    Queue orderQueue,                                    QueueSession queueSession) {         this.parent = accounting;         this.transactionalSession = queueSession;         this.queue = orderQueue;       }       public void onMessage(Message message) {         if (message instanceof BytesMessage) {           try {             receiveTransferFundsNotification((BytesMessage) message);           } catch (Exception e) {             e.printStackTrace();           }         }       }       void receiveTransferFundsNotification(BytesMessage msg)               throws JMSException {         try {           Instruction instruction = null;           instruction =             new Instruction(msg.getStringProperty("customerBank"),                             msg.getStringProperty("accountNumber"),                             msg.getStringProperty("customerName"),                             msg.getStringProperty("date"),                             msg.getStringProperty("checkNumber"),                             msg.getStringProperty("routingNumber"),                             msg.getStringProperty("payeeAccount"),                             msg.getStringProperty("payeeName"),                             msg.getDoubleProperty("amount"),                             msg.getStringProperty("action"));           transactionalSession.commit();           // Inform everyone about the transfer funds           String display = "Received " + instruction.getAction()                            + " request for Check No. :"                            + instruction.getCheckNumber() + " from bank"                            + instruction.getCustomerBank() + " on "                            + queue.getQueueName();           System.out.println(display);           parent.appendData(display);           // It's a Transfer Funds notification. Process it here ...         } catch (Exception e) {           transactionalSession.rollback();           e.printStackTrace();         }       }     } 

The DisplayHandler Interface

Any class that needs to get notified of changes happening in the AccountsDepartment class implements the DisplayHandler interface. The appendData() method is invoked by the AccountsDepartment class whenever any changes need to be communicated:

     package ptp.onlinebanking;     public interface DisplayHandler {       public void appendData (String data);     } 

The AccountingPanel Class

Now that we have the AccountsDepartment class ready, we need to write a GUI component that will allow the user to interact with our system. The GUI component that we're going to write will be generic enough that it could be used both as an applet and a standalone application.

Since Applet extends Panel, we create our AccountingPanel class to extend Applet. That way we can use the same front-end component both as an applet and as a standalone Java application:

     package ptp.onlinebanking;     import java.awt.*;     import java.awt.event.*;     import java.applet.*;     public class AccountingPanel extends Applet implements ItemListener,             ActionListener, DisplayHandler {       Choice routingNumberChoice;       TextArea displayArea;       Button quitButton;       AccountsDepartment accounting; 

The init() method is used to create a new instance of the AccountsDepartment class and also layout the GUI for our application:

       public void init() {         accounting = new AccountsDepartment(Constants.PROVIDER_URL,                                             Constants.USER_NAME,                                             Constants.PASSWORD);         accounting.setParent(this);         routingNumberChoice = new Choice();         routingNumberChoice.addItem(Constants.ECOMMWARE_BANK_NO);         routingNumberChoice.addItem(Constants.HERITAGE_BANK_NO);         routingNumberChoice.addItem(Constants.RAJ_BANK_NO);         displayArea = new TextArea(15, 50);         displayArea.setEnabled(false);         displayArea.setText("Before Proceeding, Set up the Receiver:\n"                             + "Select a Bank from the Choice above ...");         quitButton = new Button("Close Connections");         this.setLayout(new BorderLayout());         add(routingNumberChoice, BorderLayout.NORTH);         add(displayArea, BorderLayout.CENTER);         add(quitButton, BorderLayout.SOUTH);         routingNumberChoice.addItemListener(this);         quitButton.addActionListener(this);       } 

The appendData() callback method is implemented so that the AccountsDepartment can invoke this method to inform the AccountingPanel of any changes:

       public synchronized void appendData (String data) {         displayArea.append(data + "\n\r");       } 

The AccountingPanel class implements the ItemListener interface and its itemStateChanged() method so that whenever the user changes between different bank routing numbers, the system responds by recording the new choice and enabling the panel so that the application can be used:

       public void itemStateChanged(ItemEvent itemEvent) {         String choice = (String) itemEvent.getItem();         accounting.setRoutingNumber(choice);         accounting.initialize();         accounting.createConnections();         accounting.process();         routingNumberChoice.setEnabled(false);         displayArea.setText("");       } 

The AccountingPanel class implements the ActionListener interface and its actionPerformed() method so that whenever the user clicks on the Close Connections button, the system closes all connections and exits the program. The call to System.exit(0) has no effect when running as an applet inside a browser:

       public void actionPerformed(ActionEvent actionEvent) {         if (actionEvent.getSource() == quitButton) {           accounting.closeConnections();           System.exit(0);         }       } 

The main() method is used when running an accounting panel as a standalone Java application:

       public static void main(String[] args) {         AccountingPanel panel = new AccountingPanel();         panel.init();         Frame frame = new Frame("Bank Accounting Department");         frame.add(panel, BorderLayout.CENTER);         frame.setSize(250, 250);         frame.setLocation(300, 200);         frame.pack();         frame.show();       }     } 

Using the Accounts Department Module

Before doing anything else, make sure the FioranoMQ server (or other messaging server) is up and is ready to send and receive messages. Realize that if the server is not up, we may not be able to either send or receive messages. We can now compile all the files and run the Teller module.

Important 

For FioranoMQ users, the CLASSPATH should contain fmprtl.zip, and jndi.jar. These files can be found in the %FMP_DIR%\lib directory, where %FMP_DIR% refers to directory file path where you have installed your FioranoMQ Server.

The screenshot below shows the command prompt console while compiling and executing the accounts department module:

click to expand

The screenshot below shows the AccountingPanel class while executing the accounts department module:

click to expand

As soon as this frame comes up, select the bank that you want to connect to. If you do not make a choice, nothing will happen. Once the choice is made, the command prompt console will mention that the "Bank is up and ready for eCommerce ". Now if you execute the TellerPanel class and log onto the same bank, you will be able to perform all the operations including TRANSFER_FUNDS, STOP_PAYMENT, and BROWSE_PENDING_REQUESTS. When you click on the Close Connections button, all open connections for the AccountsDepartment class will be closed and the module will terminate execution.



Professional JMS
Professional JMS
ISBN: 1861004931
EAN: 2147483647
Year: 2000
Pages: 154

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net