Source code for Phonebook.java

package PhoneApp.Data;

// Added: Include ObjectStore functionality.
import com.odi.*;
import com.odi.util.*;
import com.odi.util.query.*;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.Collection;

// Removed: we will use the database for data management rather than the vector
// import java.util.Vector;

/**
 * Stores all phone entries, new and existing. This list should
 * be persistent in some fashion, such that it can be shared,
 * backed up, and retrieved as necessary.
 *
 * @version $Date: 2001/06/25 $
 * @author  Steve Franklin
 * @since   jdk 1.3.1
 */

public class Phonebook extends DataObject {

 /**
  * Constructs a new phone book
  */
  public Phonebook() {
    // Removed: No need for vector, replaced by oodb
    // phoneList = new Vector();

    // Added: Need to initialize database information.
    // The phonebook will still remain as our interface to the
    // data, but its means of managing phonebook entries is shifted
    // to the oodb instead of clumsy vector manipulation
    session = Session.create(null, null);
    session.join();

    // Added: A connection to the database is required before we can
    // do anything within our session.
    try {
      db = Database.open(database, ObjectStore.UPDATE);
    } catch (DatabaseNotFoundException e) {
      db=Database.create(database, ObjectStore.ALL_READ | ObjectStore.ALL_WRITE);
    }

    // Added: All objects are added in the context of roots, i.e. entry
    // points into the database. We will create one entry point for the
    // phonebook entries. This entry points to a map for quick location
    // by key (name).
    // Ref: ObjectStore PSE Pro documentation
    Transaction t = Transaction.begin(ObjectStore.UPDATE);
    try {
      entries = (OSHashMap) db.getRoot("entries");
    } catch (DatabaseRootNotFoundException e) {
      db.createRoot("entries", entries = new OSHashMap());
    }
    t.commit(ObjectStore.RETAIN_READONLY);
  }

  // Added: Need to clean up database resources when we release the phonebook.
 /**
  * Cleans up database resources allocated when the phonebook was created.
  */
  public void close() {
    db.close();
    session.terminate();
  }

 /**
  * Adds a new entry consisting of seven fields required for a phone book
  * entry.
  */
  public void addEntry(String firstName, String lastName, String phoneNumber,
                       String street, int streetNumber, String apartmentNumber,
                       String postalCode) {
    
    PhonebookEntry entry = new PhonebookEntry(
      firstName, lastName, phoneNumber, 
      street, streetNumber, apartmentNumber, postalCode
    );

    if ( entry.isValid() ) {
      // Removed: No need for the vector, replaced by oodb
      // phoneList.add(entry);

      // Added: Need to tie into database functionality in order to persist
      // new objects.
      Transaction t = Transaction.begin(ObjectStore.UPDATE);
      if (entries.get(phoneNumber) != null) {
        t.abort(ObjectStore.RETAIN_READONLY);
        System.out.println("Entry already exists.");
      } else {
        System.out.println("Added " + entry.toString());
        entries.put(phoneNumber, entry);
        t.commit(ObjectStore.RETAIN_READONLY);
      }
    } else {
      debug("Phonebook::addEntry()","Invalid entry");
      debug("Phonebook::addEntry()",entry.toString());
    }
  }

  // Added: Allow for deletion of entries in the phone book.
 /**
  * Deletes an entry from the phone book based on the unique key for
  * that entry.
  */
  public boolean deleteEntry(String key) {
    boolean success = true;
    
    Transaction t = Transaction.begin(ObjectStore.UPDATE);

    PhonebookEntry entry = (PhonebookEntry) entries.get(key);

    if ( entry == null ) {
      System.out.println("Unable to find data associated with " + key);
      success = false;
    } else {
      entries.remove(key);
      ObjectStore.destroy(entry);
    }

    t.commit(ObjectStore.RETAIN_HOLLOW);

    return success;
  }

  // Added: Allow for update of entries in the phone book.
 /**
  * Updates an entry from the phone book based on the unique key for
  * that entry, the specified field, and the new value.
  */
  public boolean updateEntry(String key, String fieldName, String fieldValue) {
    boolean success = true;
    
    Transaction t = Transaction.begin(ObjectStore.UPDATE);

    PhonebookEntry entry = (PhonebookEntry) entries.get(key);

    if ( entry == null ) {
      System.out.println("Unable to find data associated with " + key);
      success = false;
    } else {
      if ( fieldName.equals("firstName") ) { entry.setFirstName(fieldValue); }
      else if ( fieldName.equals("lastName") ) { entry.setLastName(fieldValue); }
      else if ( fieldName.equals("street") ) { entry.setStreet(fieldValue); }
      else if ( fieldName.equals("phoneNumber") ) { entry.setPhoneNumber(fieldValue); }
      else if ( fieldName.equals("apartmentNumber") ) { entry.setApartmentNumber(fieldValue); }
      else if ( fieldName.equals("postalCode") ) { entry.setPostalCode(fieldValue); }
    }

    t.commit(ObjectStore.RETAIN_HOLLOW);

    return success;
  }

  // Added: Allow for query against the phone book.
 /**
  * Queries all entries that have a field matching the specified
  * value.
  */
  public String[] queryEntries(String fieldName, String fieldValue) {
    String[] results;
    if ( fieldName.length() > 0 && fieldValue.length() > 0 && fieldExists(fieldName) ) {
      // The following shows a fixed query. In our case, we need to include
      // variables in the query, determined at runtime. Not unlike dynamic SQL,
      // this scenario needs special attention using Free Variables.
      // Query query = new Query(PhonebookEntry.class, "firstName == \"John\"");

      FreeVariables dynamicVariables = new FreeVariables();
      dynamicVariables.put("fieldValue", String.class);
      Query query = new Query(PhonebookEntry.class, fieldName + " == fieldValue", dynamicVariables);
      FreeVariableBindings binding = new FreeVariableBindings();
      binding.put("fieldValue", fieldValue);

      Transaction t = Transaction.begin(ObjectStore.UPDATE);

      // As described above, use of free variables determined at runtime
      // involves a little bit extra effort.
      // Collection queryResults = query.select(entries.values());

      Collection queryResults = query.select( entries.values(), binding);

      results = new String[queryResults.size()];

      Iterator resultIterator = queryResults.iterator();
      int count = 0;
      while(resultIterator.hasNext()) {
        results[count] = ((PhonebookEntry)resultIterator.next()).toString();
        count++;
      }    

      t.commit(ObjectStore.RETAIN_READONLY);
    } else {
      System.out.println("Invalid input. fieldName and fieldValue must exist and be properly formed.");
      results = new String[1];
    }
    return results;
 }
 
  // Added: Ability to generate a report of entries in the phone book.
 /**
  * Reports on all phonebook entries in short or long form.
  */
  public String[] getEntryReport(int reportType) {
    String results[];

    if ( (reportType != KEY_REPORT) && (reportType != DETAILED_REPORT) ) {
      System.out.println("Error: Unable to identify requested type of report (" + reportType + ").");
      results = new String[1];
      return results;
    }
    
    Transaction t = Transaction.begin(ObjectStore.UPDATE);

    Set keys = entries.keySet();
    
    results = new String[keys.size()];

    Iterator keyIterator = keys.iterator();
    int count = 0;
    while(keyIterator.hasNext()) {
      if ( reportType == DETAILED_REPORT ) {
        PhonebookEntry entry = (PhonebookEntry)entries.get(keyIterator.next());
        results[count] = entry.toString();
      } else if ( reportType == KEY_REPORT ) {
        results[count] = (String)keyIterator.next();
      }
      count++;
    }    
    t.commit(ObjectStore.RETAIN_HOLLOW);

    return results;
  }
 
  // Added: This array of strings provides a convenient means of knowing
  // what fields we can query against. This information is more appropriate
  // in the PhonebookEntry class, but for conservation of space, we will
  // leave it at this level.
 /**
  * Provides a list of queryable fields for phonebook entries.
  */
  public String[] getQueryableFields() {
    return queryFields;
  }
 
  //Added: Test to ensure a fieldName exists within the list
  //of queryable fields.
  public boolean fieldExists(String fieldName) {
    boolean fieldExists = false;
    for (int i=0; i< queryFields.length; i++) {
      if ( fieldName.equals(queryFields[i]) ){
        fieldExists = true;
        break;
      }
    }
    return fieldExists;
  }

  // Removed: No need for a vector, replaced by oodb
  // /** Provides a vector for all phone entries. */
  // private Vector phoneList = null;

  // Added: Need to persist our connection to the OODB
  /** Manages the session connecting to our OODB */
  Session session = null;
  /** Manages the connection to the database */
  Database db = null;
  /** Names the current ObjectStore database containing the data */
  String database = "phones.odb";
  /** Serves as the OODB entry point into the phonebook entries */
  OSHashMap entries = null;

  // Added: Allows us to configure the level of detail in a database report
  /** Generates a report only listing the unique keys for each pieces of data */
  public static final int KEY_REPORT = 1;
  /** Generates a report with a summary of each record in the database */
  public static final int DETAILED_REPORT = 2;
  /** Lists the fields of a phone book entry that can be queried/updated */
  private String[] queryFields = {
    "firstName", "lastName", "phoneNumber",
    "street", "apartmentNumber", "postalCode"
  };
}