Cars Example

Real applications combine many of the concepts presented in this chapter. Our cars example demonstrates using a stateful and stateless session bean to model an automobile dealer and a prospective buyer. Of course, this example does not illustrate all of the required information and steps in purchasing a real car, but it does illustrate a typical design of a larger application.

The automobile dealer presents a purchasing service to the potential customer. Users need to be able to query the automobile dealer to look for cars in the dealer's stock that match their criteria. Then, the customer may make an offer on a particular car. If the dealer accepts the offer, the car is sold and removed from the dealer's stock.

Because the dealer stock is persistent and must survive any system crashes or outages, it is stored in a database table. The automotive dealer is modeled with a stateless session bean. The stateless session bean presents a facade to the potential customer, and the customer never directly accesses the database. This design separates the customer from the persistent representation allowing the persistent logic to be changed independently of the customer application (see Figure 8-6).

Figure 8-6. Objects and Database for the Cars Example


The potential automobile buyer is modeled with a stateful session bean. Each instance of this bean corresponds to a single auto shopper, and it maintains conversational state with its associated shopper. The client application interacts directly with the automobile buyer stateful session bean. This bean, in turn, accesses the AutoDealer stateless session bean. This is a common arrangement where a client application maintains a stateful conversation with a bean, which then proxies work back to a stateless service.

Running the Example

To run the Cars example, follow the procedures outlined in Chapter 5 for connecting to the default Cloudscape database, or follow your standard procedures for connecting WebLogic Server to your in-house database. In a command window, set your environment with beawlserver6.0configmydomainsetEnv.cmd to build the application.

In another command window, set your environment with beawlserver6.0configexamplessetExamplesEnv.cmd to run the Cars example once it has been built. Use the Examples Server to enable the default Cloudscape database connection.

The following paragraphs explain the functions of the build and deploy scripts for the Cars example.

Before running the application, we must create the associated database table.

CREATE TABLE AutoStock (make varchar(25), model varchar(25), year integer,
  color varchar(25), mileage integer, price float(7), vin integer);

Next, we manually populate the dealer's inventory with database inserts.

INSERT INTO AutoStock VALUES (
'Porsche', '911', 2001, 'Black', 25, 90000, 123456
);

INSERT INTO AutoStock VALUES (
'Ferrari', 'Maranello', 2001, 'Red', 15, 200000, 24601
);

INSERT INTO AutoStock VALUES (
'Lamborghini', 'Diablo', 2001, 'Yellow', 20, 275000, 80386
);

INSERT INTO AutoStock VALUES (
'Audi', 'TT', 2001, 'Gray', 25, 40000, 5000
);

This example requires a value object to represent a car. The code that accesses the database populates these objects and returns them to the client. The client code accesses the values in the car value object to choose a car to buy. Because this object is passed through the remote interface, it must implement java.io.Serializable.

public final class CarValueObject implements Serializable {

  private static String EOL = System.getProperty("line.separa
tor");

  private String make;
  private String model;

  private int year;

  private String color;

  private int mileage;

  private double price;

  private int vin;  // vehicle identification number

  public CarValueObject() {}

  public CarValueObject(String make, String model, int year,
    String color, int mileage, double price, int vin) {
    setMake(make);
    setModel(model);
    setYear(year);
    setColor(color);
    setMileage(mileage);
    setPrice(price);
    setVin(vin);

  }

  public String toString() {

    StringBuffer sb = new StringBuffer();

    sb.append(EOL);
    sb.append("Make: "+getMake());
    sb.append(" Model: "+getModel());
    sb.append(" Year: "+getYear());
    sb.append(" Color: "+getColor());
    sb.append(" Mileage: "+getMileage());
    sb.append(" Price: "+getPrice());
    sb.append(" VIN: "+getVin());

    sb.append(EOL);

    return sb.toString();
  }

  public String getMake() { return make; }
  public void setMake(String m) { make = m; }

  public String getModel() { return model; }
  public void setModel(String m) { model = m; }

  public int getYear() { return year; }
  public void setYear(int y) { year = y; }

  public String getColor() { return color; }
  public void setColor(String c) { color = c; }

  public int getMileage() { return mileage; }
  public void setMileage(int mi) { mileage = mi; }

  public double getPrice() { return price; }
  public void setPrice(double p) { price = p; }

  public int getVin() { return vin; }
  public void setVin(int v) { vin = v; }
}

The AutoBuyer EJB is the stateful session bean that acts as the client's interface to the auto dealer. The AutoBuyerHome exposes a single create method. The AutoBuyer bean stores the required make, model, and maximum price, and the stateful session bean uses these values when interacting with the auto dealer.

public interface AutoBuyerHome extends EJBHome {

  public AutoBuyer create(String make, String model, double max-
Price)
    throws CreateException, RemoteException;

}

It exposes three business methods in its remote interface. The getAvailableCars method asks the AutoDealer for cars matching the buyer's criteria. The getPreviousOffers method returns a map from vehicle ID numbers to the last bid amount on that car. This is conversational state maintained by the AutoBuyer bean. Finally, there is a buyCar method that returns true if the dealer accepts the offer. If the car is no longer available, a NoSuchCarException is thrown.

public interface AutoBuyer extends EJBObject {

  public Collection getAvailableCars()
    throws RemoteException;

  public Map getPreviousOffers()
    throws RemoteException;

  public boolean buyCar(int vin, double offer)
    throws RemoteException, NoSuchCarException;

}

The AutoBuyerBean is the EJB's bean class. It implements the business methods exposed in the remote interface.

public class AutoBuyerBean implements SessionBean {

  private SessionContext ctx;

  private String make;
  private String model;
  private double maxPrice;
  private AutoDealer autoDealer;

  private Map offerMap = new HashMap();

  public void setSessionContext(SessionContext c) {
    ctx = c;
  }

The AutoBuyerBean references the AutoDealer EJB. An EJB reference is used to avoid hard-coding the AutoDealer's JNDI name into the bean class.

  public void ejbCreate(String ma, String mo, double mp) {
    make     = ma;
    model    = mo;
    maxPrice = mp;

    AutoDealerHome dealerHome = null;

    try {
      Context ic = new InitialContext();

      Object h = ic.lookup("java:/comp/env/ejb/AutoDealerHome");

      dealerHome = (AutoDealerHome)
        PortableRemoteObject.narrow(h, AutoDealerHome.class);

    } catch (NamingException ne) {
      // Cannot find AutoDealerHome

      ne.printStackTrace();

      throw new EJBException(ne);
    }

    try {
      autoDealer = dealerHome.create();
    } catch (Exception e) {
      // exception during a creation of the Dealer
      e.printStackTrace();

      throw new EJBException(e);
    }
  }

  public void ejbRemove() {}
  public void ejbActivate() {}
  public void ejbPassivate() {}

The getAvailableCars and buyCar methods proxy their work to the AutoDealer EJB. A real application often interposes additional logic within the stateful session bean to manage additional state.

  public Collection getAvailableCars()
    throws RemoteException
  {
    return autoDealer.getAvailableCarsInPriceRange(make, model,
      maxPrice);
  }


  public Map getPreviousOffers() {
    return offerMap;
  }

  public void deletePreviousOffers() {
    offerMap.clear();
  }

  public boolean buyCar(int vin, double offer)
    throws RemoteException, NoSuchCarException
  {

    offerMap.put(new Long(vin), new Double(offer));

    return autoDealer.buyCar(vin, offer);
  }

}

The AutoDealer is modeled as a stateless session bean, and it exposes the buyCar and getAvailableCarsInPriceRange business methods. These methods access the underlying database to query or update the associated data.

The buyCar method is deployed with the Required transaction attribute. It makes database updates and must run within a transaction. The getAvailableCarsInPriceRange runs a query against the database. It is deployed with the NotSupported attribute because this application does not require the query to run within a transaction. This helps minimize the length of time locks are held in the database layer. Any data updates are through the buyCar method and use transactions.

public interface AutoDealer extends EJBObject {

  public boolean buyCar(int vin, double offer)
    throws RemoteException, NoSuchCarException;

  public Collection getAvailableCarsInPriceRange(String make,
    String model, double highPrice)
    throws RemoteException;
}

The AutoDealerBean provides the implementation of the dealer's business methods.

public class AutoDealerBean implements SessionBean {

  private SessionContext ctx;

  private DataSource dataSource;

  private String tableName;

The database table name is stored as an EJB environment name. This allows the table name to be changed without altering the EJB code. The AutoDealerBean keeps a DataSource in its member variables. Business methods use this DataSource to create JDBC connections. The data source is found through a resource reference. Like the table name, this enables the data source name to be changed without affecting the bean code.

  public void setSessionContext(SessionContext c) {
    ctx = c;

    try {
      Context ic = new InitialContext();

      tableName = (String) ic.lookup("java:/comp/env/TableName");

      dataSource = (DataSource) ic.lookup("java:/comp/env/jdbc/DBPool");

    } catch (NamingException ne) {
      ne.printStackTrace();

      throw new EJBException(ne);
    }
  }

  public void ejbCreate() {}
  public void ejbRemove() {}

  public void ejbActivate() {}
  public void ejbPassivate() {}

Accessing the Database

The buyCar method must determine whether the car is still available and then decide whether the offer is acceptable. This dealer uses a simple algorithm that any offer greater than 95 percent of the list price is acceptable. If the car is bought, it is removed from the database table and true is returned.

The database code is written to minimize potential deadlocks. First, a conditional delete is performed. This blind write succeeds and returns one row if the VIN exists and the offer is acceptable. In this case, true is returned. If zero rows were deleted, it is possible that the car has already been sold or the offer is not acceptable. The EJB performs a query to determine whether the VIN exists in the database table. If no car exists, a NoSuchCarException is thrown.

Note that the EJB is using rather generic SQL in this example. Real applications often incorporate database-specific SQL to enhance performance or scalability.

Finally, the buyCar method is careful to close any JDBC resources that it has used by closing the Connection, Statement, and ResultSet.

  public boolean buyCar(int vin, double offer)
    throws NoSuchCarException
  {
    boolean accepted = false;

    // This dealer accepts an offer of 95% of the list price or
    // higher.  We use the offer price to figure out the purchas-
ing
    // power of this offer.
    double priceLimit = Math.floor(offer / 0.95);

    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;

    try {
      conn = dataSource.getConnection();

      st = conn.createStatement();

      int rowCount = st.executeUpdate("delete from "+tableName+
        " WHERE vin="+vin+" AND price <="+priceLimit);

      if (rowCount == 1) {
        // car was bought
        return true;
      } else {

        // need to determine whether no rows were deleted because
        // the vin does not exist, or because the price was not
        // acceptable.

        st = conn.createStatement();

        rs = st.executeQuery("select vin from "+tableName+
          " WHERE vin="+vin);

        if (rs.next()) {
          // vin exists so price was not acceptable
          return false;
        } else {
          // vin no longer exists

          throw new NoSuchCarException("The car with vin: "+vin+
            " is no longer available.");
        }
      }
    } catch (SQLException e) {

      e.printStackTrace();

      throw new EJBException(e);

    } finally {

      // close out resources

      if (rs != null) try { rs.close(); } catch (Exception ignore)
{}
      if (st != null) try { st.close(); } catch (Exception ignore)
{}
      if (conn != null) {
        try { conn.close(); } catch (Exception ignore) {}
      }
    }
  }

The getAvailableCarsInPriceRange is another example of a stateless session bean method accessing an underlying database. This method receives a ResultSet and iterates through the ResultSet populating CarValueObjects to return to the client.

  public Collection getAvailableCarsInPriceRange(String make,
    String model, double highPrice) {

    List cars = new ArrayList();

    Connection conn = null;
    Statement st    = null;
    ResultSet rs    = null;

    try {
      conn = dataSource.getConnection();

      st = conn.createStatement();

      rs = st.executeQuery("select make, model, year, color,"+
        " mileage, price, vin from "+tableName+
        " WHERE price<="+highPrice+" AND model='"+model+"' AND
make='"+
        make + "'");

      while (rs.next()) {

        int year     = rs.getInt(3);
        String color = rs.getString(4);
        int mileage  = rs.getInt(5);
        double price = rs.getDouble(6);
        int vin      = rs.getInt(7);

        cars.add(new CarValueObject(make, model, year, color,
mileage,
          price, vin));
      }

      return cars;

    } catch (SQLException e) {

      e.printStackTrace();
      throw new EJBException(e);

    } finally {

      // close out resources

      if (rs != null) try { rs.close(); } catch (Exception ignore)
{}
      if (st != null) try { st.close(); } catch (Exception ignore)
{}
      if (conn != null) {
        try { conn.close(); } catch (Exception ignore) {}
      }
    }
  }
}

Client Code

The client code creates an AutoBuyer and then queries for the available cars matching its criteria. Finally, the buyCar method is called in an attempt to make an offer on the car. This simple example always offers 98 percent of the list price which, as we've seen, is accepted by the dealer. We have omitted the auxiliary client code and only demonstrate here the EJB interactions.

    AutoBuyer buyer = home.create(make, model, price);

    // finding the cars that match my criteria
    Collection cars = buyer.getAvailableCars();

    if (cars.isEmpty()) {
      System.out.println("Sorry there was no available "+
        make + " " + model + " for under $"+price);
    } else {

      try {
        // Bid on the first car in the Collection
        CarValueObject car = (CarValueObject) cars.itera
tor().next();

        System.out.println("Found a car: "+car);

        double bidPrice = Math.floor(car.getPrice() * 0.98);

boolean accepted = buyer.buyCar(vin, price);

      } catch (NoSuchCarException nsce) {
        System.err.println("Sorry.  This car is no longer avail
able.");
      }
    }

We can see the output of our sample client when it attempts to buy a Porsche 911 for less than $100,000.

Created a customer to find a Porsche 911.
Found a car:
Make: Porsche Model: 911 Year: 2001 Color: Black Mileage: 25
Price: 90000.0 VIN: 123456

Congratulations.  Your bid of 88200.0 was accepted.

We can query the database and see that indeed the Porsche no longer exists in the database table.

select * from AutoStock;
   make     |  model   | year | color  | mileage | price  |  vin
--------------+------------+------+--------+----------+--------+-------
----
 Ferrari     | Maranello | 2001 | Red    |      15 | 200000 |
24601
 Lamborghini | Diablo    | 2001 | Yellow |      20 | 275000 |
80386
 Audi       | TT       | 2001 | Gray    |     25 |  40000 |  5000
(3 rows)

If we run our client again asking for a Porsche 911 for less than $100,000, we find that none are available.

Created a customer to find a Porsche 911.

Sorry there was no available Porsche 911 for under $100000.0

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.161.188