This is the second in a series of articles focused on using some of the prominent Internet and Java technologies to develop a Ticket Store application. In the last issue of JDJ we defined the APIs and technologies and the network topology that would be used to develop the Ticket Store. We also walked through skeletal definitions of the classes used to implement this application. Now we'll add some meat to these classes.
We'll also define some workflows and scenarios for our application, with emphasis on the design of the objects for the middleware tier of the store. Our focus will still be on the purchase of tickets via an online travel agent that gets quotes from different airlines and presents the Internet user with the best quote. The online store portion of our application will be discussed in the next article in the series.
The UI design for this application isn't a major part of our Java-based design. A basic browser UI will be developed for the end user. In corresponding issues of ColdFusion Developer's Journal (Vol. 1, issues 46) we'll develop the more sophisticated and personalized UI in parallel using ColdFusion. I'd like to reiterate that as this isn't a real-world application, several decisions made for the design will be oversimplified, their primary purpose being to illustrate how the components defined here can be integrated into a distributed application.
Of the four tiers of the Ticket Store application described in the last article, the Service Access tier, and specifically the Ticket Reservation and Sales Broker, will occupy most of our attention. As defined in last month's article, the Service Access tier is a middleware tier that accepts service requests from the Merchant Server tier, routes them to the Application Services tier and serves back the response to the Merchant Server tier. The Merchant Server tier adds in user-specific data and sends back the response to the user interface.
Ticket Reservation and Sales
The Ticket Reservation and Sales Broker (we'll call it "Broker" from this point) is actually our simulation (albeit oversimplified) of a real-life ticket agent. The software modules that make up this tier attempt to duplicate the base functionality of a human ticket agent who would typically gather information about the end user's (User's) flight requirements, search in his or her reservation system for flight availability and prices, and get back to the end user with a quote for the flight. Thus the main functions of the Broker are:
- Accept a request for a flight from the user.
- Define search criteria for getting flight quotes.
- Search for available flights based on the criteria.
- Obtain quote(s) from the reservation system.
- Add promotions or discounts on the price based on the user's purchase history.
- Return the quote(s) to the user.
- Accept a reservation request from the user. This includes getting his or her credit card information.
- Reserve the seat(s) for the user.
- Return a confirmation to the user.
This is a simple workflow that runs through the system in a sequential fashion. We'll implement this workflow in our system, ignoring other functionality such as canceling a reservation, etc. The idea is to demonstrate how data flows through our Ticket Store from the end customer to the back office and back. Figure 1 illustrates the use cases for the Broker. The workflow is illustrated in Figure 2.
Now let's define the classes involved in this set of transactions from the Broker's point of view. For now, we'll forgo discussion on the UI and assume that the user's input somehow arrives at the Broker. Allaire's ColdFusion will be used to develop a more sophisticated UI. (This will be discussed in issue 5 of ColdFusion Developer's Journal.)
Step 3 of the workflow defined above encapsulates the main functionality of the Broker. Let's break this step down and identify the classes we'll need to provide this functionality. The classes needed for the design of the Broker and their function are summarized in Table 1. The relationship between the classes is illustrated in a class diagram in Figure 3. The classes and code listings are described below.
This class was defined in last month's article as the TicketServlet. I've renamed it here to better indicate its functions, and I'll also expand on its functionality. The TicketBrokerServlet is the crux of the Broker. It receives a ticket request from the Merchant Server, packages it into a query, submits it to the Service Access tier, receives ticket quotes and passes them back to the Merchant Server. The request is passed in the form of name-value parameters via the "request_" (HTTPServlet Request) parameter into the service() method of the servlet. The method createTicketQuery() is called to extract the values from the request stream and to package them into a TicketQuery object. The string parameters passed into the servlet are defined below and are discussed under the TicketQuery class:
NUMBER="Number of seats"
Our Broker is capable of connecting to airline carriers with different kinds of interfaces based on the protocol used for the data exchange. In our system we have four fictitious airline carriers in the Service Access tier SeemaAir, KarunaAir, NitiAir and ApuAir. SeemaAir makes its prices available via a simple Socket interface. KarunaAir exposes its services via an RMI service. NitiAir uses a CORBA server. ApuAir offers a service via a Servlet, i.e., it supports a URL invocation. TicketBrokerServlet (which represents the Broker's connection layer) sends the TicketRequest to each of these carriers and waits for responses on each of them. When it receives all the responses, it uses the TicketPricer (described later) to send back the best quotation. The network connections between the Broker and the airline components in the Application Services layer are illustrated in Figure 4.
Since the Broker supports four kinds of services, it creates four clients, one for each service. These clients are instantiated by the TicketClientManager class, which establishes and manages connections to the different airline carriers:
if (clientManager_ == null)
clientManager_ = new TicketClientManager();
The service() method of the TicketBrokerServlet instantiates the TicketClientManager. It then calls a getQuotes() method on this instance. The method getQuotes() returns a vector of TicketQuote objects. The four client classes (SocketTicketClient, RMITicketClient, CORBATicketClient and ServletTicketClient) and the TicketQuery and TicketQuote classes are described later in this article.
The next method call in the service() method is to the method getBestQuote(). A vector of TicketQuote objects is passed in as a parameter. The method getBestQuote() calls static methods on the TicketPricer object to get the final price on each of the quotes obtained from the airlines. It selects the best price and sends a new TicketQuote object to the service() method of the servlet. The service() method calls the getQuoteString() method and passes it the TicketQuote object that it just got back. The resultant string is returned to the invoker of the URL. The string is in the form of name-value pairs, similar to the input received by the TicketBrokerServlet.
One of the parameters passed into the servlet is a "TYPE=VALUE" parameter. The "VALUE" can be "QUERY" or "BOOK". When the string "Query" is passed in as an argument to the servlet, the getQuotes() method described above is invoked. When the string "BOOK" is passed in, the bookSeats() method is called to actually book the flight. The bookSeats() method is described later in this article under the TicketService class.
The user will use certain criteria for his or her flight query/request. Since this query will have the same parameters for all the users (e.g., name, address), it's encapsulated in a separate class called TicketQuery. This class was defined in last issue's article. The TicketQuote class is a simple class with only getter and setter methods for the data fields.
The fields in the TicketQuery class are:
Notice that the class has a "begin date" and an "end date." This is because our reservation system allows a user to specify a range of days on which he or she can fly. For customers who have flexibility on the actual day of the flight and want to base their reservations on the cheapest fare, the system will search for the cheapest fare available in the time date range. If the user has no flexibility concerning the date, he or she will pass in the same value for the begin and end dates.
- query ID
- departure city
- arrival city
- begin date
- end date
- no. of seats
- seat class (coach, business, first class)
- seating preference (window, aisle)
- smoking preference (smoking/nonsmoking)
The TicketQuote class encapsulates the response from an airline in the Application Services tier to the Broker. Similar to the TicketQuery class, the TicketQuote class is a simple class with only getter and setter methods for the data fields. Some of the fields are just copied from the TicketQuery object to the TicketQuote object. The queryId is a unique ID that indicates the request.
The fields in the TicketQuote class are:
- query ID
- airline (SeemaAir, KarunaAir, NitiAir, ApuAir)
- departure city
- departure date
- departure time
- arrival city
- arrival date
- arrival time
- no. of seats
- seat class (coach, business, first class)
- smoking preference (smoking/nonsmoking)
- total price
In case there is no availability based on the search criteria, the TicketQuote returns with a "0" in the noOfSeats and the other fields are invalid. This is not the most efficient way of sending a response, but it will serve our purpose.
All four <protocol>TicketClient classes inherit from the TicketService interface and hence provide implementations of the getQuote() method. The functionality for all the clients is the same. They basically take in a TicketQuery object and return a TicketQuote object. They also implement a bookSeats() method that takes in a queryId parameter for booking the flights. In our implementations the server-side counterparts for these classes don't perform any real-time function (actually checking for availability, etc.). The prices are looked up in an MS Access database and the seats are assumed to be booked as soon as the request is made.
My assumption is that the reader is familiar with the workings of the different protocols used in this application. Thus I won't go into details of the classes used here. Each of the service protocols (Socket/RMI/CORBA/Servlet) has the following set of classes as illustrated earlier in Figure 3:
- <protocol>TicketClient, e.g., RMITicketClient: This implements the TicketService interface. The methods getQuote() and booksSeats() in this class throw a more specific exception (RemoteException) as compared to the superclass methods in the TicketService interface.
- <protocol> TicketServer, e.g., RMITicketServer: This class resides in the Application Service Layer and also extends the TicketService class. The RMI TicketServer is described later in this article.
In the interest of space and general sanity, I won't go into details of the classes for all four types of services. I will cover the RMI example here. The understanding of the rest is left as an exercise for readers.
In a real-world application the responses from each "airline" could take varying amounts of time.
There would also be a timeout for waiting for the responses. This kind of a transaction is asynchronous and would be best handled by spawning each ticket quote service as a separate thread. However, in our application we'll assume that the responses are instantaneous (indeed they will be, because our Service Application Tier is pretty much hard-coded). Therefore, each of the clients that request a quote from the corresponding airline server is started sequentially as shown in the TicketBrokerServlet class. The clients for the different protocols are described in the next four sections.
As the name suggests, this class is responsible for managing the TicketClients. The TicketClientManager constructor creates all four clients. Each client does the actual connect (Sockets) or bind (RMI/CORBA), or establishes the URL connection (servlets) and returns a reference to the TicketClientManager.
The TicketClientManager contains a getQuotes() method that calls a getQuote() on each of the clients. Each client in turn calls the getQuotes() method on its corresponding remote server. The resultant value is a TicketQuote object that is passed back to the TicketBrokerServlet. The TicketClientManager also contains a bookSeats() method that is forwarded in a similar fashion to the corresponding protocol server. The bookSeats() method returns a boolean value that indicates the result of the operation. The bookSeats() method takes in a string parameter that indicates the protocol (and corresponding airline) that was selected for the best fare.
This is the RMI client that gets the ticket quote from an RMI-based service. It implements the TicketService interface. This service is the one used by KarunaAir. The constructor establishes a connection with the RMITicketServer. The getQuote() method calls the corresponding getQuote() method on the RMITicketServer handle. Similarly, the bookSeats() method calls the corresponding method on the server.
The purpose of the TicketPricer is to get the final price on a quote. The implementation of this class uses a random selection to give a discount on the price. In a real-world application the broker would add promotions, discounts, and so forth based on the ticket agent's pricing policies. The TicketPricer has a single method, getDiscountedPrice(), that returns a new price for the tickets.
Application Service Tier Classes
The Service Access Tier consists of five classes. Four of them represent the different type of connection protocols RMITicketServer, CORBATicketServer, SocketTicketServer and ServletTicketServer. Each of these classes looks up the ticket prices from a static database implemented in Microsoft Access. This is achieved via the Quoter class. These classes and their purpose are listed in Table 2.
Each class inherits from the TicketService interface. The classes provide implementations for the client-side stubs. Hence, they provide implementations of the getQuote() and bookSeats() methods. The code in the ticket server classes is trivial and is not described here.
The Quoter class establishes a database connection with the Microsoft Access table, Quotes.mdb. It looks up the price of each ticket based on the airline. The ticket prices are hard-coded.
Running the Programs
The code for this article is available at
www.JavaDeveloperJournal.com. It was compiled and tested on a Windows NT 4.0 workstation. To run the programs you will need the following: JDK 1.1.x, JSDK 2.0 (Java Servlet Development Kit), your servlet engine and Web server, MS Access database and Visigenic Visibroker 2.5+. Instructions are also available at the Web site.