/*
 * 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@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.core.impl;

import java.lang.ref.*;
import java.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.util.OptionSet;
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.AuditRecord;

class ConferenceCoreData implements ConferenceData
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  // Property indexes
  private static final int PROP_FLAGS = 0;    // flags

  private static final BitSet ALL_PROPS;      // BitSet representing all properties

  // Boolean property selectors
  private static final int BP_POSTPICTURES = 0;

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

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

  private EnvConferenceData env;           // the environment
  private int confid;                      // ID of this conference
  private java.util.Date create_date;      // creation date of this conference
  private java.util.Date last_update;      // last update date of conference
  private int read_level;                  // access level required to read conference contents
  private int post_level;                  // access level required to post messages
  private int create_level;                // access level required to create new topics
  private int hide_level;                  // access level required to hide posts/archive topics
  private int nuke_level;                  // access level required to delete topics/scribble/nuke posts
  private int change_level;                // access level required to modify conference profile
  private int delete_level;                // access level required to delete conference
  private short top_topic;                 // the highest topic number in the conference
  private String name;                     // the name of the conference
  private String description;              // the conference's description
  private String cached_alias = null;      // the cached alias (for getAnAlias)
  private boolean creating_topic = false;  // is somebody creating a topic?
  private boolean deleted = false;         // has this conference been deleted?
  private OptionSet flags;                 // option flags
  private boolean has_custom = false;      // do we have custom blocks?
  private SoftReference cust_top = null;   // custom block for top
  private SoftReference cust_bottom = null; // custom block for bottom

  /*--------------------------------------------------------------------------------
   * Constructors
   *--------------------------------------------------------------------------------
   */

  ConferenceCoreData(EnvEngine env, int confid) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("new ConferenceCoreData for conference " + confid);
    this.env = new EnvConferenceData(env);
    this.confid = confid;

    Connection conn = null;
    Statement stmt = null;

    try
    { // get a database connection from this object
      conn = env.getConnection();

      // get the conference basic data from the database
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT * FROM confs WHERE confid = ");
      sql.append(confid).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
	throw new DataException("Conference #" + confid + " does not exist in the database.");

      loadData(rs);  // load the conference data
      SQLUtil.shutdown(rs);
      flags = new OptionSet();
      loadProperties(conn);
      loadCustom(conn);

    } // end try
    catch (SQLException e)
    { // translate into a nicer DataException
      logger.error("DB error reading conference data: " + e.getMessage(),e);
      throw new DataException("unable to load conference data: " + e.getMessage(),e);

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

    } // end finally

  } // end constructor

  protected ConferenceCoreData(EnvCommunityData env, int confid, java.util.Date created, int[] levels,
			       String name, String descr)
  {
    if (logger.isDebugEnabled())
      logger.debug("new ConferenceCoreData for NEW conference " + confid);
    this.env = new EnvConferenceData(env);
    this.confid = confid;
    this.create_date = created;
    this.last_update = null;
    this.read_level = levels[0];
    this.post_level = levels[1];
    this.create_level = levels[2];
    this.hide_level = levels[3];
    this.nuke_level = levels[4];
    this.change_level = levels[5];
    this.delete_level = levels[6];
    this.top_topic = 0;
    this.name = name;
    this.description = descr;
    this.flags = new OptionSet();
    if (env.getCommunityData().getParamBoolean(CommunityDataBackend.BP_POSTPICTURES))
      flags.set(BP_POSTPICTURES);

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Internal functions
   *--------------------------------------------------------------------------------
   */

  private synchronized final void loadData(ResultSet rs) throws SQLException
  {
    // "confid" field is skipped
    create_date = SQLUtil.getFullDateTime(rs,"createdate");
    last_update = SQLUtil.getFullDateTime(rs,"lastupdate");
    read_level = rs.getInt("read_lvl");
    post_level = rs.getInt("post_lvl");
    create_level = rs.getInt("create_lvl");
    hide_level = rs.getInt("hide_lvl");
    nuke_level = rs.getInt("nuke_lvl");
    change_level = rs.getInt("change_lvl");
    delete_level = rs.getInt("delete_lvl");
    top_topic = rs.getShort("top_topic");
    name = rs.getString("name");
    description = rs.getString("descr");
    // "icon_url" and "color" fields are skipped

  } // end loadData

  private synchronized final void loadProperties(Connection conn) throws SQLException
  {
    Statement stmt = null;

    try
    { // load the properties
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT ndx, data FROM propconf WHERE confid = ");
      sql.append(confid).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      while (rs.next())
      { // load the property records one at a time
	switch (rs.getInt(1))
	{ // figure out which properties to load
	  case PROP_FLAGS:
	    flags.assign(rs.getString(2));
	    break;

	  default:
	    break;

	} // end switch

      } // end while

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

    } // end finally

  } // end loadProperties

  private synchronized final void loadCustom(Connection conn) throws SQLException
  {
    Statement stmt = null;

    try
    { // retrieve the custom HTML blocks
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT htmltop, htmlbottom FROM confcustom WHERE confid = ");
      sql.append(confid).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (rs.next())
      { // load the custom data and save it as SoftReferences (so it can be erased 
	has_custom = true;
	String s;
	if ((cust_top==null) || (cust_top.get()==null))
	{ // retrieve the top custom block
	  s = rs.getString(1);
	  if (s!=null)
	    cust_top = new SoftReference(s);

	} // end if

	if ((cust_bottom==null) || (cust_bottom.get()==null))
	{ // retrieve the bottom custom block
	  s = rs.getString(2);
	  if (s!=null)
	    cust_bottom = new SoftReference(s);

	} // end if

      } // end if

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

    } // end finally

  } // end loadCustom

  private synchronized final void touchUpdate(Connection conn) throws SQLException
  {
    Statement stmt = null;

    try
    { // set the last update field
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE confs SET lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE confid = ").append(confid).append(';');
      stmt.executeUpdate(sql.toString());
      last_update = now;

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

    } // end finally

  } // end touchUpdate

  private final void newProperties(Connection conn) throws SQLException
  {
    Statement stmt = null;

    try
    { // store off the properties
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("INSERT INTO propconf (confid, ndx, data) VALUES ");

      // Append Property 0 - the "flags" property.
      sql.append('(').append(confid).append(", ").append(PROP_FLAGS).append(", '");
      sql.append(flags.asString()).append("')");

      // Finish up.
      sql.append(';');
      stmt.executeUpdate(sql.toString());

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

    } // end finally

  } // end newProperties

  private synchronized final void updateProperties(BitSet delta) throws DataException
  {
    Connection conn = null;
    Statement stmt = null;

    try
    { // get a connection and create a statement
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer();

      if (delta.get(PROP_FLAGS))
      { // store the flags
	sql.append("UPDATE propconf SET data = '").append(flags.asString()).append("' WHERE confid = ");
	sql.append(confid).append(" AND ndx = ").append(PROP_FLAGS).append(';');
	stmt.executeUpdate(sql.toString());
	sql.setLength(0);

      } // end if

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

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

    } // end finally

  } // end updateProperties

  private synchronized final void updateProperties() throws DataException
  {
    updateProperties(ALL_PROPS);

  } // end updateProperties

  private final ConferenceProperties createProperties()
  {
    ConferenceProperties rc = new ConferenceProperties();
    rc.setDisplayPostPictures(flags.get(BP_POSTPICTURES));
    return rc;

  } // end createProperties

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

    if (flags.assign(BP_POSTPICTURES,props.getDisplayPostPictures()))
      rc.set(PROP_FLAGS);

    return rc;

  } // end storeProperties

  /*--------------------------------------------------------------------------------
   * Implementations from interface ConferenceData
   *--------------------------------------------------------------------------------
   */

  public int getID()
  {
    return confid;

  } // end getID

  public String getName()
  {
    return name;

  } // end getName
 
  public String getDescription()
  {
    return description;

  } // end getDescription

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

  } // end getCreationDate

  public java.util.Date getLastUpdateDate()
  {
    return last_update;

  } // end getLastUpdate

  public List getAliases() throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;
    Statement stmt = null;
    ArrayList rc = new ArrayList();

    try
    { // get a database connection from this object
      conn = env.getConnection();

      // get a list of all aliases
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT alias FROM confalias WHERE confid = ");
      sql.append(confid).append(" ORDER BY alias;");

      // execute the query
      ResultSet rs = stmt.executeQuery(sql.toString());
      while (rs.next())
	rc.add(rs.getString(1));

    } // end try
    catch (SQLException e)
    { // translate into a nicer DataException
      logger.error("DB error reading conference data: " + e.getMessage(),e);
      throw new DataException("unable to load conference data: " + e.getMessage(),e);

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

    } // end finally

    return Collections.unmodifiableList(rc);

  } // end getAlias

  public List getHosts() throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;
    Statement stmt = null;
    ArrayList rc = new ArrayList();

    try
    { // get a database connection from this object
      conn = env.getConnection();

      // get a list of all hosts (with user info)
      stmt = conn.createStatement();
      StringBuffer sql =
	  new StringBuffer("SELECT u.uid, u.username, u.description, c.given_name, c.family_name, "
			   + "c.locality, c.region, c.country FROM users u, contacts c, confmember m "
			   + "WHERE u.contactid = c.contactid AND u.uid = m.uid AND m.confid = ");
      sql.append(confid).append(" AND m.granted_lvl >= ");
      sql.append(env.getDefaultRole("Conference.HostPrivs").getLevel()).append(" ORDER BY u.username;");

      // execute the query
      ResultSet rs = stmt.executeQuery(sql.toString());
      while (rs.next())
      { // create the UserFound 
	UserFound uf = new UserFoundImpl(rs.getInt(1),rs.getString(2),rs.getString(3),rs.getString(4),
					 rs.getString(5),rs.getString(6),rs.getString(7),rs.getString(8));
	rc.add(uf);

      } // end while

    } // end try
    catch (SQLException e)
    { // translate into a nicer DataException
      logger.error("DB error reading conference hosts: " + e.getMessage(),e);
      throw new DataException("unable to load conference hosts: " + e.getMessage(),e);

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

    } // end finally

    return Collections.unmodifiableList(rc);

  } // end getHosts

  public boolean canReadConference(int level)
  {
    if (deleted)
      return false;
    return (level>=read_level);

  } // end canReadConference
    
  public boolean canPostToConference(int level)
  {
    if (deleted)
      return false;
    return (level>=post_level);

  } // end canReadConference

  public boolean canCreateTopic(int level)
  {
    if (deleted)
      return false;
    return (level>=create_level);

  } // end canCreateTopic

  public boolean canChangeConference(int level)
  {
    if (deleted)
      return false;
    return (level>=change_level);

  } // end canChangeConference

  public int getReadLevel()
  {
    return read_level;

  } // end getReadLevel

  public int getPostLevel()
  {
    return post_level;

  } // end getPostLevel

  public int getCreateLevel()
  {
    return create_level;

  } // end getCreateLevel

  public int getHideLevel()
  {
    return hide_level;

  } // end getHideLevel

  public int getNukeLevel()
  {
    return nuke_level;

  } // end getNukeLevel

  public int getChangeLevel()
  {
    return change_level;

  } // end getChangeLevel

  public int getDeleteLevel()
  {
    return delete_level;

  } // end getDeleteLevel

  public synchronized void setSecurityLevels(EnvCommunity outer, int read, int post, int create, int hide,
					     int nuke, int change, int delete) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

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

      // create the SQL statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE confs SET read_lvl = ");
      sql.append(read).append(", post_lvl = ").append(post).append(", create_lvl = ").append(create);
      sql.append(", hide_lvl = ").append(hide).append(", nuke_lvl = ").append(nuke).append(", change_lvl = ");
      sql.append(change).append(", delete_lvl = ").append(delete).append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE confid = ").append(confid).append(';');

      // execute the update
      stmt.executeUpdate(sql.toString());

      // save copies of the data in our local variables
      read_level = read;
      post_level = post;
      create_level = create;
      hide_level = hide;
      nuke_level = nuke;
      change_level = change;
      delete_level = delete;
      last_update = now;

      // create an audit record reflecting the change
      ar = outer.newAudit(AuditRecord.CONF_SECURITY,"conf=" + confid);

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

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

    } // end finally

  } // end setSecurityLevels

  public synchronized void setName(EnvCommunity outer, String val) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
    AuditRecord ar = null;   // audit record
      
    try
    { // get a database connection
      conn = env.getConnection();
    
      // create the SQL statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE confs SET name = '");
      sql.append(SQLUtil.encodeString(val)).append("', lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE confid = ").append(confid).append(';');

      // execute the update
      stmt.executeUpdate(sql.toString());

      // save copies of the data in our local variables
      name = val;
      last_update = now;

      // create an audit record reflecting the change
      ar = outer.newAudit(AuditRecord.CONF_SECURITY,"conf=" + confid);

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

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

    } // end finally

  } // end setName

  public synchronized void setDescription(String val) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();

      // create the SQL statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE confs SET descr = '");
      sql.append(SQLUtil.encodeString(val)).append("', lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE confid = ").append(confid).append(';');

      // execute the update
      stmt.executeUpdate(sql.toString());

      // save copies of the data in our local variables
      description = val;

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

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

    } // end finally

  } // end setDescription

  public synchronized void addAlias(EnvCommunity outer, String alias) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

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

      stmt = conn.createStatement();
      stmt.executeUpdate("LOCK TABLES confalias WRITE;");
      try
      { // first check to see if the alias already exists
	StringBuffer sql = new StringBuffer("SELECT confid FROM confalias WHERE alias = '");
	String alias_encode = SQLUtil.encodeString(alias);
	sql.append(alias_encode).append("';");
	ResultSet rs = stmt.executeQuery(sql.toString());
	if (rs.next())
	{ // already exists...but is it in this conference or a different one?
	  if (confid==rs.getInt(1))
	    return;  // this conference - this is a no-op
	  throw new DataException("The alias \"" + alias + "\" is already in use by a different conference.");

	} // end if

	SQLUtil.shutdown(rs);

	// now add it!
	sql.setLength(0);
	sql.append("INSERT INTO confalias (confid, alias) VALUES (").append(confid).append(", '");
	sql.append(alias_encode).append("');");
	stmt.executeUpdate(sql.toString());

      } // end try
      finally
      { // make sure we unlock the tables before we go
	SQLUtil.unlockTables(conn);

      } // end finally

      // set the database's update date and generate a new audit record
      touchUpdate(conn);
      ar = outer.newAudit(AuditRecord.CONF_ALIAS,"conf=" + confid,"add=" + alias);
    
    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing conference alias: " + e.getMessage(),e);
      throw new DataException("unable to save conference alias: " + e.getMessage(),e);

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

    } // end finally

  } // end addAlias

  public synchronized void removeAlias(EnvCommunity outer, String alias) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
    AuditRecord ar = null;   // audit record
      
    try
    { // get a database connection
      conn = env.getConnection();
      boolean did_it = false;

      stmt = conn.createStatement();
      stmt.executeUpdate("LOCK TABLES confalias WRITE;");
      try
      { // first check to see how many aliases there are
	StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM confalias WHERE confid = ");
	sql.append(confid).append(';');
	ResultSet rs = stmt.executeQuery(sql.toString());
	if (!(rs.next()))
	  throw new InternalStateError("removeAlias query screwup - must be at least ONE row!");
	int alias_count = rs.getInt(1);
	if (alias_count<=0)
	  throw new InternalStateError("removeAlias database failure - detected less than one alias!");

	if (alias_count==1)
	{ // OK, there's only one alias - make sure it ain't the one we're trying to remove!
	  sql.setLength(0);
	  sql.append("SELECT alias FROM confalias WHERE confid = ").append(confid).append(" AND alias = '");
	  sql.append(SQLUtil.encodeString(alias)).append("';");
	  rs = stmt.executeQuery(sql.toString());
	  if (rs.next())  // it is - that would be bad, throw an error!
	    throw new DataException("The conference must have at least one alias.");
	  return;  // but if it isn't, we're not going to succeed in removing it anyway

	} // end if

	SQLUtil.shutdown(rs);

	// OK, go delete the row (or not, if this alias is not in the table)
	sql.setLength(0);
	sql.append("DELETE FROM confalias WHERE confid = ").append(confid).append(" AND alias = '");
	sql.append(SQLUtil.encodeString(alias)).append("';");
	did_it = (stmt.executeUpdate(sql.toString())>0);

      } // end try
      finally
      { // make sure we unlock the tables before we go
	SQLUtil.unlockTables(conn);

      } // end finally

      if (did_it)
      { // set the database's update date and generate a new audit record
	touchUpdate(conn);
	if ((cached_alias!=null) && cached_alias.equals(alias))
	  cached_alias = null;  // also release the cached alias and force a re-get
	ar = outer.newAudit(AuditRecord.CONF_ALIAS,"conf=" + confid,"remove=" + alias);

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

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

    } // end finally

  } // end removeAlias

  public synchronized void setMembership(EnvCommunity outer, int uid, int grant_level) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
    AuditRecord ar = null;   // audit record
      
    try
    { // get a database connection
      conn = env.getConnection();
      boolean did_it = false;

      stmt = conn.createStatement();
      stmt.executeUpdate("LOCK TABLES confmember WRITE;");
      try
      { // how we go about this depends on whether we're adding or removing
	StringBuffer sql = new StringBuffer();
	if (grant_level>=0)
	{ // see whether the user is in the table yet...
	  sql.append("SELECT granted_lvl FROM confmember WHERE confid = ").append(confid);
	  sql.append(" AND uid = ").append(uid).append(';');
	  ResultSet rs = stmt.executeQuery(sql.toString());
	  sql.setLength(0);

	  if (rs.next())
	  { // just poke the new level value in
	    sql.append("UPDATE confmember SET granted_lvl = ").append(grant_level).append(" WHERE confid = ");
	    sql.append(confid).append(" AND uid = ").append(uid).append(';');

	  } // end if
	  else
	  { // need to insert a new row...
	    sql.append("INSERT INTO confmember (confid, uid, granted_lvl) VALUES (").append(confid);
	    sql.append(", ").append(uid).append(", ").append(grant_level).append(");");

	  } // end else

	  SQLUtil.shutdown(rs);
	  stmt.executeUpdate(sql.toString());
	  did_it = true;

	} // end if
	else
	{ // just try to delete this row from the table (if possible)
	  sql.append("DELETE FROM confmember WHERE confid = ").append(confid).append(" AND uid = ");
	  sql.append(uid).append(';');
	  did_it = (stmt.executeUpdate(sql.toString())>0);

	} // end else

      } // end try
      finally
      { // make sure we unlock the tables before we go
	SQLUtil.unlockTables(conn);

      } // end finally

      if (did_it)
      { // set the database's update date and generate a new audit record
	touchUpdate(conn);
	ar = outer.newAudit(AuditRecord.CONF_MEMBERSHIP,"conf=" + confid,"uid=" + uid,"level=" + grant_level);

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

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

    } // end finally

  } // end setMembership

  public boolean canHideTopics(int level)
  {
    if (deleted)
      return false;
    return (level>=hide_level);

  } // end canHideTopics

  public synchronized String getAnAlias() throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    if (cached_alias!=null)
      return cached_alias;

    Connection conn = null;
    Statement stmt = null;

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // create the SQL statement and execute it
      StringBuffer sql = new StringBuffer("SELECT alias FROM confalias WHERE confid = ");
      sql.append(confid).append(" LIMIT 1;");
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
	throw new DataException("unable to find alias for conference");
      cached_alias = rs.getString(1);
      return cached_alias;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error retrieving conference data: " + e.getMessage(),e);
      throw new DataException("unable to get conference data: " + e.getMessage(),e);

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

    } // end finally

  } // end getAnAlias

  public synchronized ReturnTopicInfo createNewTopic(EnvCommunity outer, String title, String pseud,
						     String body, int body_lines) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;    // database connection
    Statement stmt = null;
    AuditRecord ar = null;     // audit record
    short new_topic_num;       // sequential number of the new topic
    int new_topic_id;          // ID of the new topic
    java.util.Date creation;   // creation date for new topic

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // lock the tables we need to use so we can update them
      stmt.executeUpdate("LOCK TABLES confs WRITE, topics WRITE, posts WRITE, postdata WRITE;");

      try
      { // determine the data for the initial topic
	new_topic_num = (short)(top_topic + 1);

	// add the topic row to the database
	StringBuffer sql = new StringBuffer("INSERT INTO topics (confid, num, creator_uid, createdate, "
					    + "lastupdate, name) VALUES (");
	sql.append(confid).append(", ").append(new_topic_num).append(", ").append(outer.getUserID());
	creation = new java.util.Date();
	String now_str = SQLUtil.encodeDate(creation);
	sql.append(", '").append(now_str).append("', '").append(now_str).append("', '").append(title);
	sql.append("');");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// get the topic ID we just inserted
	ResultSet rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
	if (!(rs.next()))
	  throw new InternalStateError("createNewTopic() could not get back inserted Topic ID");
	new_topic_id = rs.getInt(1);
	SQLUtil.shutdown(rs);

	// insert the "header" for the "zero post" in the topic
	sql.setLength(0);
	sql.append("INSERT INTO posts (topicid, num, linecount, creator_uid, posted, pseud) VALUES (");
	sql.append(new_topic_id).append(", 0, ").append(body_lines).append(", ").append(outer.getUserID());
	sql.append(", '").append(now_str).append("', '").append(pseud).append("');");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// get the ID of the zero post
	rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
	if (!(rs.next()))
	  throw new InternalStateError("createNewTopic() could not get back inserted zero post ID");
	long zero_post_id = rs.getLong(1);
	SQLUtil.shutdown(rs);

	// insert the post data
	sql.setLength(0);
	sql.append("INSERT INTO postdata (postid, data) VALUES (").append(zero_post_id).append(", '");
	sql.append(body).append("');");
	stmt.executeUpdate(sql.toString());

	// touch the "conference" entry to reflect the update
	sql.setLength(0);
	sql.append("UPDATE confs SET lastupdate = '").append(now_str).append("', top_topic = ");
	sql.append(new_topic_num).append(" WHERE confid = ").append(confid).append(';');
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// touch the local variables, too
	top_topic = new_topic_num;
	last_update = creation;

	// create an audit record indicating we were successful
	ar = outer.newAudit(AuditRecord.CREATE_TOPIC,"confid=" + confid,"num=" + new_topic_num,
			    "title=" + title);

      } // end try
      finally
      { // we need to unlock the tables before we go
	SQLUtil.unlockTables(conn);

      } // end finally

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

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

    } // end finally

    // we have three pieces of data to return, so we need a temporary object
    return new ReturnTopicInfo(new_topic_id,new_topic_num,creation);

  } // end createNewTopic

  public boolean canScribblePosts(int level)
  {
    if (deleted)
      return false;
    return (level>=nuke_level);

  } // end canScribblePosts

  public boolean canNukePosts(int level)
  {
    if (deleted)
      return false;
    return (level>=nuke_level);

  } // end canNukePosts

  public synchronized void touchUpdate(Connection conn, java.util.Date date) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Statement stmt = null;

    try
    { // update the last update date
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE confs SET lastupdate = '");
      sql.append(SQLUtil.encodeDate(date)).append("' WHERE confid = ").append(confid).append(';');
      stmt.executeUpdate(sql.toString());
      last_update = date;

    } // end try
    catch (SQLException e)
    { // convert the SQLException
      throw new DataException("Database error updating conference: " + e.getMessage(),e);

    } // end catch
    finally
    { // shut down the statement to conserve resources
      SQLUtil.shutdown(stmt);

    } // end finally

  } // end touchUpdate

  public synchronized short enterCreateTopic()
  {
    while (creating_topic)
    { // we're ready to create a topic
      try
      { // wait for another thread to relinquish the topic create mechanism
	wait();

      } // end try
      catch (InterruptedException e)
      { // do nothing
      } // end catch

    } // end while

    creating_topic = true;
    // new topic number, needed to properly parse post links in new topic's zero post
    return (short)(top_topic + 1);

  } // end enterCreateTopic

  public synchronized void exitCreateTopic()
  {
    creating_topic = false;
    notify();  // wake somebody up if they were going to create a topic

  } // end exitCreateTopic

  public short getTopTopic()
  {
    return top_topic;

  } // end getTopTopic

  public List getMemberList() throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    if (logger.isDebugEnabled())
      logger.debug("Member list: conference = " + confid);

    ArrayList rc = new ArrayList();    // return from this function
    Connection conn = null;      // pooled database connection
    Statement stmt = null;

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // create a new SQL statement
      StringBuffer sql = new StringBuffer("SELECT u.uid, u.username, u.description, c.given_name, "
					  + "c.family_name, c.locality, c.region, c.country, m.granted_lvl "
					  + "FROM users u, contacts c, confmember m WHERE "
					  + "u.contactid = c.contactid AND u.uid = m.uid AND m.confid = ");
      sql.append(confid).append(" AND u.is_anon = 0 ORDER BY u.username;");

      if (logger.isDebugEnabled())
	logger.debug("SQL: " + sql.toString());

      // launch the search!
      ResultSet rs = stmt.executeQuery(sql.toString());

      while (rs.next())
      { // add all the found users to the list
	UserFoundImpl ufi = new UserFoundImpl(rs.getInt(1),rs.getString(2),rs.getString(3),rs.getString(4),
					      rs.getString(5),rs.getString(6),rs.getString(7),rs.getString(8),
					      rs.getInt(9));
	UserFound tmp = (UserFound)ufi;
	rc.add(tmp);

      } // end while

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading member entries: " + e.getMessage(),e);
      throw new DataException("unable to retrieve member information: " + e.getMessage(),e);

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

    } // end finally

    return Collections.unmodifiableList(rc);

  } // end getMemberList

  public int getMemberLevel(int uid) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;      // pooled database connection
    Statement stmt = null;

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // create the statement
      StringBuffer sql = new StringBuffer("SELECT granted_lvl FROM confmember WHERE confid = ");
      sql.append(confid).append(" AND uid = ").append(uid).append(';');

      // execute the statement and return the retrieved value
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (rs.next())
	return rs.getInt(1);

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading member entries: " + e.getMessage(),e);
      throw new DataException("unable to retrieve member information: " + e.getMessage(),e);

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

    } // end finally

    return -1;  // default return if no member level present

  } // end getMemberLevel

  public boolean isDeleted()
  {
    return deleted;

  } // end isDeleted

  public boolean canDeleteConference(int level)
  {
    if (deleted)
      return false;
    return (level>=delete_level);

  } // end canDeleteConference

  public synchronized void delete(EnvCommunity outer) throws DataException
  {
    if (deleted)
      throw new DataException("This conference has been deleted.");

    Connection conn = null;      // database connection
    Statement stmt = null;
    AuditRecord ar = null;       // audit record
    int topic_count, topic_max;  // count and maximum topic

    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // lock tables on the critical stuff that MUST be deleted now
      stmt.executeUpdate("LOCK TABLES confs WRITE, confalias WRITE, topics READ;");
      try
      { // first delete the conference record itself
	StringBuffer sql = new StringBuffer("DELETE FROM confs WHERE confid = ");
	sql.append(confid).append(';');
	stmt.executeUpdate(sql.toString());

	// now delete all its alias records
	sql.setLength(0);
	sql.append("DELETE FROM confalias WHERE confid = ").append(confid).append(';');
	stmt.executeUpdate(sql.toString());

	// determine the number of topics in this conference, and the maximum topic ID
	sql.setLength(0);
	sql.append("SELECT COUNT(*), MAX(topicid) FROM topics WHERE confid = ").append(confid).append(';');
	ResultSet rs = stmt.executeQuery(sql.toString());
	if (!(rs.next()))
	  throw new InternalStateError("ConferenceCoreData.delete screwup on topic SELECT");
	topic_count = rs.getInt(1);
	topic_max = rs.getInt(2);
	SQLUtil.shutdown(rs);

	// record that we're now deleted
	create_date = null;
	last_update = null;
	top_topic = -1;
	name = null;
	description = null;
	cached_alias = null;
	deleted = true;

      } // end try
      finally
      { // unlock the tables before we go
	SQLUtil.unlockTables(conn);

      } // end finally

      // create an audit record indicating we were successful
      ar = outer.newAudit(AuditRecord.DELETE_CONF,"confid=" + confid);

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

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

    } // end finally

    // Delete the rest of the gunk in the background; spin off another thread to handle it.
    BackgroundConferencePurge purger = new BackgroundConferencePurge(env,confid,topic_count,topic_max);
    Thread thrd = new Thread(purger);
    thrd.setPriority(Thread.NORM_PRIORITY-1);
    thrd.start();

  } // end delete

  public boolean displayPostPictures()
  {
    return flags.get(BP_POSTPICTURES);

  } // end displayPostPictures

  public ConferenceProperties getProperties()
  {
    return createProperties();

  } // end getProperties

  public synchronized void setProperties(ConferenceProperties props) throws DataException
  {
    ConferenceProperties 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 String getCustomBlock(int selector) throws DataException
  {
    SoftReference r;
    if (selector==ConferenceContext.CUST_BLOCK_TOP)
      r = cust_top;
    else if (selector==ConferenceContext.CUST_BLOCK_BOTTOM)
      r = cust_bottom;
    else
      throw new IndexOutOfBoundsException("invalid custom block selector");

    if ((!has_custom) || (r==null))
      return null;   // no custom block to return

    // try to retrieve a custom block value
    String rc = (String)(r.get());
    if (rc==null)
    { // need to reload the custom block data
      Connection conn = null;
      try
      { // load the custom information
	conn = env.getConnection();
	loadCustom(conn);

	// set up the return value
	if (selector==ConferenceContext.CUST_BLOCK_TOP)
	  rc = (String)(cust_top.get());
	else if (selector==ConferenceContext.CUST_BLOCK_BOTTOM)
	  rc = (String)(cust_bottom.get());

      } // end try
      catch (SQLException e)
      { // database error - this is a DataException
	logger.error("DB error getting custom block: " + e.getMessage(),e);
	throw new DataException("unable to get custom block: " + e.getMessage(),e);

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

      } // end finally

    } // end if

    return rc;

  } // end getCustomBlock

  public synchronized void setCustomBlock(int selector, String data) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("setCustomBlock(" + selector + ") entry");

    String fieldname;
    if (selector==ConferenceContext.CUST_BLOCK_TOP)
      fieldname = "htmltop";
    else if (selector==ConferenceContext.CUST_BLOCK_BOTTOM)
      fieldname = "htmlbottom";
    else
      throw new IndexOutOfBoundsException("invalid custom block selector");
      
    Connection conn = null;
    Statement stmt = null;

    try
    { // get a connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // build the right SQL command
      StringBuffer sql = null;
      if (has_custom)
      { // build an UPDATE command
	sql = new StringBuffer("UPDATE confcustom SET ");
	sql.append(fieldname).append(" = ").append(SQLUtil.encodeStringArg(data)).append(" WHERE confid = ");
	sql.append(confid).append(';');
	
      } // end if
      else
      { // build an INSERT command
	sql = new StringBuffer("INSERT INTO confcustom (confid, ");
	sql.append(fieldname).append(") VALUES (").append(confid).append(", ");
	sql.append(SQLUtil.encodeStringArg(data)).append(");");

      } // end else

      stmt.executeUpdate(sql.toString());  // update the database

      // save off all the data cached locally
      has_custom = true;
      SoftReference old_r = null;
      if (selector==ConferenceContext.CUST_BLOCK_TOP)
      { // replace the top block
	old_r = cust_top;
	cust_top = new SoftReference(data);

      } // end if
      else if (selector==ConferenceContext.CUST_BLOCK_BOTTOM)
      { // replace the bottom block
	old_r = cust_bottom;
	cust_bottom = new SoftReference(data);

      } // end else if

      if (old_r!=null)
	old_r.clear();   // dump the old data block before we go

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error setting custom block: " + e.getMessage(),e);
      throw new DataException("unable to set custom block: " + e.getMessage(),e);

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

    } // end finally

  } // end setCustomBlock

  public synchronized void removeCustomBlocks() throws DataException
  {
    Connection conn = null;
    Statement stmt = null;

    try
    { // get a connection
      conn = env.getConnection();
      stmt = conn.createStatement();

      // execute the delete statement
      stmt.executeUpdate("DELETE FROM confcustom WHERE confid = " + confid + ";");

      // zap the custom data in this object
      has_custom = false;
      if (cust_top!=null)
	cust_top.clear();
      if (cust_bottom!=null)
	cust_bottom.clear();
      cust_top = cust_bottom = null;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error removing custom blocks: " + e.getMessage(),e);
      throw new DataException("unable to remove custom blocks: " + e.getMessage(),e);

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

    } // end finally

  } // end removeCustomBlocks

  /*--------------------------------------------------------------------------------
   * External static operations (usable only from within package)
   *--------------------------------------------------------------------------------
   */

  static ReturnConfSeq createConference(EnvCommunity outer, EnvCommunityData env, String name, String alias,
					String description, boolean pvt, boolean hide_list, int host_uid)
      throws DataException
  {
    Connection conn = null;    // database connection
    Statement stmt = null;
    AuditRecord ar = null;     // audit record
    int new_confid;            // new conference ID
    short new_sequence;        // new sequence number
    java.util.Date created;    // date/time conference created
    ConferenceCoreData conf;   // returned conference core data

    if (logger.isDebugEnabled())
      logger.debug("createConference entry");

    try
    { // start by locking all the tables we need
      conn = env.getConnection();
      stmt = conn.createStatement();
      stmt.executeUpdate("LOCK TABLES confs WRITE, sigtoconf WRITE, confalias WRITE, confmember WRITE, "
			 + "propconf WRITE;");

      try
      { // first check on the alias availability
	StringBuffer sql = new StringBuffer("SELECT confid FROM confalias WHERE alias = '");
	String alias_encode = SQLUtil.encodeString(alias);
	sql.append(alias_encode).append("';");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	ResultSet rs = stmt.executeQuery(sql.toString());
	if (rs.next())  // already used - this is bogus!
	  throw new DataException("The alias \"" + alias + "\" is already in use by a different conference.");

	// compute our new sequence number
	sql.setLength(0);
	sql.append("SELECT MAX(sequence) FROM sigtoconf WHERE sigid = ").append(env.getCommunityID());
	sql.append(';');
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	rs = stmt.executeQuery(sql.toString());
	if (!(rs.next()))
	  throw new InternalStateError("bogus query in createConference - must return at least 1 row!");
	new_sequence = (short)(rs.getShort(1) + 10);
	SQLUtil.shutdown(rs);

	// insert the record into the conferences table!
	sql.setLength(0);
	sql.append("INSERT INTO confs (createdate, read_lvl, post_lvl, create_lvl, hide_lvl, nuke_lvl, "
		   + "change_lvl, delete_lvl, name, descr) VALUES ('");
	created = new java.util.Date();
	int levels[] = new int[7];
	if (pvt)
	{ // load levels for private conference
	  levels[0] = outer.getConferenceDefaultRole("Conference.Read.Private").getLevel();
	  levels[1] = outer.getConferenceDefaultRole("Conference.Post.Private").getLevel();
	  levels[2] = outer.getConferenceDefaultRole("Conference.Create.Private").getLevel();

	} // end if
	else
	{ // load levels for public conference
	  levels[0] = outer.getConferenceDefaultRole("Conference.Read.Public").getLevel();
	  levels[1] = outer.getConferenceDefaultRole("Conference.Post.Public").getLevel();
	  levels[2] = outer.getConferenceDefaultRole("Conference.Create.Public").getLevel();

	} // end else

	levels[3] = outer.getConferenceDefaultRole("Conference.Hide").getLevel();
	levels[4] = outer.getConferenceDefaultRole("Conference.Nuke").getLevel();
	levels[5] = outer.getConferenceDefaultRole("Conference.Change").getLevel();
	levels[6] = outer.getConferenceDefaultRole("Conference.Delete").getLevel();

	sql.append(SQLUtil.encodeDate(created)).append("', ").append(levels[0]).append(", ");
	sql.append(levels[1]).append(", ").append(levels[2]).append(", ").append(levels[3]).append(", ");
	sql.append(levels[4]).append(", ").append(levels[5]).append(", ").append(levels[6]);
	sql.append(", '").append(SQLUtil.encodeString(name)).append("', '");
	sql.append(SQLUtil.encodeString(description)).append("');");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// Get the new conference ID.
	rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
	if (!(rs.next()))
	  throw new InternalStateError("bogus query in createConference - must get new confid!");
	new_confid = rs.getInt(1);
	SQLUtil.shutdown(rs);

	// Make the insert into the confalias table to give the conference an alias.
	sql.setLength(0);
	sql.append("INSERT INTO confalias (confid, alias) VALUES (").append(new_confid).append(", '");
	sql.append(alias_encode).append("');");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// Link the community to the conference by adding a row to sigtoconf.
	sql.setLength(0);
	sql.append("INSERT INTO sigtoconf (sigid, confid, sequence, hide_list) VALUES (");
	sql.append(env.getCommunityID()).append(", ").append(new_confid).append(", ").append(new_sequence);
	sql.append(", ").append(hide_list ? '1' : '0').append(");");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// Make the specified UID the host of this new conference.
	sql.setLength(0);
	sql.append("INSERT INTO confmember (confid, uid, granted_lvl) VALUES (").append(new_confid);
	sql.append(", ").append(host_uid).append(", ");
	sql.append(outer.getConferenceDefaultRole("Conference.NewHost").getLevel()).append(");");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// Create a new ConferenceCoreData object representing this conference and register it with the
	// engine's conference data object cache.
	conf = new ConferenceCoreData(env,new_confid,created,levels,name,description);
	conf.newProperties(conn);

      } // end try
      finally
      { // unlock the tables before we go
	SQLUtil.unlockTables(conn);

      } // end finally

      env.getEngine().registerNewConference(conf);

      // create an audit record indicating we were successful
      ar = outer.newAudit(AuditRecord.CREATE_CONF,"confid=" + new_confid,"name=" + name,"alias=" + alias);

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

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

    } // end finally

    return new ReturnConfSeq(conf,new_sequence);
    
  } // end createConference

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  { // initialize the "all properties" bit set
    BitSet tmp = new BitSet();
    tmp.set(PROP_FLAGS);
    ALL_PROPS = tmp;

  } // end static initializer

} // end class ConferenceCoreData
