The Address Book

I l @ ve RuBoard

You need to get a couple of pieces of infrastructure in place before moving on to order checkout. One major piece of dangling functionality involves the wallet and the address book.

The wallet (the list of credit cards) will need to use addresses. As you recall way back in the ERD design, you used the Address object as a property of the credit card rather than recording address information separately in the credit card itself. As a result, you should get addresses working first.

To begin, you need several new queries in your properties file. To remind you, the schema for ADDRESS in the database is as follows :

 create table ADDRESS (        ADDRESS_ID     int not null auto_increment,        FIRST_NAME     char(30) not null,        LAST_NAME      char(40) not null,        STREET_1             char(128) not null,        STREET_2             char(128),        CITY       char(50) not null,        STATE      char(2) not null,        POSTAL_CODE    char(10) not null,        primary key(ADDRESS_ID)); create table ADDRESS_BOOK (        CUSTOMER_KEY     int references CUSTOMER,        ADDRESS_KEY      int references ADDRESS); 

To create, modify, find, and delete from these tables, add the SQL shown in Listing 11.6.

Listing 11.6 com.bfg.customer.SQLQueries.properties
 addressQuery=SELECT * FROM ADDRESS WHERE ADDRESS_ID=? addressDelete=DELETE FROM ADDRESS WHERE ADDRESS_ID=? addressInsert=INSERT INTO ADDRESS (FIRST_NAME, LAST_NAME, STREET_1, \               STREET_2, CITY, STATE, POSTAL_CODE) \               VALUES (?, ?, ?, ?, ?, ?, ?) addressUpdate=UPDATE ADDRESS SET FIRST_NAME=?, LAST_NAME=?, STREET_1=?, \             STREET_2=?, CITY=?, STATE=?, POSTAL_CODE=? WHERE ADDRESS_ID=? addressID=SELECT LAST_INSERT_ID() getAddressBook=SELECT ADDRESS_KEY FROM ADDRESS_BOOK WHERE CUSTOMER_KEY=? addAddress=INSERT INTO ADDRESS_BOOK (ADDRESS_KEY, CUSTOMER_KEY) VALUES (?, ?) removeAddress=DELETE FROM ADDRESS_BOOK WHERE ADDRESS_KEY=? AND CUSTOMER_KEY=? 

With these in place, you can write the Address bean (see Listing 11.7).

Listing 11.7 Address.java
 package com.bfg.customer; import java.util.Vector; import java.util.HashMap; import java.util.Iterator; import org.apache.turbine.services.db.TurbineDB; import org.apache.turbine.util.db.pool.DBConnection; import org.apache.turbine.util.TurbineConfig; import com.bfg.exceptions.CustomerActivityException; import java.sql.*; import java.util.ResourceBundle; import org.apache.log4j.Category; import javax.naming.NamingException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingEnumeration; import javax.naming.directory.InitialDirContext; public class Address {     static Category cat = Category.getInstance(Address.class);     private static ResourceBundle sql_bundle = ResourceBundle.getBundle("com.bfg.customer.SQLQueries");     protected int addressID;     protected String firstName;     protected String lastName;     protected String street1;     protected String street2;     protected String city;     protected String state;     protected String postalCode;     public int getAddressID() {      return addressID;     }     public void setAddressID(int id) {      addressID = id;     }     public String getFirstName() {      return firstName;     }     public void setFirstName(String name) {      firstName = name;     }     public String getLastName() {      return lastName;     }     public void setLastName(String name) {      lastName = name;     }     public String getStreet1 () {      return street1;     }     public void setStreet1(String street) {      street1 = street;     }     public String getStreet2 () {      return street2;     }     public void setStreet2(String street) {      street2 = street;     }     public String getCity () {      return city;     }     public void setCity(String c) {      city = c;     }     public String getState () {      return state;     }     public void setState(String st) {      state = st;     }     public String getPostalCode () {      return postalCode;     }     public void setPostalCode(String pc) {      postalCode = pc;     }     private HashMap validationErrors = new HashMap();     public String getFieldError(String fieldname) {      return((String)validationErrors.get(fieldname));     }     public void addFieldError(String fieldname, String error) {      validationErrors.put(fieldname, error);     }     public boolean validateAddress() {      validationErrors.clear();      boolean valid = true;      if ((lastName == null)           (lastName.length() == 0)) {          addFieldError("lastName", "Last Name is required.");          valid = false;      }      if ((firstName == null)           (firstName.length() == 0)) {          addFieldError("firstName", "First Name is required.");          valid = false;      }      if ((street1 == null)           (street1.length() == 0)) {          addFieldError("street1", "Street Address is required.");          valid = false;      }      if ((city == null)           (city.length() == 0)) {          addFieldError("city", "City is required.");          valid = false;      }      if ((state == null)           (state.length() == 0)) {          addFieldError("state", "State is required.");          valid = false;      }      if ((postalCode == null)           (postalCode.length() == 0)) {          addFieldError("postalCode", "Postal Code is required.");          valid = false;      }      return valid;     }     public static Address findAddress(int ID)      throws CustomerActivityException {      Address addr = null;          DBConnection dbConn = null;          try             {                  dbConn = TurbineDB.getConnection();                  if (dbConn == null) {                      cat.error("Can't get database connection");                      throw new CustomerActivityException();                  }                  PreparedStatement pstmt =                      dbConn.prepareStatement(sql_bundle.getString("address Query"));                  pstmt.setInt(1, ID);                  ResultSet rs = pstmt.executeQuery();                  if (rs.next()) {                      addr = new Address();                      addr.setFirstName(rs.getString("FIRST_NAME"));                      addr.setLastName(rs.getString("LAST_NAME"));                      addr.setStreet1(rs.getString("STREET_1"));                      addr.setStreet2(rs.getString("STREET_2"));                      addr.setCity(rs.getString("CITY"));                      addr.setState(rs.getString("STATE"));                      addr.setPostalCode(rs.getString("POSTAL_CODE"));                      addr.setAddressID(ID);                 }  else {                      cat.error("Couldn't find record for Address");                 }                 rs.close();                 pstmt.close();              }          catch (Exception e)              {                  cat.error("Error during findAddress", e);                  throw new CustomerActivityException();              }         finally             {                  try                      {                         TurbineDB.releaseConnection(dbConn);                      }                  catch (Exception e)                      {                          cat.error("Error during release connection", e);                      }             }         return addr;      }     public void createAddress()      throws CustomerActivityException {          DBConnection dbConn = null;          try              {                  dbConn = TurbineDB.getConnection();                  if (dbConn == null) {                      cat.error("Can't get database connection");                      throw new CustomerActivityException();                  }                  PreparedStatement pstmt =                  dbConn.prepareStatement(sql_bundle.getString("addressIn sert"));                  pstmt.setString(1, getFirstName());                  pstmt.setString(2, getLastName());                  pstmt.setString(3, getStreet1());                  pstmt.setString(4, getStreet2());                  pstmt.setString(5, getCity());                  pstmt.setString(6, getState());                  pstmt.setString(7, getPostalCode());                  pstmt.executeUpdate();                  pstmt.close();                  pstmt =                      dbConn.prepareStatement(sql_bundle.getString("addressID"));                  ResultSet rs = pstmt.executeQuery();                  if (rs.next()) {                  addressID = rs.getInt(1);                  }  else {                       cat.error("Couldn't find record for new Address");                  }                  rs.close();                  pstmt.close();               }           catch (Exception e)               {                   cat.error("Error during createAddress", e);                   throw new CustomerActivityException();               }           finally               {                   try                       {                          TurbineDB.releaseConnection(dbConn);                       }                   catch (Exception e)                       {                          cat.error("Error during release connection", e);                       }              }      }     public void updateAddress()       throws CustomerActivityException {           DBConnection dbConn = null;           try               {                  dbConn = TurbineDB.getConnection();                  if (dbConn == null) {                      cat.error("Can't get database connection");                      throw new CustomerActivityException();                  }                  PreparedStatement pstmt =                      dbConn.prepareStatement(sql_bundle.getString("addressUp date"));                  pstmt.setString(1, getFirstName());                  pstmt.setString(2, getLastName());                  pstmt.setString(3, getStreet1());                  pstmt.setString(4, getStreet2());                  pstmt.setString(5, getCity());                  pstmt.setString(6, getState());                  pstmt.setString(7, getPostalCode());                  pstmt.setInt(8, getAddressID());                  pstmt.executeUpdate();                  pstmt.close();               }           catch (Exception e)               {                  cat.error("Error during updateAddress", e);                  throw new CustomerActivityException();               }           finally               {                  try                       {                           TurbineDB.releaseConnection(dbConn);                       }                   catch (Exception e)                       {                           cat.error("Error during release connection", e);                       }               }      }     public void deleteAddress()       throws CustomerActivityException {           DBConnection dbConn = null;           try                {                    dbConn = TurbineDB.getConnection();                    if (dbConn == null) {                        cat.error("Can't get database connection");                        throw new CustomerActivityException();                    }                    PreparedStatement pstmt =                        dbConn.prepareStatement(sql_bundle.getString("address Delete"));                    pstmt.setInt(1, getAddressID());                    pstmt.executeUpdate();                    pstmt.close();                }            catch (Exception e)                {                   cat.error("Error during deleteAddress", e);                   throw new CustomerActivityException();                }           finally               {                  try                      {                         TurbineDB.releaseConnection(dbConn);                      }                  catch (Exception e)                      {                          cat.error("Error during release connection", e);                      }         }     } } 

You start with your normal collection of required libraries, set up for logging, and access to your SQL query resource bundle. Following that are the state variables for the object along with their accessors.

You need a validation hash map to store validation errors and a validation method that ensures that all the fields have been filled out.

The find , create , modify , and delete methods are almost identical to the code used in Customer (see Listing 8.1 in Chapter 8, "Retrieving, Storing, and Verifying User Input").

GAINING MOMENTUM: WHEN THE CODE STARTS FLOWING

There comes a point in every project (or in a major subcomponent of very large projects) when it seems that the volume of code starts to grow rapidly .

Where it once seemed to take a whole day to grind out a few hundred lines of Java, now you seem to be generating thousands. Major pieces of functionality start to appear like magic. What the heck is going on?

There are several reasons for this avalanche of code. For one thing, by that point in the project, you've probably ironed out all your infrastructure issues. Your code repository is working. You know how to log errors and get to the database from your code. So, instead of doing "science projects" all the time to discover how to get things done, you can concentrate on functionality.

You've also probably started to "wrap your mind" around the problem. I find that during the first couple of weeks on a new project, I'm just trying to learn the customer's terminology and requirements, understand any new platform technologies, learn the schema, and so on. Then one day I wake up and it all makes sense ”I can see it from 30,000 feet and understand how everything fits together.

Finally, the nature of Java is that you end up doing a lot of cut-and-paste code reuse. When a bean needs to read in an instance from the database, it's going to be done pretty much the same way every time. So, you can copy and paste the code from the last object that did it. The more code you write, the more is borrowed code and the less new stuff you're writing.

The createAddress method lets you see a particular technique that you need to use with MySQL. Databases such as Oracle have objects called sequences that are set up to increment when read. Thus, if you do a select against it, you are sure to get the next number in sequence. You can then use that number as the index to a newly created row of a table.

MySQL does things a bit differently. If you indicate that a row is an autoincrementing index, you can get the value assigned to it with a SELECT LAST_INSERT_ID() SQL query after doing the INSERT . LAST_INSERT_ID() returns the value of the last autoincremented column created in this thread. Use it here to find the ADDRESS_ID of the address just created. You could have used this when you were inserting records into the Customer table, but in that case you could also use the unique e-mail address for a lookup.

To support your new address object, you need to add some things to the Customer object. Rather than use Vector s to hold the list of addresses and credit cards, change them to HashMap s so that they can be looked up by the database key, which will allow the Customer object to be used as a cache for its addresses and credit cards (see Listing 11.8).

You also need to add a couple of methods that will maintain the ADDRESS_BOOK record for a customer.

The Customer class should also automatically read in its address book when a customer is loaded. So, you need to add some code to findCustomer .

CACHING AND MULTIPLE LOGINS

In this application, you're caching objects such as the credit card and address lists. This allows greater performance and also lets you do a direct == comparison of objects rather than having to implement a .equals for them. This is because, by caching, you can be sure that a request for cardId 2345 always returns the same object.

This strategy is not without peril, however. Consider the following hypothetical situation.

Bob logs in from work to his account and changes the address for Mary Jones in his address book. Unfortunately, his wife, Susan, is also logged in at the same time from home. Because the address book is cached and first read in upon login, she has a version that precedes Bob's edit. If Susan tries to use the address book entry for Mary Jones, she'll get the old version.

If this is really an issue for your application, you can put code in place to enforce letting a user be logged in from only one location at a time. In most cases, the problems are mild and unlikely enough that this is not necessary.

Listing 11.8 Changes to Customer.java
 . . .     private HashMap addresses = new HashMap();;     private HashMap wallet = new HashMap();     public HashMap getWallet() {      return wallet;     }     public HashMap getAddressBook() {      return addresses;     } . . .     public static Customer findCustomer(String emailAddress)          throws CustomerActivityException {          DBConnection dbConn = null;          Customer cust = null;          try             {               dbConn = TurbineDB.getConnection();               if (dbConn == null) {                   cat.error("Can't get database connection");                   throw new CustomerActivityException();             }             PreparedStatement pstmt =                 dbConn.prepareStatement(sql_bundle.getString("findQuery"));             pstmt.setString(1, emailAddress);             ResultSet rs = pstmt.executeQuery();             if (rs.next()) {                 cust = new Customer();                 cust.setCustomerId(rs.getInt("CUSTOMER_ID"));                 cust.setEmail(rs.getString("EMAIL_ADDRESS"));                 cust.setPassword(rs.getString("PASSWORD"));             }  else {                  return null;             }             rs.close();             pstmt.close();             pstmt = dbConn.prepareStatement(sql_bundle.getString("getAddress Book"));             pstmt.setInt(1, cust.getCustomerId());             rs = pstmt.executeQuery();             while (rs.next()) {                 Address addr = Address.findAddress(rs.getInt(1));                 cust.addresses.put(new Integer(addr.getAddressID()),                                     addr);             }             rs.close();             pstmt.close();           }       catch (Exception e)           {               cat.error("Error during findCustomer", e);                throw new CustomerActivityException();            }        finally            {                try                    {                        TurbineDB.releaseConnection(dbConn);                    }                catch (Exception e)                    {                        cat.error("Error during releaseConnection", e);                    }            }        return cust;      }     public void addAddress(Address addr) throws CustomerActivityException {          DBConnection dbConn = null;          try              {                dbConn = TurbineDB.getConnection();                if (dbConn == null) {                    cat.error("Can't get database connection");                    throw new CustomerActivityException();                }                PreparedStatement pstmt =                    dbConn.prepareStatement(sql_bundle.getString("addAddress"));                pstmt.setInt(1, addr.getAddressID());                pstmt.setInt(2, getCustomerId());                pstmt.executeUpdate();                pstmt.close();                getAddressBook().put(new Integer(addr.getAddressID()), addr);              }          catch (Exception e)              {                  cat.error("Error during addAddress", e);                  throw new CustomerActivityException();              }         finally              {             try                  {                      TurbineDB.releaseConnection(dbConn);                  }              catch (Exception e)                  {                      cat.error("Error during release connection", e);                  }              }           }     public void deleteAddress(Address addr) throws CustomerActivityException {          DBConnection dbConn = null;          try              {                  dbConn = TurbineDB.getConnection();                  if (dbConn == null) {                      cat.error("Can't get database connection");                      throw new CustomerActivityException();                  }                  PreparedStatement pstmt =                      dbConn.prepareStatement(sql_bundle.getString("removeAddress"));                  pstmt.setInt(1, addr.getAddressID());                  pstmt.setInt(2, getCustomerId());                  pstmt.executeUpdate();                  pstmt.close();              }          catch (Exception e)              {                  cat.error("Error during addAddress", e);                  throw new CustomerActivityException();              }          finally              {                 try                  {                      TurbineDB.releaseConnection(dbConn);                  }              catch (Exception e)                  {                      cat.error("Error during release connection", e);                  }              }     } 

In this case, findAddress is used to get each address record instead of explicitly doing a SQL query and building the addresses here or using a third class to explicitly load and save the address book as a unit. The number of entries in the address book is relatively small, so it was acceptable in this case to use a slightly less efficient technique rather than loading all the entries with a single query because it simplified the code a bit. In the case of a query that might load a large number of rows, organizing the code to do the loadup in a single call would be the preferred solution.

Now you need somewhere for the customer to make use of this new functionality. Start with a top-level account maintenance page (see Listing 11.9 ).

Listing 11.9 MyAccount.jsp
 <%@ include file="/jsp/cust/AutoLogin.jsp" %> <%@ page import="com.bfg.customer.Customer" %> <%@ page import="com.bfg.customer.Address" %> <%@ page import="java.util.Iterator" %> <jsp:useBean id="customer" class="com.bfg.customer.Customer" scope="session"/> <% if (customer.getEmail() == null) {     response.sendRedirect("Login.jsp");     return; } %> <head> <title>Account Maintainence</title> </head> <%@ include file="/jsp/includes/bfgheader.jsp" %> <h2 align="center">Account Maintenance</h2> <center><h2>Address Book</h2></center> <A HREF="NewAddress.jsp">Create New Address</A> <% if (customer.getAddressBook().size() > 0) { %> <TABLE WIDTH="100%">   <TR><TH>Name</TH><TH>City</TH><TH>Edit</TH><TH>Delete</TH></TR> <% Iterator it = customer.getAddressBook().keySet().iterator(); while (it.hasNext()) {     Address addr = (Address) customer.getAddressBook().get(it.next()); %>      <TR><TD><%= addr.getFirstName() %> <%= addr.getLastName() %></TD>          <TD><%= addr.getCity() %></TD>          <TD><A HREF="NewAddress.jsp?operation=update&addressId=<%= addr.getAddressID() % graphics/ccc.gif >">X</A></TD>          <TD><A HREF="DeleteAddress.jsp?addressId=<%= addr.getAddressID() %>" graphics/ccc.gif TARGET="tempwindow">X</A></TD></TR> <% }  %> </TABLE> <% }  %> <%@ include file="/jsp/includes/bfgfooter.jsp" %> 

First, make sure that you've logged in from cookies, if you can. If the customer e-mail is still null, it means that you're not logged in and should be kicked over to the login page so that you can establish a cookie or create a new account.

Give the customer a link to create a new address, and then list all the addresses that already exist for the customer with edit and delete links. You'll notice that the edit and create links both go to the same JSP file ”this is something that you learn to do after spending some time coding JSP form handlers.

Both create and edit are essentially the same operation. It turns out that you end up maintaining much less code if you combine the two and teach the handler the different actions to take.

Coding NewAddress.jsp (see Listing 11.10) involves a number of little tricks. Again, first make sure that you're logged in.

Figure 11.1. MyAccount.jsp with no addresses.

graphics/11fig01.jpg

Listing 11.10 NewAddress.jsp
 <%@ include file="/jsp/cust/AutoLogin.jsp" %> <%@ page import="com.bfg.customer.Customer" %> <%@ page import="com.bfg.customer.Address" %> <%@ page import="java.text.NumberFormat" %> <jsp:useBean id="customer" class="com.bfg.customer.Customer" scope="session"/> <jsp:useBean id="newaddr" class="com.bfg.customer.Address" scope="request"/> <% if (customer.getEmail() == null) {     response.sendRedirect("Login.jsp");     return; } %> <jsp:setProperty name="newaddr" property="*"/> <% NumberFormat nf = NumberFormat.getInstance(); String operation = request.getParameter("operation"); if (operation == null) {     operation = "create"; } if (request.getParameter("SUBMITTED") != null) {     if (newaddr.validateAddress()) {      if (operation.equals("update")) {          String addrId = request.getParameter("addressId");          if (addrId != null) {              Integer id = null;              try {                  Number num = nf.parse(request.getParameter("addressId"));                  id = new Integer(num.intValue());              }  catch (Exception e) {                  response.sendRedirect("general_error.jsp");                 return;              }              Address addr = (Address) customer.getAddressBook().get(id);              if (addr != null) {                  newaddr.setAddressID(id.intValue());                  newaddr.updateAddress();                  customer.addAddress(newaddr);                  response.sendRedirect("MyAccount.jsp");                  return;              }  else {                   response.sendRedirect("noaccess.jsp");                   return;              }         }  else {              response.sendRedirect("general_error.jsp");         }       }       else {           newaddr.createAddress();           customer.addAddress(newaddr);           response.sendRedirect("MyAccount.jsp");           return;       }     } }  else {     if (operation.equals("update")) {       String addrId = request.getParameter("addressId");       if (addrId != null) {           Integer id = null;         try {             Number num = nf.parse(request.getParameter("addressId"));             id = new Integer(num.intValue());        }  catch (Exception e) {             response.sendRedirect("general_error.jsp");             return;         }         Address addr = (Address) customer.getAddressBook().get(id);         if (addr != null) {             newaddr = addr;         }      }     } } if (newaddr.getLastName() == null) {     newaddr.setLastName(""); } if (newaddr.getFirstName() == null) {     newaddr.setFirstName(""); } if (newaddr.getStreet1() == null) {     newaddr.setStreet1(""); } if (newaddr.getStreet2() == null) {     newaddr.setStreet2(""); } if (newaddr.getCity() == null) {     newaddr.setCity(""); } if  (newaddr.getState() == null) {     newaddr.setState(""); } if (newaddr.getPostalCode() == null) {     newaddr.setPostalCode(""); } %> <% if (operation.equals("update")) { %> <HEAD><TITLE>Edit Address</TITLE></HEAD><BODY> <%     }  else {%> <HEAD><TITLE>Create New Address</TITLE></HEAD><BODY> <% }  %> <%@ include file="/jsp/includes/bfgheader.jsp" %> <% if (operation.equals("update")) { %> <CENTER><H1>Edit Address</H1></CENTER> <%     }  else {%> <CENTER><H1>Create New Address</H1></CENTER> <% }  %> <FORM METHOD=POST ACTION="NewAddress.jsp"> <INPUT TYPE="HIDDEN" NAME="SUBMITTED" VALUE="T"> <INPUT TYPE="HIDDEN" NAME="operation" VALUE="<%= operation %>"> <INPUT TYPE="HIDDEN" NAME="addressId" VALUE="<%= request.getParameter("addressId") %>"> <% if (newaddr.getFieldError("firstName") != null) { %> <FONT COLOR="#FF0000"><%= newaddr.getFieldError("firstName")%></FONT><BR> <% }  %> <% if (newaddr.getFieldError("lastName") != null) { %> <FONT COLOR="#FF0000"><%= newaddr.getFieldError("lastName")%></FONT><BR> <% }  %> First Name: <INPUT NAME="firstName" TYPE="TEXT" SIZE=30        VALUE="<%= newaddr.getFirstName() %>"> Last Name: <INPUT NAME="lastName" TYPE="TEXT" SIZE=40        VALUE="<%= newaddr.getLastName() %>"><BR> <% if (newaddr.getFieldError("street1") != null) { %> <FONT COLOR="#FF0000"><%= newaddr.getFieldError("street1")%></FONT><BR> <% }  %> Street Addr 1: <INPUT NAME="street1" TYPE="TEXT" SIZE=80        VALUE="<%= newaddr.getStreet1() %>"><BR> Street Addr 2: <INPUT NAME="street2" TYPE="TEXT" SIZE=80        VALUE="<%= newaddr.getStreet2() %>"><BR> <% if (newaddr.getFieldError("city") != null) { %> <FONT COLOR="#FF0000"><%= newaddr.getFieldError("city")%></FONT><BR> <% }  %> <% if  (newaddr.getFieldError("state") != null) { %> <FONT COLOR="#FF0000"><%= newaddr.getFieldError("state")%></FONT><BR> <% }  %> <% if (newaddr.getFieldError("postalCode") != null) { %> <FONT COLOR="#FF0000"><%= newaddr.getFieldError("postalCode")%></FONT><BR> <% }  %> City: <INPUT NAME="city" TYPE="TEXT" SIZE=50        VALUE="<%= newaddr.getCity() %>"> State: <INPUT NAME="state" TYPE="TEXT" SIZE=2        VALUE="<%= newaddr.getState() %>"> Postal Code: <INPUT NAME="postalCode" TYPE="TEXT" SIZE=10        VALUE="<%= newaddr.getPostalCode() %>"><BR> <INPUT TYPE=SUBMIT> </FORM> <%@ include file="/jsp/includes/bfgfooter.jsp" %> 

The page first fills in the newaddr object with the values from the form. The first time through, there are no values, of course, but this is harmless in that case.

The parameter operation will be either create or update . If it's blank, the code defaults to create .

If the customer has already submitted the form, the page runs the address validation. If it succeeds, then it checks to see if the customer is updating. If so, it gets the addressId parameter, which is the ID of the address record to be updated. If it's null or it can't be parsed to an integer, it sends the user to a general error page. Note that whenever you do a response.sendRedirect , you should immediately follow it with a return . This will cause the servlet generated from the JSP code to return without processing the rest of the page.

If the page has a valid ID, it looks in the address book of the customer to see if it can find the ID. If so, the page sets the address of the temporary object holding the form values to equal the ID, calls updateAddress to write the new values to the database, and then tells the customer object to use the temporary address object as the new permanent one. Alternatively, the code could have copied all the fields from the temporary address to the address already in the address book, but that involves more code.

If that address ID was not found in the address book, something is wrong. It's possible that someone nasty is typing address book IDs in at random to try to steal information. So, the customer is sent to a "You Don't Have Permission to Look at That" page.

PROTECTING USER INFORMATION

The cardinal rule of e-commerce is this: "Thou shalt not disclose one user's information to another user."

This can take an awful lot of due diligence. You need to make sure that if you ever have to pass something such as an object ID to a form handler, the handler validates that the user has permission to view that data.

In other words, never assume that, just because a page is requesting a piece of information, the page should automatically be granted access to it. The nice thing about session objects is that the client browser has no way to directly change their state, so you can compare a requested operation (such as looking at a specific create card record) with the information stored in the session to determine whether the operation is legal.

In spite of your best efforts, your platform might still undo all your work. On one project I was involved with, a problem with the platform caused it to confuse which user was associated with which session, and it ended up writing address book (and credit card!) entries into the wrong user's account information.

It turned out to be especially pernicious to reproduce as well. It basically came down to, "Okay, Bob, you hit Submit and now hit Back. Now, Jane, hit Submit and now hit Back. Now both hit Submit at once ”3, 2, 1, now!"

Luckily, this was caught during testing instead of production ”yet another strong argument for thorough QA.

If the request is to create instead of update, a call is made to createAddress . Then the address object is added to the customer's address book and they are redirected to the account page.

If this is the customer's first time through the form and it's for updating, the page tries to find the address to be edited in the address book. If it finds it, it sets newaddr to the address, which makes the form fill in from the existing data rather than remain blank.

The next piece of code makes sure that blank entries are presented as "" rather than as null . Then the page presents the appropriate page title and banner based on the operation.

The error flagging follows the model used in NewUser.jsp, in which error messages were displayed before each form field.

After the form has been used to create a few addresses, you can see them in the MyAccount page and edit them, if you want to.

Figure 11.2. The NewAddress.jsp form.

graphics/11fig02.jpg

Figure 11.3. MyAccount.jsp with addresses.

graphics/11fig03.jpg

I l @ ve RuBoard


MySQL and JSP Web Applications. Data-Driven Programming Using Tomcat and MySQL
MySQL and JSP Web Applications: Data-Driven Programming Using Tomcat and MySQL
ISBN: 0672323095
EAN: 2147483647
Year: 2002
Pages: 203
Authors: James Turner

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