/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@ricochet.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.core.impl;

import java.io.*;
import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.util.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.core.internals.*;
import com.silverwrist.venice.db.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.security.*;
import com.silverwrist.venice.svc.SecurityMonitorEnvironment;
import com.silverwrist.venice.util.BuildVCard;

class AdminUserContextImpl implements AdminUserContext
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static Category logger = Category.getInstance(AdminUserContextImpl.class);

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private EnvUser env;                     // the local environment
  private int uid;                         // the user ID of this user
  private int contactid;                   // ID of associated contact information
  private int level;                       // base security level for this user
  private boolean email_verified;          // has email address been verified?
  private boolean lockout;                 // is this account locked out?
  private String username;                 // the user name we're using
  private java.util.Date created;          // when was this user created? (GMT)
  private java.util.Date last_access;      // when did we last log in? (GMT)
  private String description;              // personal description
  private java.sql.Date m_dob;             // date of birth
  private Locale my_locale;                // my default locale (cached)
  private TimeZone my_tz;                  // my default timezone (cached)
  private OptionSet flags = null;          // option flags

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  protected AdminUserContextImpl(Connection conn, EnvUser env, ResultSet rs) throws SQLException
  {
    this.env = env;
    this.uid = rs.getInt("uid");
    this.contactid = rs.getInt("contactid");
    this.level = rs.getInt("base_lvl");
    this.email_verified = rs.getBoolean("verify_email");
    this.lockout = rs.getBoolean("lockout");
    this.username = rs.getString("username");
    this.created = SQLUtil.getFullDateTime(rs,"created");
    this.last_access = SQLUtil.getFullDateTime(rs,"lastaccess");
    this.description = rs.getString("description");
    this.m_dob = rs.getDate("dob");
    this.my_locale = International.get().createLocale(rs.getString("localeid"));
    this.my_tz = TimeZone.getTimeZone(rs.getString("tzid"));

    PreparedStatement stmt = null;

    try
    { // get user properties
      stmt = conn.prepareStatement("SELECT ndx, data FROM propuser WHERE uid = ?;");
      stmt.setInt(1,uid);
      ResultSet rs2 = stmt.executeQuery();
      while (rs2.next())
      { // load the properties...
	switch (rs2.getInt(1))
	{ // based on the property, do what is needed
	  case UserContextImpl.PROP_FLAGS:
	    flags = new OptionSet(rs2.getString(2));
	    break;

	  default:
	    break;

	} // end switch

      } // end while

    } // end try
    finally
    { // close statement to conserve resources
      SQLUtil.shutdown(stmt);

    } // end finally

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal operations
   *--------------------------------------------------------------------------------
   */

  private final void updateProperties(BitSet delta) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    try
    { // get a connection and create a statement
      conn = env.getConnection();

      if (delta.get(UserContextImpl.PROP_FLAGS))
      { // store the flags
	stmt = conn.prepareStatement("UPDATE propuser SET data = ? WHERE uid = ? AND ndx = ?;");
	stmt.setString(1,flags.asString());
	stmt.setInt(2,uid);
	stmt.setInt(3,UserContextImpl.PROP_FLAGS);
	stmt.executeUpdate();

      } // end if

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing user properties: " + e.getMessage(),e);
      throw new DataException("unable to save user properties: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end updateProperties

  private final void updateProperties() throws DataException
  {
    updateProperties(UserContextImpl.ALL_PROPS);

  } // end updateProperties

  private final AdminUserProperties createProperties()
  {
    AdminUserProperties rc = new AdminUserProperties();
    rc.setDisplayPostPictures(flags.get(UserContextImpl.BF_POSTPICTURES));
    rc.setDisallowPhoto(flags.get(UserContextImpl.BF_ADM_NOPHOTO));
    rc.setMassMailOptOut(flags.get(UserContextImpl.BF_MASSMAIL_OPTOUT));
    return rc;

  } // end createProperties

  private final BitSet storeProperties(AdminUserProperties props)
  {
    BitSet rc = new BitSet();

    if (flags.assign(UserContextImpl.BF_POSTPICTURES,props.getDisplayPostPictures()))
      rc.set(UserContextImpl.PROP_FLAGS);
    if (flags.assign(UserContextImpl.BF_ADM_NOPHOTO,props.getDisallowPhoto()))
      rc.set(UserContextImpl.PROP_FLAGS);
    if (flags.assign(UserContextImpl.BF_MASSMAIL_OPTOUT,props.getMassMailOptOut()))
      rc.set(UserContextImpl.PROP_FLAGS);

    return rc;

  } // end storeProperties

  /*--------------------------------------------------------------------------------
   * Implementations from interface AdminUserContext
   *--------------------------------------------------------------------------------
   */

  public int getUID()
  {
    return uid;

  } // end getUID

  public String getUserName()
  {
    return username;

  } // end getUserName

  public int getContactID()
  {
    return contactid;

  } // end getContactID

  public String getDescription()
  {
    return description;

  } // end getDescription

  public void setDescription(String new_descr) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    if (new_descr.equals(description))
      return;

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();
      stmt = conn.prepareStatement("UPDATE users SET description = ? WHERE uid = ?;");
      stmt.setString(1,new_descr);
      stmt.setInt(2,uid);
      stmt.executeUpdate();
      description = new_descr;  // change stored information
      ar = env.newAudit(AuditRecord.ADMIN_ACCOUNT_CHANGE,"uid=" + uid,"field=description");
      
    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error changing description: " + e.getMessage(),e);
      throw new DataException("Unable to set user description: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setDescription

  public int getBaseLevel()
  {
    return level;

  } // end getBaseLevel

  public void setBaseLevel(int new_level) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    if (level==new_level)
      return;

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();
      stmt = conn.prepareStatement("UPDATE users SET base_lvl = ? WHERE uid = ?;");
      stmt.setInt(1,new_level);
      stmt.setInt(2,uid);
      stmt.executeUpdate();
      level = new_level;
      ar = env.newAudit(AuditRecord.ADMIN_SET_SECURITY,"uid=" + uid,"level=" + new_level);
      
    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error changing base level: " + e.getMessage(),e);
      throw new DataException("Unable to set user base level: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setBaseLevel

  public Role getBaseRole()
  {
    return env.getRoleForLevel(level);

  } // end getBaseRole

  public void setBaseRole(Role new_role) throws DataException
  {
    setBaseLevel(new_role.getLevel());

  } // end setBaseRole

  public boolean isEmailVerified()
  {
    return email_verified;

  } // end isEmailVerified

  public void setEmailVerified(boolean flag) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    if (flag==email_verified)
      return;

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();
      stmt = conn.prepareStatement("UPDATE users SET verify_email = ? WHERE uid = ?;");
      stmt.setInt(1,flag ? 1 : 0);
      stmt.setInt(2,uid);
      stmt.executeUpdate();
      email_verified = flag;
      ar = env.newAudit(AuditRecord.ADMIN_ACCOUNT_CHANGE,"uid=" + uid,"field=verify_email");
      
    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error changing verify flag: " + e.getMessage(),e);
      throw new DataException("Unable to set user verify flag: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setEmailVerified

  public boolean isLockedOut()
  {
    return lockout;

  } // end isLockedOut

  public void setLockedOut(boolean flag) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    if (flag==lockout)
      return;

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();
      stmt = conn.prepareStatement("UPDATE users SET lockout = ? WHERE uid = ?;");
      stmt.setInt(1,flag ? 1 : 0);
      stmt.setInt(2,uid);
      stmt.executeUpdate();
      lockout = flag;
      ar = env.newAudit(AuditRecord.ADMIN_LOCK_OUT,"uid=" + uid,flag ? "locked" : "unlocked");
      
    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error changing lockout flag: " + e.getMessage(),e);
      throw new DataException("Unable to set user lockout flag: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setLockedOut

  public ContactInfo getContactInfo() throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("getContactInfo() for UID " + uid);

    ContactInfoImpl rc;
    if (contactid>=0)
      rc = new ContactInfoImpl(env,contactid,true);
    else
      rc = new ContactInfoImpl(uid,true);
    return rc;

  } // end getContactInfo

  public void putContactInfo(ContactInfo ci) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("putContactInfo() for UID " + uid);

    if ((ci.getOwnerUID()!=uid) || (ci.getOwnerCommunityID()>=0))
    { // the contact information is not owned correctly
      logger.error("ContactInfo ownership wrong (it's " + ci.getOwnerUID() + ", " + ci.getOwnerCommunityID()
		   + "), should be (" + uid + ", -1)");
      throw new DataException("invalid contact information record");

    } // end if

    Connection conn = null;  // database connection
    AuditRecord ar = null;   // audit record
      
    try
    { // get a database connection
      conn = env.getConnection();
      Stashable obj = (Stashable)ci;

      // save the contact information
      obj.stash(conn);

      if (contactid<0)
      { // contact being established for the first time
	contactid = ci.getContactID();
	if (logger.isDebugEnabled())
	  logger.debug("...established initial contact (" + contactid + ") for user");

      } // end if

      ar = env.newAudit(AuditRecord.ADMIN_USER_CONTACT_INFO,"uid=" + uid,"contactid=" + contactid);

    } // end try
    catch (ClassCastException cce)
    { // we need to be able to coerce the ContactInfo to a Stashable
      logger.error("ContactInfo needs to be a Stashable for this to work");
      throw new DataException("improper contact information record");

    } // end catch
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error updating contact info: " + e.getMessage(),e);
      throw new DataException("unable to access user contact data: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end if

  } // end putContactInfo

  public void setPassword(String password, String reminder) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();
      stmt = conn.prepareStatement("UPDATE users SET passhash = ?, passreminder = ?, access_tries = 0 WHERE uid = ?;");
      stmt.setString(1,Generator.hashPassword(password));
      stmt.setString(2,reminder);
      stmt.setInt(3,uid);
      stmt.executeUpdate();

      // record an audit record for this user
      ar = env.newAudit(AuditRecord.ADMIN_PASSWORD_CHANGE,"uid=" + uid);
      
    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error changing password: " + e.getMessage(),e);
      throw new DataException("Unable to set user password: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setPassword

  public Locale getLocale()
  {
    return my_locale;

  } // end getLocale

  public void setLocale(Locale locale) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();

      // create the update statement
      stmt = conn.prepareStatement("UPDATE userprefs SET localeid = ? WHERE uid = ?;");
      stmt.setString(1,locale.toString());
      stmt.setInt(2,uid);

      // execute the statement
      stmt.executeUpdate();

      // replace the locale here
      my_locale = locale;
      ar = env.newAudit(AuditRecord.ADMIN_ACCOUNT_CHANGE,"uid=" + uid,"field=localeid");

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error setting user locale: " + e.getMessage(),e);
      throw new DataException("unable to set user locale: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setLocale

  public TimeZone getTimeZone()
  {
    return my_tz;

  } // end getTimeZone

  public void setTimeZone(TimeZone timezone) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();

      // create the update statement
      stmt = conn.prepareStatement("UPDATE userprefs SET tzid = ? WHERE uid = ?;");
      stmt.setString(1,timezone.getID());
      stmt.setInt(2,uid);

      // execute the statement
      stmt.executeUpdate();

      // replace the locale here
      my_tz = timezone;
      ar = env.newAudit(AuditRecord.ADMIN_ACCOUNT_CHANGE,"uid=" + uid,"field=tzid");

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error setting user timezone: " + e.getMessage(),e);
      throw new DataException("unable to set user timezone: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setTimeZone

  public java.util.Date getCreationDate()
  {
    return created;

  } // end getCreationDate

  public java.util.Date getLastAccessDate()
  {
    return last_access;

  } // end getLastAccessDate

  public AdminUserProperties getProperties() throws DataException
  {
    return createProperties();

  } // end getProperties

  public void setProperties(AdminUserProperties props) throws DataException
  {
    AdminUserProperties orig = createProperties();
    BitSet delta = storeProperties(props);
    if (delta.length()==0)
      return;

    boolean succeeded = false;
    try
    { // update the properties in the database
      updateProperties(delta);
      succeeded = true;

    } // end try
    finally
    { // make sure the connection is released before we go
      if (!succeeded)
	storeProperties(orig);

    } // end finally

  } // end setProperties

  public void export(Writer xml) throws IOException, DataException
  {
    // get the community-level security monitor
    SecurityMonitorEnvironment sm_env =
        (SecurityMonitorEnvironment)(env.getGlobalSite().queryService(SecurityMonitorEnvironment.class));
    SecurityMonitor cmon = sm_env.getMonitor("Community");

    // get the contact information
    ContactInfoImpl ci = new ContactInfoImpl(env,contactid,true);

    // some things we need a database connection for...
    String hash = null, reminder = null;
    List join_comms = null;
    Connection conn = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;
    try
    { // get the password and password reminder
      conn = env.getConnection();
      stmt = conn.prepareStatement("SELECT passhash, passreminder FROM users WHERE uid = ?;");
      stmt.setInt(1,uid);
      rs = stmt.executeQuery();
      if (!(rs.next()))
	throw new DataException("internal error: can\'t find user record for uid " + uid);
      hash = rs.getString(1);
      reminder = rs.getString(2);

      // get the list of communities of which we're a member
      rs.close();
      stmt.close();
      stmt = conn.prepareStatement("SELECT s.alias, m.granted_lvl FROM sigmember m, sigs s "
				   + "WHERE s.sigid = m.sigid AND m.uid = ?;");
      stmt.setInt(1,uid);
      rs = stmt.executeQuery();
      while (rs.next())
      { // load alias and grant levels
	Role r = cmon.getRoleForLevel(rs.getInt(2));
	Object[] foo = new Object[] { rs.getString(1), r };
	if (join_comms==null)
	  join_comms = new ArrayList();
	join_comms.add(foo);

      } // end while

      if (join_comms==null)
	join_comms = Collections.EMPTY_LIST;
      
    } // end try
    catch (SQLException e)
    { // translate the SQLException
      throw new DataException("Database error: " + e.getMessage(),e);

    } // end catch
    finally
    { // shut down everything
      SQLUtil.shutdown(rs);
      SQLUtil.shutdown(stmt);
      SQLUtil.shutdown(conn);

    } // end finally

    xml.write("<venice-user id=\"" + uid + "\">\n<username>" + username
	      + "</username>\n<password prehashed=\"true\">" + hash + "</password>\n");
    if (StringUtil.isStringEmpty(reminder))
      xml.write("<password-reminder/>\n");
    else
      xml.write("<password-reminder>" + reminder + "</password-reminder>\n");
    if (StringUtil.isStringEmpty(description))
      xml.write("<description/>\n");
    else
      xml.write("<description>" + description + "</description>\n");
    xml.write("<options confirmed=\"");
    xml.write(email_verified ? "true" : "false");
    xml.write("\" locked=\"");
    xml.write(lockout ? "true" : "false");
    xml.write("\" role=\"");
    xml.write(env.getRoleForLevel(level).getSymbol());
    xml.write("\" hideaddr=\"");
    xml.write(ci.getPrivateAddress() ? "true" : "false");
    xml.write("\" hidephone=\"");
    xml.write(ci.getPrivatePhone() ? "true" : "false");
    xml.write("\" hidefax=\"");
    xml.write(ci.getPrivateFax() ? "true" : "false");
    xml.write("\" hideemail=\"");
    xml.write(ci.getPrivateEmail() ? "true" : "false");
    xml.write("\" autojoin=\"");
    xml.write(email_verified ? "true" : "false");
    xml.write("\" postpictures=\"");
    xml.write(flags.get(UserContextImpl.BF_POSTPICTURES) ? "true" : "false");
    xml.write("\" optout=\"");
    xml.write(flags.get(UserContextImpl.BF_ADM_NOPHOTO) ? "true" : "false");
    xml.write("\" nophoto=\"");
    xml.write(flags.get(UserContextImpl.BF_MASSMAIL_OPTOUT) ? "true" : "false");
    xml.write("\" locale=\"");
    xml.write(my_locale.toString());
    xml.write("\" zonehint=\"");
    xml.write(my_tz.getID());
    xml.write("\" />\n");

    // get the vCard info and serialize it
    BuildVCard bvc = ci.getVCardBuilder();
    bvc.setBirthday(m_dob);
    bvc.setTimeZone(my_tz);
    bvc.create().exportXML(xml);

    for (Iterator it=join_comms.iterator(); it.hasNext(); )
    { // add <join/> elements for communities user is a member of
      Object[] foo = (Object[])(it.next());
      xml.write("<join role=\"");
      xml.write(((Role)(foo[1])).getSymbol());
      xml.write("\">");
      xml.write(foo[0].toString());
      xml.write("</join>\n");

    } // end for

    // all done!
    xml.write("</venice-user>\n");

  } // end export

  public java.sql.Date getDateOfBirth()
  {
    return m_dob;

  } // end getDateOfBirth

  public void setDateOfBirth(java.sql.Date date) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;
    AuditRecord ar = null;

    if (((date==null) && (m_dob==null)) || ((date!=null) && (m_dob!=null) && date.equals(m_dob)))
      return;  // equal dates

    try
    { // retrieve a connection from the data pool
      conn = env.getConnection();
      stmt = conn.prepareStatement("UPDATE users SET dob = ? WHERE uid = ?;");
      stmt.setDate(1,date);
      stmt.setInt(2,uid);
      stmt.executeUpdate();
      m_dob = date;  // change stored information
      ar = env.newAudit(AuditRecord.ADMIN_ACCOUNT_CHANGE,"uid=" + uid,"field=dob");
      
    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error changing date of birth: " + e.getMessage(),e);
      throw new DataException("Unable to set user date of birth: " + e.getMessage(),e);

    } // end catch
    finally
    { // make sure the connection is released before we go
      SQLUtil.shutdown(stmt);
      AuditRecord.store(conn,ar);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end setDateOfBirth

  /*--------------------------------------------------------------------------------
   * Package-level static operations
   *--------------------------------------------------------------------------------
   */

  static AdminUserContext getAdminUserContext(EnvUser env, int uid) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.prepareStatement("SELECT * FROM users INNER JOIN userprefs ON users.uid = userprefs.uid "
				   + "WHERE users.uid = ?;");
      stmt.setInt(1,uid);
      ResultSet rs = stmt.executeQuery();
      if (!(rs.next()))
	throw new DataException("The user with UID #" + uid + " was not found.");
      if (rs.getBoolean("is_anon"))
	throw new DataException("Cannot modify the defaults for the anonymous user.");

      return new AdminUserContextImpl(conn,env,rs);

    } // end try
    catch (SQLException e)
    { // we encountered an error!
      logger.error("DB exception in getAdminUserContext: " + e.getMessage(),e);
      throw new DataException("Unable to load context for user: " + e.getMessage());

    } // end catch
    finally
    { // release the connection where necessary
      SQLUtil.shutdown(stmt);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end getAdminUserContext

  static AdminUserContext getAdminUserContext(EnvUser env, String username) throws DataException
  {
    Connection conn = null;
    PreparedStatement stmt = null;

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.prepareStatement("SELECT * FROM users INNER JOIN userprefs ON users.uid = userprefs.uid "
				   + "WHERE users.username = ?;");
      stmt.setString(1,username);
      ResultSet rs = stmt.executeQuery();
      if (!(rs.next()))
	throw new DataException("The user '" + username + "' was not found.");
      if (rs.getBoolean("is_anon"))
	throw new DataException("Cannot modify the defaults for the anonymous user.");

      return new AdminUserContextImpl(conn,env,rs);

    } // end try
    catch (SQLException e)
    { // we encountered an error!
      logger.error("DB exception in getAdminUserContext: " + e.getMessage(),e);
      throw new DataException("Unable to load context for user: " + e.getMessage());

    } // end catch
    finally
    { // release the connection where necessary
      SQLUtil.shutdown(stmt);
      SQLUtil.shutdown(conn);

    } // end finally

  } // end getAdminUserContext

} // end class AdminUserContextImpl
