The Duke's Bank Application
This chapter describes the Duke's Bank app, an online banking app. Duke's Bank has two clients: a J2EE app client used by administrators to manage customers and accounts, and a Web client used by customers to access account histories and perform transactions. The clients access the customer, account, and transaction information maintained in a database through enterprise beans. The Duke's Bank app demonstrates how all the component technologies--enterprise beans, J2EE app clients, and Web components--presented in this tutorial are put together to provide a simple but functional app.
Enterprise Beans
Web and J2EE app clients access only the session beans. Within the enterprise bean tier, the session beans are clients of the entity beans. On the back end of the app, the entity beans access the database tables that store the entity states.
The source code for these enterprise beans is in the j2eetutorial/bank/src/com/sun/ebank/ejb subdirectory.
Session Beans
The Duke's Bank app has three session beans: AccountControllerEJB, CustomerControllerEJB, and TxControllerEJB. (Tx stands for a business transaction, such as transferring funds.) These session beans provide a client's view of the app's business logic. Hidden from the clients are the server-side routines that implement the business logic, access databases, manage relationships, and perform error checking.
AccountControllerEJB
The business methods of the AccountControllerEJB session bean perform tasks that fall into the following categories: creating and removing entity beans, managing the account-customer relationship, and getting the account information.
The following methods create and remove entity beans:
- createAccount
- removeAccount
These methods of the AccountControllerEJB session bean call the create and remove methods of the AccountEJB entity bean. The createAccount and removeAccount methods throw app exceptions to indicate invalid method arguments. The createAccount method throws an IllegalAccountTypeException if the type argument is neither Checking, Savings, Credit, nor Money Market. The createAccount method also verifies that the specified customer exists by invoking the findByPrimaryKey method of the CustomerEJB entity bean. If the result of this verification is false, the createAccount method throws a CustomerNotFoundException.
The following methods manage the account-customer relationship:
- addCustomerToAccount
- removeCustomerFromAccount
The AccountEJB and CustomerEJB entity beans have a many-to-many relationship. A bank account may be jointly held by more than one customer, and a customer may have multiple accounts. Because the entity beans use bean-managed persistence, there are several ways to manage this relationship.
In the Duke's Bank app, the addCustomerToAccount and removeCustomerFromAccount methods of the AccountControllerEJB session bean manage the account-customer relationship. The addCustomerToAccount method, for example, starts by verifying that the customer exists. To create the relationship, the addCustomerToAccount method inserts a row into the customer_account_xref database table. In this cross-reference table, each row contains the customerId and accountId of the related entities. To remove a relationship, the removeCustomerFromAccount method deletes a row from the customer_account_xref table. If a client calls the removeAccount method, then all rows for the specified accountId are removed from the customer_account_xref table.
The following methods get the account information:
- getAccountsOfCustomer
- getDetails
The AccountControllerEJB session bean has two get methods. The getAccountsOfCustomer method returns all of the accounts of a given customer by invoking the findByCustomer method of the AccountEJB entity bean. Instead of implementing a get method for every instance variable, the AccountControllerEJB has a getDetails method that returns an object (AccountDetails) that encapsulates the entire state of an AccountEJB bean. Because it can invoke a single method to retrieve the entire state, the client avoids the overhead associated with multiple remote calls.
CustomerControllerEJB
Because it is the AccountControllerEJB bean that manages the customer-account relationship, CustomerControllerEJB is the simpler of these two session beans. A client creates a CustomerEJB entity bean by invoking the createCustomer method of the CustomerControllerEJB session bean. To remove a customer, the client calls the removeCustomer method, which not only invokes the remove method of CustomerEJB, but also deletes from the customer_account_xref table all rows that identify the customer.
The CustomerControllerEJB session bean has two methods that return multiple customers: getCustomersOfAccount and getCustomersOfLastName. These methods call the corresponding finder methods--findbyAccountId and findByLastName--of CustomerEJB.
TxControllerEJB
The TxControllerEJB session bean handles bank transactions. In addition to its get methods, getTxsOfAccount and getDetails, the TxControllerEJB bean has several methods that change the balances of the bank accounts:
- withdraw
- deposit
- makeCharge
- makePayment
- transferFunds
These methods access an AccountEJB entity bean to verify the account type and to set the new balance. The withdraw and deposit methods are for non-credit accounts, whereas the makeCharge and makePayment methods are for credit accounts. If the type method argument does not match the account, these methods throw an IllegalAccountTypeException. If a withdrawal were to result in a negative balance, then the withdraw method throws an InsufficientFundsException. If a credit charge attempts to exceed the account's credit line, the makeCharge method throws an InsufficientCreditException.
The transferFunds method also checks the account type and new balance; if necessary, it throws the same exceptions as the withdraw and makeCharge methods. The transferFunds method subtracts from the balance of one AccountEJB instance and adds the same amount to another instance. Because both of these steps must complete, the transferFunds method has a Required transaction attribute. If either step fails, the entire operation is rolled back and the balances remain unchanged.
Entity Beans
For each business entity represented in our simple bank, the Duke's Bank app has a matching entity bean:
- AccountEJB
- CustomerEJB
- TxEJB
The purpose of these beans is to provide an object view of these database tables: account, customer, and tx. For each column in a table, the corresponding entity bean has an instance variable. Because they use bean-managed persistence, the entity beans contain the SQL statements that access the tables. For example, the create method of the CustomerEJB entity bean calls the SQL INSERT command.
Unlike the session beans, the entity beans do not validate method parameters (except for the primary key parameter of ejbCreate). During the design phase, we decided that the session beans would check the parameters and throw the app exceptions, such as CustomerNotInAccountException and IllegalAccountTypeException. Consequently, if some other app were to include these entity beans, its session beans would also have to validate the method parameters.
Helper Classes
The EJB JAR files include several helper classes that are used by the enterprise beans. The source code for these classes is in the j2eetutorial/bank/src/com/sun/ebank/util subdirectory. Table 18-1 briefly describes the helper classes.
Table 18-1 Helper Classes for the Application's Enterprise Beans Class Name
Description
AccountDetails Encapsulates the state of an AccountEJB instance. Returned by the getDetails methods of AccountControllerEJB and AccountEJB. CodedNames Defines the strings that are the logical names in the calls of the lookup method. (For example: java:comp/env/ejb/account). The EJBGetter class references these strings. CustomerDetails Encapsulates the state of a CustomerEJB instance. Returned by the getDetails methods of CustomerControllerEJB and CustomerEJB. DBHelper Provides methods that generate the next primary keys (for example, getNextAccountId). Debug Has simple methods for printing a debugging message from an enterprise bean. These messages appear on the stdout of the J2EE server if it's run with the -verbose option. DomainUtil Contains validation methods: getAccountTypes, checkAccountType, and isCreditAccount. EJBGetter Has methods that locate (by invoking lookup) and return home interfaces (for example, getAccountControllerHome). TxDetails Encapsulates the state of a TxEJB instance. Returned by the getDetails methods of TxControllerEJB and TxEJB.
Database Tables
A database table of the Duke's Bank app may be categorized by its purpose: representing business entities and holding the next primary key.
Tables Representing Business Entities
The customer and account tables have a many-to-many relationship: A customer may have several bank accounts, and each account may be owned by more than one customer. This many-to-many relationship is implemented by the cross-reference table named customer_account_xref. The account and tx tables have a one-to-many relationship: A bank account may have many transactions, but each transaction refers to a single account.
PK stands for primary key, the value that uniquely identifies a row in a table. FK is an abbreviation for foreign key, which is the primary key of the related table. Tx is short for transaction, such as a deposit or withdrawal.
Tables That Hold the Next Primary Key
These tables have the following names:
- next_account_id
- next_customer_id
- next_tx_id
Each of these tables has a single column named id. The value of id is the next primary key that is passed to the create method of an entity bean. For example, before it creates a new AccountEJB entity bean, the AccountControllerEJB session bean must obtain a unique key by invoking the getNextAccountId method of the DBHelper class. The getNextAccountId method reads the id from the next_account_id table, increments the id value in the table, and then returns the id.
Protecting the Enterprise Beans
In the J2EE platform, you can protect an enterprise bean by specifying the security roles that can access its methods (see EJB-Tier Security). In the Duke's Bank app, two roles are defined--BankCustomer and BankAdmin--because two categories of operations are defined by the enterprise beans.
A user in the BankAdmin role is allowed to perform administrative functions: creating or removing an account, adding a customer to or removing a customer from an account, setting a credit line, and setting an initial balance. A user in the BankCustomer role is allowed to deposit, withdraw, transfer funds, make charges and payments, and list the account's transactions. Notice that there is no overlap in functions that users in either role can perform.
Access to these functions was restricted to the appropriate role by setting method permissions on selected methods of the CustomerControllerEJB, AccountControllerEJB, and TxControllerEJB enterprise beans. For example, by allowing only users in the BankAdmin role to access the createAccount method in the AccountControllerEJB enterprise bean, you have denied users in the BankCustomer role or any other role permission to create bank accounts. To see the method permissions that have been set, in deploytool locate the CustomerControllerEJB, AccountControllerEJB, and TxControllerEJB enterprise beans in the tree view. For each bean, select the Security tab and examine the method permissions.
Application Client
Sometimes, enterprise apps use a stand-alone client app for handling tasks such as system or app administration. For example, the Duke's Bank app uses a J2EE app client to manually administer customers and accounts. This capability is useful in the event the site becomes inaccessible for any reason or a customer prefers to communicate things such as changes to account information by phone.
A J2EE app client is a standalone program launched from the command line or desktop; it accesses enterprise beans running on the J2EE app server.
Customer Administration
- View customer information
- Add a new customer to the database
- Update customer information
- Find customer ID
Account Administration
- Create a new account
- Add a new customer to an existing account
- View account information
- Remove an account from the database
Error and informational messages appear in the left pane under Application Message Watch:, and data is entered and displayed in the right pane.
The Classes and Their Relationships
The J2EE app client is divided into three classes: BankAdmin, EventHandle, and DataModel; the relationships among the classes are depicted below:
BankAdmin builds the initial user interface, creates the EventHandle object, and provides methods for the EventHandle and DataModel objects to call to update the user interface.
EventHandle listens for button clicks by the user, takes action based on which button the user clicks, creates the DataModel object, calls methods in the DataModel object to write data to and read data from the underlying database, and calls methods in the BankAdmin object to update the user interface when actions complete.
DataModel retrieves data from the user interface, performs data checks, writes valid data to and reads stored data from the underlying database, and calls methods in the BankAdmin object to update the user interface based on the success of the database read or write operation.
BankAdmin Class
The BankAdmin class, which creates the user interface, is the class with the main method, and provides protected methods for the other BankAdmin app classes to call.
main Method
The main method creates instances of the BankAdmin and EventHandle classes. Arguments passed to the main method are used to initialize a locale, which is passed to the BankAdmin constructor.
public static void main(String args[]) { String language, country; if(args.length == 1) { language = new String(args[0]); currentLocale = new Locale(language, ""); } else if(args.length == 2) { language = new String(args[0]); country = new String(args[1]); currentLocale = new Locale(language, country); } else currentLocale = Locale.getDefault(); frame = new BankAdmin(currentLocale); frame.setTitle(messages.getString("CustAndAccountAdmin")); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; frame.addWindowListener(l); frame.pack(); frame.setVisible(true); ehandle = new EventHandle(frame, messages); System.exit(0); } }Constructor
The BankAdmin constructor creates the initial user interface, which consists of a menu bar and two panels. The menu bar contains the customer and account menus, the left panel contains a message area, and the right panel is a data display or update area.
Class Methods
The BankAdmin class provides methods that other objects call when they need to update the user interface. These methods are as follows:
- clearMessages: Clears the app messages that appear in the left panel
- resetPanelTwo: Resets the right panel when the user selects OK to signal the end of a data view or update operation
- createPanelTwoActLabels: Creates labels for account fields when account information is either viewed or updated
- createActFields: Creates account fields when account information is either viewed or updated
- createPanelTwoCustLabels: Creates labels for customer fields when customer information is either viewed or updated
- createCustFields: Creates customer fields when account information is either viewed or updated
- addCustToActFields: Creates labels and fields for when an add customer to account operation is invoked
- makeRadioButtons: Makes radio buttons for selecting the account type when a new account is created
- getDescription: Makes the radio button labels that describe each available account type
EventHandle Class
The EventHandle class implements the ActionListener interface, which provides a method interface for handling action events. Like all other interfaces in the Java programming language, ActionListener defines a set of methods, but does not implement their behavior. Instead, you provide the implementations because they take app-specific actions.
Constructor
The constructor receives an instance of the ResourceBundle and BankAdmin classes and assigns them to its private instance variable so that the EventHandle object has access to the app client's localized text and can update the user interface as needed. Lastly, the constructor calls the hookupEvents method to create the inner classes to listen for and handle action events.
public EventHandle(BankAdmin frame, ResourceBundle messages) { this.frame = frame; this.messages = messages; this.dataModel = new DataModel(frame, messages); //Hook up action events hookupEvents(); }actionPerformed Method
The ActionListener interface has only one method, the actionPerformed method. This method handles action events generated by the BankAdmin user interface when users create a new account. Specifically, it sets the account description when a bank administrator selects an account type radio button and sets the current balance to the beginning balance for new accounts when a bank administrator presses the Return key in the Beginning Balance field.
hookupEvents Method
The EventHandle class uses inner classes to handle menu and button press events. An inner class is a class nested or defined inside another class. Using inner classes in this way modularizes the code, making it easier to read and maintain. EventHandle inner classes manage the following app client operations:
- View customer information
- Create new customer
- Update customer information
- Find customer ID by last name
- View account information
- Create new account
- Add customer to account
- Remove account
- Clear data on Cancel button press
- Process data on OK button press
DataModel Class
The DataModel class provides methods for reading data from the database, writing data to the database, retrieving data from the user interface, and checking that data before it is written to the database.
Constructor
The constructor receives an instance of the BankAdmin class and assigns it to its private instance variable so that the DataModel object can display error messages in the user interface when its checkActData, checkCustData, or writeData method detects errors. It also receives an instance of the ResourceBundle class and assigns it to its private instance variable so that the DataModel object has access to the app client's localized text.
Because the DataModel class interacts with the database, the constructor also has the code to establish connections with the remote interfaces for the CustomerController and AccountController enterprise beans, and to use their remote interfaces to create an instance of the CustomerController and AccountController enterprise beans.
//Constructor public DataModel(BankAdmin frame, ResourceBundle messages) { this.frame = frame; this.messages = messages; //Look up and create CustomerController bean try { CustomerControllerHome customerControllerHome = EJBGetter. getCustomerControllerHome(); customer = customerControllerHome.create(); } catch (Exception NamingException) { NamingException.printStackTrace(); } //Look up and create AccountController bean try { AccountControllerHome accountControllerHome = EJBGetter.getAccountControllerHome(); account = accountControllerHome.create(); } catch (Exception NamingException) { NamingException.printStackTrace(); } }Methods
The getData method retrieves data from the user interface text fields and uses the String.trim method to remove extra control characters such as spaces and returns. Its one parameter is a JTextfield so that any instance of the JTextfield class can be passed in for processing.
private String getData(JTextField component) { String text, trimmed; if(component.getText().length() > 0) { text = component.getText(); trimmed = text.trim(); return trimmed; } else { text = null; return text; } }The checkCustData method stores customer data retrieved by the getData method, but first checks the data to be sure all required fields have data, the middle initial is no longer than one character, and the state is no longer than two characters. If everything checks out, the writeData method is called. If there are errors, they are printed to the user interface in the BankAdmin object. The checkActData method uses a similar model to check and store account data.
The createCustInf and createActInf methods are called by the EventHandle class to refresh the Panel 2 display in the event of a view, update, or add action event.
- Create Customer Information
- For a view or update event, the createCustInf method gets the customer information for the specified customer from the database and passes it to the createCustFields method in the BankAdmin class. A Boolean variable is used to determine whether the createCustFields method should create read-only fields for a view event or writable fields for an update event.
- For create event, the createCustInf method calls the createCustFields method in the BankAdmin class with null data and a Boolean variable to create empty editable fields for the user to enter customer data.
- Create Account Information
- For a view or update event, the createActInf method gets the account information for the specified account from the database and passes it to the createActFields method in the BankAdmin class. A Boolean variable is used to determine whether the createActFields method should create read-only fields for a view event or writable fields for an update event.
- For a create event, the createActInf method calls the createActFields method in the BankAdmin class with null data and a Boolean variable to create empty editable fields for the user to enter customer data.
- Adding a customer to an account or removing an account events operate directly on the database without creating any user interface components.
Web Client
In the Duke's Bank app, the Web client is used by customers to access account information and perform operatio