package com.oreilly.jent.ejb.beanManaged;

/**
 * In general, you may use the code in this book in your programs and 
 * documentation. You do not need to contact us for permission unless 
 * you're reproducing a significant portion of the code. For example, 
 * writing a program that uses several chunks of code from this book does 
 * not require permission. Selling or distributing a CD-ROM of examples 
 * from O'Reilly books does require permission. Answering a question by 
 * citing this book and quoting example code does not require permission. 
 * Incorporating a significant amount of example code from this book into 
 * your product's documentation does require permission.
 * 
 * We appreciate, but do not require, attribution. An attribution usually 
 * includes the title, author, publisher, and ISBN. For example: 
 * 
 *   "Java Enterprise in a Nutshell, Third Edition, 
 *    by Jim Farley and William Crawford 
 *    with Prakash Malani, John G. Norman, and Justin Gehtland. 
 *    Copyright 2006 O'Reilly Media, Inc., 0-596-10142-2."
 *  
 *  If you feel your use of code examples falls outside fair use or the 
 *  permission given above, feel free to contact us at 
 *  permissions@oreilly.com.
 */

import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.Properties;

import javax.ejb.CreateException;
import javax.ejb.EJBException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.FinderException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import com.oreilly.jent.ejb.DuplicateProfileException;


public class ProfileBean implements EntityBean {
  // Entries in the profile (name/value pairs)
  public Properties mEntries;

  // Our EJB context (non-persistent)
  public EntityContext mContext = null;

  //--------------------------------------------------
  // Entity bean methods
  //--------------------------------------------------

  // EJB activation method.  During activation, create our entry lookup table.
  public void ejbActivate() {
    mEntries = new Properties();
    System.out.println("ProfileBean activated.");
  }

  // Load bean from persistent store.  In this case, we're managing the dbase
  // storage, so we store our profile entries as independent records in a
  // separate "PROFILE_ENTRY" table.
  public void ejbLoad() {
      try {
          String key = (String)mContext.getPrimaryKey();
          loadFromDB(key);
      }
      catch (Exception e) {
          System.out.println("Failed to load ProfileBean: ");
          e.printStackTrace();
          throw new EJBException("ejbLoad failed: ", e);
      }
      System.out.println("ProfileBean load finished.");
  }

  protected void loadFromDB(String key) throws FinderException {
      boolean found = false;
      Connection conn = null;
      Statement s = null;
      try {
          conn = getConnection();
          s = conn.createStatement();
          s.executeQuery("select name from profile where name = '" + key + "'");
          ResultSet rs = s.getResultSet();
          if (rs.next()) {
              found = true;
              s.executeQuery("select key, value from profile_entry where name = '" +
                      key + "'");
              rs = s.getResultSet();
              while (rs.next()) {
                  String pKey = rs.getString(1);
                  String pValue = rs.getString(2);
                  mEntries.put(pKey, pValue);
              }
          }
      }
      catch (SQLException e) {
          throw new FinderException("Failed to load profile entries from DB: " +
                  e.toString());
      }
      catch (NamingException ne) {
          throw new FinderException("Failed to access DataSource from server: " +
                  ne.toString());
      }
      finally {
          try { s.close(); } catch (Exception e) {}
          try { conn.close(); } catch (Exception e) {}
      }
      
      if (!found) {
          throw new FinderException("No profile found for " + key);
      }
  }
  
  // Store bean to persistent store.  Properties are stored as records in the
  // PROFILE_ENTRY table.
  public void ejbStore() {
      String key = (String)mContext.getPrimaryKey();
      Connection conn = null;
      Statement s = null;
      try {
          conn = getConnection();
          s = conn.createStatement();
          // Clear out old profile entries and replace with the current ones
          s.executeUpdate("delete from profile_entry where name = '" + key + "'");
          Enumeration pKeys = mEntries.propertyNames();
          while (pKeys.hasMoreElements()) {
              String pKey = (String)pKeys.nextElement();
              String pValue = mEntries.getProperty(pKey);
              s.executeUpdate("insert into profile_entry (name, key, value) values "
                      + "('" + key + "', '" + pKey + "', '" + pValue + "')");
          }
      }
      catch (Exception e) {
          System.out.println("Failed to store ProfileBean: ");
          e.printStackTrace();
          throw new EJBException("ejbStore failed: ", e);
      }
      finally {
          try { s.close(); } catch (Exception e) {}
          try { conn.close(); } catch (Exception e) {}
      }
      System.out.println("ProfileBean store finished.");
  }
  
  // Remove this named profile from the database.
  public void ejbRemove() {
      // Get this profile's name
      String key = (String)mContext.getPrimaryKey();
      Connection conn = null;
      Statement s = null;
      try {
          conn = getConnection();
          s = conn.createStatement();
          // Clear out any profile entries
          s.executeUpdate("delete from profile_entry where name = '" + key + "'");
          // Clear out the profile itself
          s.executeUpdate("delete from profile where name = '" + key + "'");
          
          System.out.println("ProfileBean removed.");
      }
      catch (SQLException se) {
          System.out.println("Error removing profile for " + key);
          se.printStackTrace();
      }
      catch (NamingException ne) {
          System.out.println("Error accessing DataSource");
          ne.printStackTrace();
      }
      finally {
          try { s.close(); } catch (Exception e) {}
          try { conn.close(); } catch (Exception e) {}
      }
  }

  // When we're passivated, release our entry table.
  public void ejbPassivate() {
    mEntries = null;
    System.out.println("ProfileBean passivated.");
  }

  // Get context from container.
  public void setEntityContext(EntityContext context) {
    mContext = context;
    System.out.println("ProfileBean context set.");
  }

  // Container is removing our context.
  public void unsetEntityContext() {
    mContext = null;
    System.out.println("ProfileBean context unset.");
  }

  //  Since we're managing persistence here in the bean, we need to
  //  implement the finder methods.
  public String ejbFindByPrimaryKey(String key) throws FinderException {
    loadFromDB(key);
    return key;
  }

  public Collection ejbFindByEntryValue(String key, String value)
      throws FinderException {
      LinkedList userList = new LinkedList();
      // Get a new connection from the EJB server
      Connection conn = null;
      Statement s = null;
      try {
          conn = getConnection();
          s = conn.createStatement();
          // Issue a query for matching profile entries, grabbing just the name
          s.executeQuery("select distinct(name) from profile_entry where " +
                         " key = '" + key + "' and value = '" + value + "'");
          // Convert the results in primary keys and return an enumeration
          ResultSet results = s.getResultSet();
          while (results.next()) {
              String name = results.getString(1);
              userList.add(name);
          }
      }
      catch (SQLException se) {
          // Failed to do database lookup
          throw new FinderException();
      }
      catch (NamingException ne) {
          // Failed to access DataSource
          throw new FinderException();
      }
      finally {
          try { s.close(); } catch (Exception e) {}
          try { conn.close(); } catch (Exception e) {}
      }
      return userList;
  }

  // Create method with name of profile owner.
  public String ejbCreate(String name)
      throws DuplicateProfileException, CreateException {
      Connection conn = null;
      Statement s = null;
      try {
          conn = getConnection();
          s = conn.createStatement();
          s.executeUpdate("insert into profile (name) values ('" + name + "')");
      }
      catch (SQLException se) {
          System.out.println("Error creating profile, assuming duplicate.");
          try {
              StringWriter strw = new StringWriter();
              PrintWriter prntw = new PrintWriter(strw);
              se.printStackTrace(prntw);
              throw new DuplicateProfileException(
                      "SQL error creating profile for " +
                      name + ": " + se.toString() +
                      "\n" + strw.toString());
          }
          catch (Exception e) {}
      }
      catch (NamingException ne) {
          System.out.println("Error accessing DataSource");
          throw new CreateException("Error accessing DataSource.");
      }
      finally {
          try { s.close(); } catch (Exception e) {}
          try { conn.close(); } catch (Exception e) {}
      }
      System.out.println("ProfileBean created for " + name + ".");
      return name;
  }

  // Post-creation notification.  Nothing to do here, but we need
  // to provide an implementation.
  public void ejbPostCreate(String name) {
    System.out.println("ProfileBean post-create called for " + name + ".");
  }
   
  //--------------------------------------------------
  // Utility methods
  //--------------------------------------------------

  // Get a connection from our J2EE data source.
  private Connection getConnection() throws SQLException, NamingException {
    Context ctx = new InitialContext();
    DataSource profileDB =
      (DataSource)ctx.lookup("java:comp/env/jdbc/profileDB");
    return profileDB.getConnection();
  }

  //--------------------------------------------------
  // Business methods
  //--------------------------------------------------

  // Returns the name of the owner of this profile.
  public String getName() {
    return (String)mContext.getPrimaryKey();
  }

  // Returns the value of the given entry in this profile, or null
  // if the given property isn't present in this profile.
  public String getEntry(String key) {
    return mEntries.getProperty(key);
  }

  // Sets the value of the property to the given value.
  public void setEntry(String key, String value) {
    if (mEntries == null) {
      mEntries = new Properties();
    }
    mEntries.put(key, value);
  }
}
