ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


A Tour Through WebLogic Workshop 8.1: Westside Auto Sales

by Scott Semyan
10/17/2003
Content provided by BEA Systems

The new WebLogic Workshop 8.1 contains many new features and is a huge step forward from the earlier product. Now, in addition to Web services, you can build many other types of J2EE applications. As a way of introducing this new version, this article will show how to build an enterprise application incorporating many of the new features found in WebLogic Workshop 8.1.

This article assumes a basic understanding of J2EE (Java 2 Enterprise Edition).

The application we will build in this article is "Westside Auto Sales" — an end-to-end automotive sales tool. This application has several pieces illustrated in the diagram below. The core modules of the application are 1) the Web application front end, 2) the Web service for ordering more cars, and 3) the manufacturer's Web service for processing the order for more cars. These modules, in turn, call Java controls and EJBs (Enterprise Java Beans) that perform application-level tasks. The basic architecture is illustrated below.

The data flow for our application is as follows: a user comes to the Westside Auto Sales (WAS) site and views the inventory (via a NetUI grid control on a JSP populated by a database control in a Page Flow). When they see a car they like, they can buy it but they must first sign in (via integrated security). The car they purchase is then set to "Sold" status (via an Entity EJB called from a custom control), and another car of the same type is added to the database with a status of "On Order" (again, via an Entity EJB). Finally, a message is sent to the Java Messaging Service (JMS) topic (via a JMS control) to initiate ordering another car from the manufacturer. The user gets a confirmation for their purchase and then can continue shopping.

Meanwhile, a message-driven EJB sees the message arrive in the WAS JMS topic and calls a method on the WAS Web service to initiate the process of ordering another car from the manufacturer. The WAS Web service first builds an "OrderCar" XML package via XMLBeans to send to the manufacturer. A request for a new car is sent to the manufacturer's Web service with the help of a Web service control. The manufacturer first decodes the XML using XMLBeans (and for Hondas, XQuery). They then add the order to their database and immediately return an order number. Later, when the car is shipped, the manufacturer's Web service calls the callback on the WAS Web service and sends the details of the car being shipped, including the final price. The WAS Web service finally uses an entity EJB to set the vehicle's actual price and mark it as available for sale.

We will go through the steps of building this application from scratch, discussing how each of the parts work along the way.

Data Store

Before we start building our application, we first need to create our database and tables and add a connection pool and JMS message topic to our WebLogic Server domain. For this project, I am using Microsoft SQL Server 2000, but you can use any JDBC-compliant database. Create a new database on your database server called "WASdb" and run the following SQL script to create the necessary tables, stored procedures, and default data. Note: this script is for Microsoft SQL; you may need to modify it to work on your database.

------------------------------------
-- Westside Auto Sales Tables & View
------------------------------------

create table Cars
(
    CarID   int identity primary key,
    Make    varchar(50) not null,
    Model   varchar(50) not null,
    Description varchar(800) not null
)
go

create table Inventory
(
    SKU     int identity primary key,
    CarID   int not null,
    Price   int not null,
    Status  varchar(50) 
)
go

create view CarInventory
as
	select c.CarID, Make, Model, Description, SKU, Price, Status
	from Cars c, Inventory i where c.CarID = i.CarID
go

----------------------------------
-- Sample Data
----------------------------------

insert Cars select "Volkswagen", "New Beetle", 
"The 2003 New Beetle is a 2-door, 4-passenger family coupe, or sports coupe, 
available in 8 trims, ranging from the GL 2.0L to the Turbo S 1.8L."

insert Cars select "Mini", "Cooper", 
"The new Mini Cooper is more fun than a carnival ride. Both models handle 
like sports cars and the Mini Cooper S can accelerate from 0 to 60 mph in 
less than seven seconds."

insert Cars select "Mercedes-Benz", "SL-Class", 
"The 2003 SL-Class is a 2-door, 2-passenger luxury convertible, or convertible 
sports car, available in two trims, the SL500 Roadster and the SL55 
AMG Roadster."

insert Cars select "Land Rover", "Range Rover", 
"The 2003 Range Rover is a 4-door, 5-passenger luxury sport-utility, 
available in one trim only, the HSE."

insert Cars select "Honda", "Civic Coupe", 
"The 2003 Civic Coupe is a 2-door, 5-passenger family coupe, available 
in 16 trims, ranging from the DX 5-spd MT to the EX 4-spd AT w/ 
Front Side Airbags."

insert Inventory select 1, 18999, "Available"

insert Inventory select 2, 17597, "Available"

insert Inventory select 3, 92399, "Available"

insert Inventory select 4, 71200, "Available"

insert Inventory select 5, 12110, "Available"

------------------------------------------
-- Manufacturer Web Service Tables 
------------------------------------------

create table ManufacturerOrders
(
    OrderNumber int identity primary key,
    SKU     int not null,
    Make    varchar(50) not null,
    Model   varchar(50) not null,
    Price   int not null,
    Status varchar(20) not null
)
go

Create table BasePrices
(
    Make    varchar(50) not null,
    Model   varchar(50) not null,
    Price   int not null,
    primary key (Make, Model)
)
go

------------------------------------------
-- Manufacturer Web Service Tables 
------------------------------------------

create procedure AddOrder (@SKU int, @Make varchar(50), @Model varchar(50), 
@Price int, @Status varchar(20))
as
	set nocount on;
     
     	insert ManufacturerOrders
	select @SKU, @Make, @Model, @Price, @Status

	select @@identity as "ID"
go

create procedure UpdateOrder (@OrderNum int)
as
	set nocount on

	update ManufacturerOrders set Status = "Shipped" where 
	OrderNumber = @OrderNum;

 	SELECT SKU, Price FROM ManufacturerOrders
	WHERE OrderNumber = @OrderNum
go

------------------------------------------
-- Manufacturer Web Sample Data
------------------------------------------

insert BasePrices select "Volkswagen", "New Beetle", 18000
insert BasePrices select "Mini", "Cooper", 1700
insert BasePrices select "Mercedes-Benz", "SL-Class", 85000
insert BasePrices select "Honda", "Civic Coupe", 12000
insert BasePrices select "Land Rover", "Range Rover", 65000

Domain Setup

Now we need to create a connection to our database in our WebLogic Server domain. We will be using the "Workshop" domain that ships with the WebLogic samples (found in [BEA Install Directory]\WebLogic81\samples\domains\workshop). To add the services we need, first start the domain and then go to the console (usually http://localhost:7001/console) and log in (username: "weblogic", password: "weblogic").

First, we will set up the connection pool. Under JDBC on the main page, select "Connection Pools". Click "Configure a new JDBC Connection Pool", then choose the database type and select a driver. Because we will be using transactions, you will need to choose an "XA" driver. Because I am using Microsoft SQL (with the JDBC driver service pack), I choose BEA's MS SQL Server Driver (Type 4XA). Name the pool "WASPool". Test the connection, and then create and deploy it on the server. Because we will be using local transactions, click on the new "WASPool", choose the "Connections" tab and then advanced options. Check the "Supports Local Transactions" and click on "Apply".

Once the pool is created, we can create a data source. Click on the Home icon in the upper right-hand side of the page, and then choose "Datasources" under JDBC. Click on "Configure a JDBC Data Source", use "WASDataSource" as the name and the JNDI name. Click "continue", and then choose "WASPool" as the connection pool. Then click "continue" and then "create".

Now we need our JMS topic for our messaging. In the left-hand side of the console, go to Services\JMS\Servers\cgJMSServer\Destinations and configure a new JMS topic. Set the name and JNDI name as "WASTopic" and click "Create". Once the new topic is created you will need to restart the server.

Web Application

Now we are ready to start building our application. We will start with the Web application. Launch WebLogic Workshop 8.1 and create a new default application called "Westside". Web applications in WLW use a new technology called Page Flows. Based on the Struts framework, Page Flows allow you to separate business logic from display logic. This means rather than filling your JSP files with lots of code, you put all your code in the JPF. This will become clear once we start building our site.

We were given a default page flow to start with. Let's start up the server and run our page flow once to see what we are working with. Open the Controller.jpf under the WestsideWeb directory and hit the play button in the toolbar. Once the site is compiled you should get a page in the test browser that says "New Web Application Page". Our site is not very exciting yet, but that will change soon.

The first part of our application involves showing the current vehicle inventory to the user. Normally we would spend hours building JSPs that display the results of a rowset, allow for filtering and sorting, allow for pagination, etc. With WLW this task is easy. First we need a RowSet Control to access the data in our database. We will put all our controls in a new folder called "Controls" under the "WestsideWeb" folder. Create the new folder then right-click on it and choose New\Other File Type\RowSet Control. In the RowSet Control Wizard, name the new control Inventory.jcx, and be sure to specify the WASDataSource. Click "Next", and choose "Query a View or Table", and choose the "CarInventory" view. Set the SKU as the primary key and click "Create". This gives you a database control with two methods: detailsCarinventory and getAllCarinventory. Now for the fun part: right-click on the new control in the project pane and choose "Generate Page Flow". Give it a name of "Inventory" and click "create". You will see a new set of files appear and "InventoryController.jpf" in the editor window. Hit play to see it in action. You will see a fully functional list of vehicle inventory with sorting, pagination, and filtering. The details link will take you to details of the car. What normally would take hours to build was built with a few mouse clicks:

Let's finish up the rest of the Web interface. First we will put a link to this new page flow from our main page. It's also good to have a link back to the home page. Let's add it in the title bar of the Inventory section. Because every page uses NetUI templates, we can add the link in the file WestsideWeb\resources\jsp\header.jsp and it will appear on every Inventory page. Add the following code to this file (code to add in italics):

<!-- Styled, graphical look of "bar" defined here using a repeatable image 
specified as "background="bar-background.gif"" -->
<td width="100%" height="21" background="<%=request.getContextPath () 
%>/resources/images/bar-background.gif">
        <a href="<%=request.getContextPath ()%>/Controller.jpf">Home</a>
         |
        <a href="<%=request.getContextPath ()
		%>/Inventory/InventoryController.jpf">
		Inventory</a>
</td>

If you re-run the InventoryController.jpf file, you should see the navigation at the top of the page:

Now that we have a common navigation system, let's add this template to our home page. Go back to index.jsp under WestsideWeb and change it to look like this:

<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib uri="netui-tags-databinding.tld" prefix="netui-data"%>
<%@ taglib uri="netui-tags-html.tld" prefix="netui"%>
<%@ taglib uri="netui-tags-template.tld" prefix="netui-template"%>
<netui-template:template templatePage="/resources/jsp/grid_template.jsp">
    <netui-template:setAttribute value="Home" name="title"/>
    <netui-template:section name="bodySection">
        <blockquote>
        <h1>Westside Auto Sales</h1><br>
        <a href="./Inventory/InventoryController.jpf">View our Inventory</a>
        </blockquote>
    </netui-template:section>
</netui-template:template>

Now we'll add the code to allow users to buy a car. First we need a "buyCar" action in the Inventory page flow. Go to the design view of InventoryController.jpf and add an action "buyCar", accepting the defaults. You can do this by either right-clicking on the page and choosing "New Action" or by dragging an Action onto the page from the palette. We also want a confirmation page to tell the user they successfully bought the car. Add a new page called "Confirm.jsp" to the page flow (again either by right-clicking on the page flow or by dragging a Page from the palette). Draw a flow line from the "buyCar" action to the new page. Now we need to add a link to the buyCar action in the details page. In detailsCarinventory.jsp add the following code above the link back to the grid (code to add in italics):

</table>
<br/>
<% DatabaseForm myForm = (DatabaseForm)request.getAttribute("databaseForm");
if (myForm.getStatus().equals("Available")) { %>
    <netui:anchor action="buyCar" >Buy This Car
    <netui:parameter name="SKU" value="{request.databaseForm.sku}" />
    </netui:anchor><p>
<% } %>
<netui:imageAnchor action="showGrid" border="0" 
src="/WestsideWeb/resources/images/
grid.gif">
    <netui:parameterMap map="{pageFlow.sortFilterService.queryParamsMap}"/>
</netui:imageAnchor>

Because you can only buy a car if it is available, we first check to make sure the car has not been sold, and then we show the link. To do this, first we create a local instance of the DatabaseForm that holds all our detail information. Then we can access members within this form. Notice, our link to buy the car is a link to an action on the page flow, not a hard link to a file.

As you add this code, you will get a blue popup message asking if you want to add an import statement for Inventory.InventoryController.DatabaseForm to the page. Hit Alt+Enter to accept the import. This adds the following to the top of the page:

<%@page import="Inventory.InventoryController.DatabaseForm"%>

There will be several other instances when you add code from this article that you will need to accept the import statements WebLogic Workshop suggests.

If you switch back to design view of the InventoryController.jpf you should see the new action and page in the context of the rest of the site:

Security

When the user hits the "buyCar" link, we want to make them sign in as a registered user. There are plenty of articles on the dev2dev site already on security, so we will simply create a single user and ensure that a prospective buyer signs in as this user to access the buy action. To create the new user, right-click on Security Roles and choose "Create New Role". Name this new role "WASUser" and click "OK". To secure the "buyCar" action, go into WestsideWeb\WEB-INF\web.xml and add the following at the end of the file before the </web-app> tag:

    <security-constraint>
      <display-name>Security Constraint for resources within this project.</
	  display-name> 
    - <web-resource-collection>
          <web-resource-name>Security for the buyCar action
		  </web-resource-name> 
          <description>A web service secured by basic authentication
		  </description> 
          <url-pattern>/Inventory/buyCar.do</url-pattern> 
          <http-method>GET</http-method> 
          <http-method>POST</http-method> 
      </web-resource-collection>
    - <auth-constraint>
          <role-name>WASUser</role-name> 
      </auth-constraint>
      </security-constraint>
    - <security-role>
          <description>A general pre-defined role in WebLogic Server. 
		  All logged in 
		  users are mapped to this role by default.</description> 
            <role-name>WASUser</role-name> 
      </security-role>

This will force the user to log in when they try to buy a car. If they log in as "WASUser" with a password of "password" they can continue. Otherwise they get an error. Go to WestsideWeb\Inventory\Confirm.jsp and add the following code to make it look like the rest of the site:

<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib uri="netui-tags-databinding.tld" prefix="netui-data"%>
<%@ taglib uri="netui-tags-html.tld" prefix="netui"%>
<%@ taglib uri="netui-tags-template.tld" prefix="netui-template"%>
<netui-template:template templatePage="/resources/jsp/grid_template.jsp">
    <netui-template:setAttribute value="Confirmation" name="title"/>
    <netui-template:section name="bodySection">
        <blockquote>
        <h2>Thank you for your order.</h2><br>
        <a href="InventoryController.jpf">Continue Shopping</a>
        </blockquote>
    </netui-template:section>
</netui-template:template>

Now we need to add the meat of the application - setting the car as sold and ordering a new one. Before we do this, feel free to test the code we have so far. When you hit the buy button, you should be forced to log in. Once you log in successfully, you should get the confirmation page.

Entity Enterprise Java Beans

Because we will be working with individual cars, it would be nice to simply work with a car entity rather than deal with reading and writing fields from a database. Entity EJBs allow us to do this. An entity EJB represents an individual row in a database. It is accessed via member variables, and new rows can be created as easily as creating a new object.

We will use an EJB to access the cars in the inventory table. To do this, first add an EJB project to our application by right-clicking on the top "Westside" folder and choosing "New\Project\EJB Project". Name the new project "WASejb". Right-click on the WASejb folder and choose "New Entity Bean from Database Table". Choose the "WASDataSource", click next, then pick the inventory table. Click "next" twice more and type "CarBean" as the bean name and "Westside" as the package name. Click "Finish". Because we will be adding new rows to the inventory table, we need to add the following at the top of the CarBean.ejb file (code to add in italics):

/**
 * @ejbgen:automatic-key-generation
 *   type="SQL_SERVER" 
 *   cache-size="0" 
 *   name="SQL_SERVER"
 *
 * @ejbgen:entity order-database-operations="false" 
   enable-batch-operations="false"
 *   ejb-name = "CarBean"
 *   data-source-name = "WASDataSource"
 *   table-name = "Inventory"
 *   prim-key-class = "java.lang.Integer"
 *
 * @ejbgen:jndi-name
 *   local = "ejb.CarBeanHome"
 *
 * @ejbgen:file-generation local-class = "true" local-class-name = "Car" 
 local-home = "true" local-home-name = "CarHome" remote-class = "false" 
 remote-class-name = "CarRemote" remote-home = "false" 
 remote-home-name = "CarRemoteHome" value-class = "false" 
 value-class-name = "CarValue" pk-class = "true"
 */

Also, because our db table creates new primary keys for us, we need to modify the create() and ejbPostCreate methods in this file so they do not require any parameters:

  public Java.lang.Integer ejbCreate() 
throws CreateException
  {
    return null;
  }

  public void ejbPostCreate() 
throws CreateException
  {}

Building EJBs used to be a lot of work. In fact, it's one of the most misunderstood areas in J2EE. We've just done all the work we need to do to create our entity bean. To use it, we'll wrap the calls to the bean in an EJB control. This will hide the dirty work of finding or creating the bean when we want to use it. First we need to build and deploy the EJB by right-clicking on the WASejb project and choosing "Build". Now, right-click in the "Controls" directory under WestsideWeb and choose "New Java Control". Choose EJB control, call it "InventoryEJB" and click next. The bean we want is local, so click "Browse local EJBs...", choose the CarBean, and click "Create". This gives us a wrapper for our EJB, which we will use later.

Custom Java Control

Instead of embedding code in the page flow, we will use a custom Java control to handle all the calls to our entity bean and, later, our JMS bean. Both our JPF and the car ordering Web service will share this control. To create the control, right-click on the "Controls" directory and choose "New Custom Java Control". Call it "WASControlImpl" and click "Create". Custom controls have two files: the Java file that describes the control as a class, and the implementation file that contains the actual code of the implementation of this class. We will put all our code in this implementation file. The first thing we need in this file is a method to mark a car as sold. This method will be called by the buyCar action in InventoryController.jpf. To create the method, right-click in the design view of the control and choose "Add Method". Name the method "purchaseCar". Go to design view and modify the method declaration to the following:

/**
 * @common:operation
 */
public String purchaseCar(Integer SKU, String User)
{
    return "";
}

When we call the method, we need to pass in the SKU of the car the user wants to buy along with their user name. We will then mark the car as sold and send a message back to the user confirming they have successfully purchased the car. We will call the method from the "buyCar" action of the JPF. To do this, first drag and drop the WASControl onto InventoryController.jpf. This will create a member variable called wASControl you can use to call the control. Add the following code to the "buyCar" action to call the purchaseCar method (new code in italics):

protected Forward buyCar()
{
  // Call the main control to purchase the car and order a new one
  String OrderText = new String();
  OrderText = wASControl.purchaseCar
  (new Integer(getRequest().getParameter("SKU")), 
  getRequest().getRemoteUser());

  // Put the message in the request string so we can show it on the JSP
  getRequest().setAttribute("Message", OrderText);
                
  return new Forward("success"); }

We first get the SKU sent in the request and the remote user name and send them to the purchaseCar method of the custom control. We then take the resulting message and put it in the request string so we can pull it out and display it to the user on confirm.jps.

Switch back to the design view of WASControl.jcx, and drag and drop the InventoryEJB.jcx onto the control. This will give us a variable named "inventoryEJB" we can use to access the InventoryEJB. Add the following code to the purchaseCar method to set the current car as sold and to create a new vehicle in the database:

public String purchaseCar(Integer SKU, String User)
{
  String OrderText = new String();

  try{
    // Use my entity bean wrapper to access the Inventory EJB
    Car IBean = inventoryEJB.findByPrimaryKey(SKU);

    // Can only sell the car if it is available    
    if (IBean.getStatus().equals("Available"))
    {
	// Set the status of the car to sold
	IBean.setStatus("Sold to " + User);
	OrderText = "Thank you for your order, " + User + ". 
	Your bill is $" + 
	IBean.getPrice() + ".";
	
	// Create a new car based on the old car to 
	replace it in inventory
	Car NewBean = inventoryEJB.create();
	NewBean.setCarID(IBean.getCarID());
	NewBean.setPrice(-1);
	NewBean.setStatus("Waiting to be ordered");	
    }
    else
	// Opps. Someone else got it first. 
	OrderText = "I'm sorry. That vehicle has been sold. 
	Please choose another one.";

  } catch (Exception e){
    OrderText = "Error using EJB: " + e.getMessage();
  }
       
  return OrderText;
}

Again, you will want to hit "Alt+Enter" when prompted to add "Westside.Car" as an import statement. Back in WestsideWeb\Inventory\Confirm.jsp we need to show the message generated by the call to the WASControl to the user. The JPF put the message in the request stream, so we just need to pull it out (new code in italics):

<h2>Thank you for your order.</h2><br>
<%= request.getAttribute("Message") %><p>
<a href="InventoryController.jpf">Continue Shopping</a>

At this point, we can try the app out. From the details page of a car, click the buy link. Once you log in you will see a confirmation on Confim.jsp. When you return to the inventory, you will see the car marked as sold to that user, and a new car in the database with a status of "Waiting to be ordered". In the next section, we will use JMS to send a message to a message-driven bean to order another car from the manufacturer.

Java Message Service Topics

When a user purchases a car, we want to order another car from the manufacturer to replace the car purchased. However, we don't need to order the car immediately. In fact, it's more important to return a confirmation to the user as soon as possible. We really just want to send a message to the car ordering system that a new car needs to be ordered whenever they can get around to it. This scenario is perfect for JMS because of the disconnected nature of messaging.

We will build two controls to enable this: a Java control to send messages to the topic, and a message-driven EJB to pick the messages up and initiate the ordering of the new vehicle.

The first thing we need is a way to get messages to the JMS topic. We can easily do this by creating a JMS control. Right-click the Controls directory and select "New Java Control". Choose "JMS" and name it "MessageSender". The message type will be "Text/XMLBean" and the destination type is "Topic". Browse for the send-jndi-name and choose "WASTopic" (the topic we created in the Workshop domain earlier). Ignore the receive topic (since our EJB will receive the message) and scroll down to click on "browse" for the connection-factory (use the default: "weblogic.jws.jms.QueueConnectionFactory"). Click "Create". Now, switch back to the WASControl and in design view, drag the new "MessageSender.jcx" onto the control. All we need to do to send a message to the JMS topic is add the following in our purchase car method (new code in italics):

	NewBean.setPrice(-1);
	NewBean.setStatus("Waiting to be ordered");	
                
       // Send a message to the topic to order another car
       messageSender.sendTextMessage(NewBean.getSku().toString());
}

Now we just need something to listen for the message. We will do this with a message-driven EJB. Back in our EJB project, right-click on the "Westside" folder and choose "New/Message-Driven Bean". We will name this "ListenerBean.ejb". First we need to tell the bean which topic to listen to. In the EJBGen properties pane, change the destination-type to "javax.jms.Topic" and the destination-jndi-name to "WASTopic". We will add code later to order the new vehicle once we build our Web service. For now, just add the following code to the onMessage method to show everything is working.

/**
 * @ejbgen:message-driven
 *   ejb-name = Listener
 *   destination-jndi-name = "WASTopic"
 *   destination-type="javax.jms.Topic" 
 *
 */
public class ListenerBean
  extends GenericMessageDrivenBean
  implements MessageDrivenBean, MessageListener
{
 public void onMessage(Message msg) {

  try {
      // Print information
      System.out.println("Got a message!");
      System.out.println("Contents of the message:" + 
	  ((TextMessage)msg).getText());
  } catch (Exception e) {
      System.out.println("Exception in ListenBean: " + e.getMessage());
  }
 }
}

Let's give it a try. First save any dirty files and build your EJB project again. Now fire up the application, go to inventory, select a car, and click the "buy" link. You should get a confirmation that you successfully purchased the car along with your bill. Again, a new car will be added to the inventory of the same make and model with a status of "Waiting to be ordered". If you go to your WebLogic Server window, you should see a message in the console like the following:

Got a message!
Contents of the message:11

The content of the message is the SKU of the car that was purchased.

Web Services

The next part of the application to build is the manufacturer's Web service that allows for ordering of cars. We'll create this as a separate application. Close the Westside application, then choose File/New/Application and create a new empty application called "CarSource". Now right-click on "CarSource" and choose New\Project\Web Service Project. Call the new project "CarSourceWS". This is the Web service that will receive the request for a new car. To create a new Web service in the Web service project, right-click on the CarSourceWS project and choose New/Web Service. Call the new Web service "CarSourceWS.jws".

We will have two different methods for ordering cars - one for Hondas, and one for all other cars. We will do this to show how you can use XQuery to map between two different XML schemas. In design view of CarSourceWS.jws, right-click and add a method called "orderHonda" and another method called "orderOther". This Web service will use asynchronous callbacks. We do this because when a new car is ordered from the manufacturer, there is often a time lag between when the car is ordered and when the car is shipped. For our application we will simulate that time lag with a timer control. Right-click in design view and choose "Add Callback". Name the callback "finishOrder".

XML Schemas and XMLBeans

To communicate between our Westside application and the CarSource Web service, we need to pass information back and forth about the car to be ordered. We will pass this information as XML. The following is the schema we will use to order a car.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://www.BEA-OrderCar.com" 
xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:fn="http://www.BEA-OrderCar.com" 
elementFormDefault="qualified">
	<xs:element name="OrderCar">
		<xs:complexType>
			<xs:sequence>
                <xs:element name="SKU" type="xs:int"/>
                <xs:element name="Year" type="xs:int"/>
                <xs:element name="Make" type="xs:string"/>                
                <xs:element name="Model" type="xs:string"/>                
                <xs:element name="Options" type="xs:string"/>                
                <xs:element name="Price" type="xs:int"/>
                <xs:element name="OrderNumber" type="xs:int"/>
                <xs:element name="Status" type="xs:string"/>                
			</xs:sequence>
		</xs:complexType>
	</xs:element>
</xs:schema>

Rather than using this XML directly, we will use an XMLBean to abstract the XML into a Java class. To do this, create a new schema project called "Schemas" under the main "CarSource" folder. Right-click on this new project and choose New/XML Schema called "OrderCar.xsd". Paste the code above into this new file and click on "Save". WebLogic Workshop immediately creates an XMLBean for this schema. We will use this new XMLBean in the orderOther method of our Web service. Modify the method declaration to the following:

public String orderOther(OrderCarDocument newCar)
{
	return "";
}

Again, hit Alt+Enter to add the appropriate import statement for com.beaOrderCar.OrderCarDocument to the file.

Database Control

When we receive the order for the new car, we will record the order in the database, start the timer to wait 10 seconds, and then retrieve the information back out of the database and send the it to the requestor's callback. We will need a database control to talk to our data source. Right-click on the CarSourceWS folder and create a new folder called "CSControls". Now, right-click on this new folder and choose New/Java Control/Database. Name it "CSDataAccess" and then browse for the WASDataSource. Finally, click "create". We need three methods in this control: getPrice, createOrder, and sendOrder. We also need a new Order class within the control to pass back information to the caller. Create the three methods by right-clicking on the control, then go to source view and edit the file to the following:

package CSControls; 

import com.bea.control.*; 
import java.sql.SQLException; 

/** 
 * @jc:connection data-source-jndi-name="WASDataSource" 
 */ 
public interface CSDataAccess extends DatabaseControl, 
com.bea.control.ControlExtension
{ 
    static public class Order
    { 
       public int SKU; 
       public int Price;
    } 

    static final long serialVersionUID = 1L;

    /** 
     * @jc:sql statement="exec UpdateOrder {OrderNumber}"
     */ 
    Order sendOrder(int OrderNumber);

    /**
     * @jc:sql statement="exec AddOrder {SKU}, {Make}, {Model}, 
	 {Price}, {Status}"
     */
    int createOrder(int SKU, String Make, String Model, 
	int Price, String Status);

    /**
     * @jc:sql statement="Select Price from BasePrices where Make = 
	 {Make} and Model = {Model}"
     */
    int getPrice(String Make, String Model);
}

Notice in the sendOrder and createOrder methods, we are calling stored procedures and in the getPrice method, we are using in-line SQL. The sendOrder method returns a SKU and a price so we use the Order class to transmit that information back to the caller. The other two methods simply return int values.

Now, to use this control in our Web service, drag and drop the control onto the design view of the Web service. This gives us a new private variable to access the control called "cSDataAccess".

Timer Control

We will use a timer control to simulate our ordering pause. In the real world, it might be a few days to several months before a requested car is finally shipped to a dealer. In this case you probably wouldn't use asynchronous calls, but separate methods on the caller and sender.

To create the control, right-click on the Web service in design view and choose "Add Control/Timer". Name the control "csTimer" and give it a timeout of 10 seconds.

Asynchronous Web Service

Now we are ready to add the code to respond to an order. First we will create a variable to track the unique ID (in our case, the OrderNumber) of the car being ordered. Do this by right-clicking on the control in design view and choosing "Add Variable". Use the following as the variable declaration:

Integer OrderNumber;

Now we will add a new method that will be called by our other methods. Add the following code to CarSourceWS.jws:

    public void orderCar(int SKU, String Make, String Model, int MarkUp)
    {
        // Calculate Markup can be anywhere from 0 to MarkUp
        double markup = Math.random()*MarkUp;
        int baseprice = cSDataAccess.getPrice(Make, Model);
        double markupprice = ((markup / 100.0 * ((double)baseprice)));
        
        // Create a new order item in the DB and get the order number
        OrderNumber = new Integer(cSDataAccess.createOrder
		(SKU, Make, Model, 
		(int)(baseprice + markupprice), "On Order"));

        // Start timer
        csTimer.start();
                
        return;
    }

This code calculates the markup of the car and adds it to a base price that it pulls from the database. We then add the car to the ordering database with a status of "On Order" and send a message to the console that the car has been ordered. Finally, we start the timer to wait 10 seconds.

We will use this new method in the orderOther method (we will handle the orderHonda method later). Edit the orderOther method to the following (hitting Alt+Enter when requested to add the import for import com.beaOrderCar.OrderCarDocument.OrderCar):

/**
* @common:operation
* @jws:conversation phase="start"
*/
public String orderOther(OrderCarDocument newCar)
{
    // Get the info
    OrderCar myCar = newCar.getOrderCar();
        
   // Markup on exotic cars can be quite high - up to 10%
   int nMarkupMax = 10;

    // put a message in the console window
    System.out.println("[CarSource]Ordering 1 " + myCar.getMake() + " " 
	+ myCar.getModel() + " from other manufacturing.");        

   // Create a new order item in the DB and get the order number
   orderCar(myCar.getSKU(), myCar.getMake(), myCar.getModel(), 
   nMarkupMax);
        
   // Send back the order number
   return OrderNumber.toString();
}

Notice, the first thing we do is to use our XMLBean to get at the XML data passed into the method. We create an "OrderCar" object from the OrderCarDocument and then we can access each of the attributes as if they were member variables. This is quite powerful and eliminates the need for XML parsing and validation. Once we get the XMLBean, we send the information on the new car to order to our orderCar method, which sets the OrderNumber. Finally, we pass this OrderNumber back to the caller.

When the timer goes off 10 seconds later, we need to mark the car as shipped and send the details of the car back to the callback of the original caller. To implement the onTimeout method, click on the onTimeout method of the csTimer control in design view. Then add the following code to the csTimer_onTimeout method and the finishOrder method (again, hitting Alt+Enter when prompted to add the import for import CSControls.CSDataAccess.Order):

public void csTimer_onTimeout(long time)
{
    // Stop the timer
    csTimer.stop();
    
    // Mark the car as shipped
    Order myOrder = cSDataAccess.sendOrder(OrderNumber.intValue());
    
    OrderCarDocument myOrderDoc = OrderCarDocument.Factory.newInstance();
    OrderCar retOrder = myOrderDoc.addNewOrderCar();

    retOrder.setOrderNumber(OrderNumber.intValue());
    retOrder.setPrice(myOrder.Price);
    retOrder.setSKU(myOrder.SKU);
    retOrder.setStatus("Shipped");
    
     System.out.println("[CarSource]Order " + OrderNumber + " 
	 Complete. Sending message back to requestor.");

    callback.finishOrder(myOrderDoc); 
    
    return;
}

public interface Callback extends com.bea.control.ServiceControl
{
   // Send the car order info back to the requesting web service
   /**
    * @jws:conversation phase="finish"
    */
   public void finishOrder(OrderCarDocument yourOrder);
}

When the timer goes off, we stop the timer and mark the car as shipped using the database control. This returns an Order object (that we defined in the database control). We then create a new XML document and use an XMLBean to set all the attributes of the new document. This document is then passed back to the callback, which sends it on to the requestor. Notice that we are using a conversation in our Web service. The orderOther and orderHonda methods start the conversation, and the callback ends it. The conversation tags indicate that state should be maintained for the lifetime of the conversation. This allows us to handle multiple requests concurrently without having to worry about multi-threading issues.

At this point, we are ready to test our Web service. Save any dirty files, then switch to the CarSourceWS.jws file and click "Start". This opens up a browser window that gives you an interface to test the Web service. Because our methods take XML as an argument, click on the "Test XML" tab at the top of the screen. In the orderOther section you are given a sample XML argument. Alter this so we are ordering a Volkswagen New Beetle like so:

<orderOther xmlns="http://www.openuri.org/" 
xmlns:bea="http://www.BEA-OrderCar.com">
  <bea:OrderCar>
    <bea:SKU>3</bea:SKU>
    <bea:Year>2003</bea:Year>
    <bea:Make>Volkswagen</bea:Make>
    <bea:Model>New Beetle</bea:Model>
    <bea:Options>None</bea:Options>
    <bea:Price></bea:Price>
    <bea:OrderNumber></bea:OrderNumber>
    <bea:Status></bea:Status>
  </bea:OrderCar>
</orderOther>

Notice we don't care about the Price, OrderNumber or Status because this will be set by the Web service. Click the orderOther button to start the conversation. You will immediately get back an orderNumber in the form of XML:

Service Response 
Submitted at Friday, July 11, 2003 4:02:04 PM PDT

<SOAP-ENV:Envelope xmlns:SOAP-ENV=
"http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<ns:orderOtherResponse xmlns:ns="http://www.openuri.org/">
<ns:orderOtherResult>1</ns:orderOtherResult>
</ns:orderOtherResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope> 

Meanwhile, the timer is counting down. If you click refresh on the message log, you will be able to see the onTimeout being called and then the callback being executed. If you click on the callback you should see the XML for the OrderCar being returned to the caller:

Client Callback 
Submitted at Friday, July 11, 2003 4:02:13 PM PDT

<SOAP-ENV:Envelope xmlns:SOAP-ENC=
"http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Header>
<CallbackHeader xmlns=
"http://www.openuri.org/2002/04/soap/conversation/">
<conversationID>1057964523220</conversationID>
</CallbackHeader>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns:finishOrder xmlns:ns="http://www.openuri.org/">
<bea:OrderCar xmlns:bea="http://www.BEA-OrderCar.com">
<bea:SKU>3</bea:SKU>
<bea:Price>19162</bea:Price>
<bea:OrderNumber>1</bea:OrderNumber>
<bea:Status>Shipped</bea:Status>
</bea:OrderCar>
</ns:finishOrder>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Notice the SKU stayed the same and the price and status have been updated to reflect that the car has shipped. If you look in your console window, you should find the following messages:

[CarSource]Ordering 1 Volkswagen New Beetle from manufacturing.
[CarSource]Order 1 Complete. Sending message back to requestor.

Xquery

Before we add the code to call this Web service to our original Westside application, we will add code to accept a different XML package and translate it to the OrderCar package automatically via XQuery. In the real world, sometimes there are several different accepted schemas for transmitting data. In our example, it is possible that most car dealerships use the OrderCar schema for transmitting car-ordering information but that Honda dealers use a different HondaCar format. Usually it is a major amount of work to map between different schemas. The new WebLogic XQuery tools make this mapping simple. As an example, our orderHonda method will accept the HondaCar schema and translate it on the fly to the OrderCar schema we use in our orderOther method.

First we will create the orderHonda schema and its related XMLBeans. Right-click on the Schemas project and choose "New XML Schema". Name this Honda.xsd and then add the following to it:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://www.BEA-Honda.com" 
xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:fn="http://www.BEA-Honda.com" elementFormDefault="qualified">
	<xs:element name="HondaCar">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="countrycode" 
				type="xs:int"/>
                              <xs:element name="SKU" 
							  type="xs:int"/>
				<xs:element name="model" 
				type="xs:string"/>
				<xs:element name="options" 
				type="xs:string"/>
				<xs:element name="engine" 
				type="xs:string"/>
			</xs:sequence>
		</xs:complexType>
	</xs:element>
</xs:schema>

Now alter the orderHonda method so it looks like this:

/**
 * @common:operation
 * @jws:conversation phase="start"
 */
public String orderHonda(OrderCarDocument hondaCar)
{
   return "";
}

Like the orderOther method, the orderHonda method will mark the start of the conversation. Also like orderOther, this method takes an OrderCarDocument as the input parameter. However, we know that people ordering Hondas will be passing in the HondaCar schema. We could do the mapping between these two schemas manually within the method, but it's much easier to let XQuery do it for us. To set up the mapping, switch to design view and double-click on the arrow to the left of the orderHonda method. This takes you to the XQuery Mapper. To map from a HondaCar to an OrderCar, click the Choose button on the Parameter XML tab. Select the HondaCar element under Honda.xsd and click OK. Now click on the Edit XQuery button to edit the mapping. Mapping between the two schemas is as simple as drawing lines between SKU, Model, and Options on the XQuery screen. As you drag one field onto the other, green lines show the connections. Click OK, then OK again. You now have a map between the two schemas defined in XQuery:

* @jws:parameter-xml schema-element="ns0:HondaCar" xquery::
* declare namespace ns0="http://www.BEA-Honda.com"
* declare namespace ns1="http://www.openuri.org/"
* declare namespace ns2="http://www.BEA-OrderCar.com"

* <ns1:orderHonda>
*     <ns2:OrderCar>
*         <ns2:SKU>{data($input/ns0:SKU)}</ns2:SKU>
*         <ns2:Model>{data($input/ns0:model)}</ns2:Model>
*         <ns2:Options>{data($input/ns0:options)}</ns2:Options>
*     </ns2:OrderCar>
* </ns1:orderHonda>

This means you don't have to worry that your users are going to pass in a HondaCar XML document; you can simply treat the method as if it always gets an OrderCar XML document. Now we are ready to add the rest of the code to our orderHonda method:

public String orderHonda(OrderCarDocument hondaCar)
{
    // Get the info
    OrderCar myCar = hondaCar.getOrderCar();
    
    // Markup on Hondas is minimal
    int nMarkupMax = 5;

    // put a message in the console window
    System.out.println("[CarSource]Ordering 1 " + myCar.getMake() 
	+ " " + myCar.getModel() + " from Honda manufacturing.");
    
    // Create a new order item in the DB and get the order number
    orderCar(myCar.getSKU(), "Honda", myCar.getModel(), nMarkupMax);
    
    // Send back the order number
    return OrderNumber.toString();
}

Just like in the orderOther method, we generate an XMLBean from the document (in this case, a HondaCar XML document translated to an OrderCar XML document by XQuery) and call the method that adds the car to the database and sets the OrderNumber. Like the orderOther method, we first send back the OrderNumber and then start the timer. When the onTimout method gets hit, we send the vehicle information back to the caller.

Let's test our Web service again. Save any dirty files, make sure the CarSourceWS.jws is in the edit window, and hit "start". Again, because our method accepts an XML package, click on Test XML. In the orderHonda method, notice how incoming XML is a HondaCar format. Fill in the test XML as follows:

<HondaCar xmlns="http://www.BEA-Honda.com">
  <countrycode>3</countrycode>
  <SKU>3</SKU>
  <model>Civic Coupe</model>
  <options>None</options>
  <engine>I4</engine>
</HondaCar>

Click the orderHonda button. You will get the OrderNumber back, and 10 seconds later if you hit the refresh button you will get the new car with the price. If you look in the console, you will see the following message telling you that we successfully called the orderHonda method:

[CarSource]Ordering 1 null Civic Coupe from Honda manufacturing.
[CarSource]Order 2 Complete. Sending message back to requestor.

Calling a Web Service from a Web Service

We are finally ready to add the code to the original Westside application that calls our CarSource Web service. Before we do, we will need a WSDL that describes the CarSource Web service so our Westside application will know how to access the CarSource methods. To create this file, right-click on the CarSourceWS.jws file and choose "Generate WSDL File". This will create a file called CarSourceWSContract.wsdl.

Now, go back to the Westside application. The first thing we will do is create a java control to talk to CarSourceWS. Under the WestsideWeb/Controls directory, right-click and choose Import. Navigate to the WSDL file we created in our CarSource project and click OK. To create a Web service java control, simply right-click on the WSDL file and choose "Generate Service Control". This creates the control we will use to communicate with the CarSource Web service.

We also need to import the OrderCar and OrderHonda schemas. To do this, right-click on the Schemas project and choose Import. Select Honda.xsd and OrderCar.xsd from the CarSource project and click OK. This will create the XMLBeans we need to build the XML that we'll pass to the CarSource Web service.

Because we are using an XQuery in the CarSource Web service, we will want to edit our service control to accept the HondaCar schema for the orderHonda method and the orderCar schema for the orderOther method. To do this, open CarSourceWSControl.jcx, delete the class OrderCar (since we will use XMLBeans), and change the orderHonda, orderOther, and Callback methods to the following (click OK when it asks if you want to modify the file):

public interface Callback extends com.bea.control.ServiceControl.Callback
{
    /**
     * @jc:conversation phase="finish"
     */
    public void finishOrder (com.beaOrderCar.OrderCarDocument OrderCar);
}

/**
* @jc:protocol form-post="false" form-get="false" 
* @jc:conversation phase="start"
*/
public java.lang.String orderHonda 
(com.beaHonda.HondaCarDocument HondaCar);

/**
* @jc:protocol form-post="false" form-get="false" 
* @jc:conversation phase="start"
*/
public java.lang.String orderOther 
(com.beaOrderCar.OrderCarDocument OrderCar);

We are now ready to build our car-ordering Web service. This Web service is called by the message-driven EJB and handles ordering the car from the CarSource Web service. To create the Web service, add a new folder called "WebServices" to the WestsideWeb project and add a new Web service called "OrderCarFromWS.jws". In design view of the new Web service, drag and drop Inventory.jcx, CarSourceWSControl.jcx, and WASControlImpl.jcs onto the design service. We will use all of these controls in our Web service. Your design view should look like this:

First we need the method that calls the CarSource Web service. Right-click the design surface and add a new method called "OrderFromCS". Click on the method name to go to source view and alter the method to look like this:

/**
 * @common:operation
 * @jws:conversation phase="start"
 */
public void OrderFromCS (String SKU) throws Exception
{
    // Look up the make and model
    Integer nSKU = new Integer(SKU);
    RowSet orderCar = inventory.detailsCarinventory(nSKU);
    orderCar.next();
    String sMake = orderCar.getString("Make");
    String sModel = orderCar.getString("Model");

    // Order the car from the proper manufacturer
    String OrderNumber = new String();

    if (sMake.equalsIgnoreCase("honda"))
    {
        // order from honda method

        // Prepare the XML to send to the manufacturer        
        HondaCarDocument orderDoc = HondaCarDocument.Factory.newInstance();
        HondaCar order = orderDoc.addNewHondaCar();
        
        order.setEngine("I4");
        order.setModel(sModel);
        order.setOptions("Basic");
        order.setCountrycode(03);
        order.setSKU(nSKU.intValue());
        
        System.out.println("Ordering 1 " + sModel + 
		" from Honda manufacturer.");

        OrderNumber = carSourceWSControl.orderHonda(orderDoc);          
    }
    else
    {
        // order a car through the normal channel

        // Prepare the XML to send to the manufacturer        
        OrderCarDocument orderDoc = 
		OrderCarDocument.Factory.newInstance();
        OrderCar order = orderDoc.addNewOrderCar();
        
        order.setMake(sMake);
        order.setModel(sModel);
        order.setOptions("Loaded");
        order.setYear(2003);
        order.setSKU(nSKU.intValue());

        // Print out the message
        System.out.println("Ordering 1 " + sModel + 
		" from other manufacturer.");
                        
        OrderNumber = carSourceWSControl.orderOther(orderDoc); 
    }
    
    System.out.println("Received Order Number " + OrderNumber );
    
    // Add the car to inventory and set the status as ordered
    wASControl.AddCarToInventory(new Integer(SKU), 
	new Integer(OrderNumber));
}

As you enter the code you will be prompted (via the magic Alt+Enter again) to add the following import statements:

import com.beaHonda.HondaCarDocument;
import com.beaHonda.HondaCarDocument.HondaCar;
import com.beaOrderCar.OrderCarDocument;
import com.beaOrderCar.OrderCarDocument.OrderCar;
import javax.sql.RowSet;

The code for this method first looks up the details of the car using the inventory control, and then determines whether to call the orderHonda or the orderOther method on the CarSource Web service. For either method, first we need to create the XML document we will send. We do this by using the XMLBean's Factory.newInstance() method. We can then get a new XMLBean and use the bean to set the attributes in the XML. Finally we send the XML document to the Web service using our Web service control.

Let's implement the AddCarToInventory method in our WASControl. In WASControlImpl.jcx, add the following code:

/**
 * @common:operation
 */
public void AddCarToInventory(Integer SKU, Integer OrderNumber) 
throws Exception
{
    // Set the status of the car to Ordered and the price to -1
    Car IBean = inventoryEJB.findByPrimaryKey(SKU);
    IBean.setStatus("Ordered: " + OrderNumber.toString());
    System.out.println("Marking Car " + SKU + " as Ordered"); 
}

Once the car has been ordered from the manufacturer's Web service, we set the status to "ordered" along with the order number.

Finally, we need to implement the callback that will set the final price of the car and mark it as available for sale. In OrderCarFromCS.jws, add the following code:

public void carSourceWSControl_finishOrder
(com.beaOrderCar.OrderCarDocument incomingCarDoc) throws Exception
{
    // Turn the XML Doc into an XMLBean
    OrderCar incomingCar = incomingCarDoc.getOrderCar();
    System.out.println("Receiving order number " + 
	incomingCar.getOrderNumber()); 
    
    // Got the car back, use the main controller to set 
	the proper status on it
    wASControl.SetCarAsReady(new Integer(incomingCar.getSKU()), 
	incomingCar.getPrice());
}

Again, we are using XMLBeans to access the information contained in the XML document and then sending that information to our WASControl to update the database.

Here is the code we need to add to WASControlImpl.jcx to implement the SetCarAsReady method:

/**
 * @common:operation
 */
public void SetCarAsReady(Integer SKU, int Price) throws Exception
{
   // Set the status of the car to Available from On Order and the price
   Car IBean = inventoryEJB.findByPrimaryKey(SKU);
   IBean.setStatus("Available");
   IBean.setPrice(Price);
   System.out.println("Marking Car " + SKU + 
   " as Available, Price: " + Price); 
}

Again, we use our EJB to mark the car as available and to set the final price.

All that is left to do is to call our OrderCarFromCS Web service from our message-driven EJB. To do this, we will use .jar files that will enable our EJB to use this Web service as if it were a local Java class. Save any dirty files, then open the OrderCarFromWS.jws file in the editor and hit play. When the test Web service form comes up, select the "Overview" tab. Among the links you will see one for a Java Proxy and another for a Proxy Support Jar. Click on each of these links and save the resulting .jar file in the APP-INF/lib directory of our Westside application. Close the test browser and open ListenBean.ejb from the WASejb project in source view. Change the onMessage method to the following (new text in italics):

public void onMessage(Message msg) {

    try {
        // Print information
        System.out.println("Got a message!");
        System.out.println("Contents of the message:" + 
		((TextMessage)msg).getText());
        
        // Order the car via the OrderCarFromWS Web Service
        OrderCarFromWS_Impl CSImpl = new OrderCarFromWS_Impl();
        OrderCarFromWSSoap soap = CSImpl.getOrderCarFromWSSoap();
        soap.orderFromCS(((TextMessage)msg).getText());
        
    } catch (Exception e) {
        System.out.println("Exception in ListenBean: " + e.getMessage());
    }
}

With our proxy jars added to the application, all we need to do to call the OrderCarFromCS Web service is to use the Impl class to create a new soap object and then use that soap object to call the method we want on the Web service. Hitting Alt+Enter when prompted will add the appropriate import statements for weblogic.jws.proxies.OrderCarFromWSSoap and weblogic.jws.proxies.OrderCarFromWS_Impl.

Our application is now complete. We need to rebuild our EJBs so save any dirty files, then right-click on the top "Westside" folder and choose "Build Application". This will recompile everything and redeploy the application on the server. When the build finishes, open the controller.jpf file from the WestsideWeb folder and click "Start". Click the "View Inventory" link and choose an available car. Click on the buy link. After you log in, you are taken to the confirmation page. If you click on "continue shopping" you will see the car's status has changed to "Sold to WASUser" and that a new car has been created and is waiting to be ordered. As you hit refresh, you can see the status switch to "ordered" and then "available" as the CarSource Web service processes the order. In the console window for the server, you should get something similar to the following as the vehicle moves though the ordering process:

Got a message!
Contents of the message:12
Ordering 1 Range Rover from other manufacturer.
[CarSource]Ordering 1 Land Rover Range Rover from manufacturing.
Received Order Number 8
Marking Car 12 as Ordered
[CarSource]Order 8 Complete. Sending message back to requestor.
Receiving order number 8
Marking Car 12 as Available, Price: 70664

If you order a Honda, you will see something like the following:

Got a message!
Contents of the message:25
Ordering 1 Civic Coupe from Honda manufacturer.
[CarSource]Ordering 1 null Civic Coupe from Honda manufacturing.
Received Order Number 10
Marking Car 25 as Ordered
[CarSource]Order 10 Complete. Sending message back to requestor.
Receiving order number 10
Marking Car 25 as Available, Price: 12337

These messages show that when ordering a Honda, the request is sent to the orderHonda method of the CarSource Web service, which used XQuery to translate the request into the HondaCar schema. As a way of understanding how this all works, you can set breakpoints within the code then step through it watching the process along the way.

As we have seen, the new WebLogic Workshop 8.1 is more than just a tool to build Web services. You can now build many types of projects within the WLW IDE. This gives you the power to create much more complex applications and do it all within a unified editing and debugging environment.

Carl Sjogreen is senior product manager for BEA WebLogic Workshop.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.