/*
 * 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.sql.*;
import java.util.*;
import org.apache.log4j.*;
import com.silverwrist.util.OptionSet;
import com.silverwrist.util.StringUtil;
import com.silverwrist.util.cache.*;
import com.silverwrist.venice.db.*;
import com.silverwrist.venice.core.*;
import com.silverwrist.venice.core.internals.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.security.AuditRecord;
import com.silverwrist.venice.svc.*;

class CommunityCoreData implements CommunityData, CommunityDataBackend
{
  /*--------------------------------------------------------------------------------
   * Internal class that creates new ConferenceCommunityContextImpl objects
   *--------------------------------------------------------------------------------
   */

  protected class ConferenceCommunityContextImplCreator implements ObjectFactory
  {
    protected ConferenceCommunityContextImplCreator()
    { // do nothing
    } // end constructor

    public Object newObject(Object key) throws ObjectFactoryException
    {
      Integer xconf = (Integer)key;
      try
      { // create the desired object
	return new ConferenceCommunityContextImpl(env,xconf.intValue());

      } // end try
      catch (DataException e)
      { // rethrow as a "wrapped" exception
	throw new ObjectFactoryException(e);

      } // end catch

    } // end build

  } // end class ConferenceCoreDataCreator

  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

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

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

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

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

  private EnvCommunityData env;         // the environment
  private int cid;                      // ID of this community
  private java.util.Date created;       // date/time of database creation 
  private java.util.Date last_access;   // date/time of last access
  private java.util.Date last_update;   // date/time of last update
  private int read_level;               // access level required to read contained data
  private int write_level;              // access level required to write summary data
  private int create_level;             // access level required to create subobjects
  private int delete_level;             // access level required to delete community
  private int join_level;               // access level required to become a community member
  private int contactid;                // this community's contact ID (geographic info)
  private int host_uid;                 // UID of the community host
  private int category_id;              // category ID for this community
  private boolean hidden_directory;     // hidden from directory listings?
  private boolean hidden_search;        // hidden from keyword searches?
  private boolean members_only;         // only members allowed in this community?
  private boolean is_admin;             // is this the administrative community?
  private String name;                  // the name of this community
  private String language;              // the default language code
  private String synopsis;              // the synopsis of the community
  private String rules;                 // the community "rules"
  private String alias;                 // the community alias value
  private boolean public_comm;          // is this a public community?
  private ObjectCache conf_objcache = new ObjectCache(new ConferenceCommunityContextImplCreator());
  private boolean deleted = false;      // has this community been deleted?
  private OptionSet flags;              // property flags
  private HashSet services;             // services for this community
  private ServiceToken initial_service; // the initial service

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

  CommunityCoreData(EnvEngine env, int cid) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("new CommunityCoreData for community " + cid);
    this.env = new EnvCommunityData(env,this);
    this.cid = cid;

    Connection conn = null;
    Statement stmt = null;

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

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

      loadData(rs);  // load the community data
      SQLUtil.shutdown(rs);

      sql.setLength(0);
      sql.append("SELECT ndx, data FROM propcomm WHERE cid = ").append(cid).append(';');
      rs = stmt.executeQuery(sql.toString());
      while (rs.next())
      { // load all properties
	switch (rs.getInt(1))
	{ // look for the various property indices
	  case PROP_FLAGS:
	    flags = new OptionSet(rs.getString(2));
	    break;

	  default:
	    break;

	} // end switch

      } // end while

      SQLUtil.shutdown(rs);

      // get the set of community services
      services = new HashSet();
      sql.setLength(0);
      sql.append("SELECT ftr_code FROM sigftrs WHERE sigid = ").append(cid).append(';');
      rs = stmt.executeQuery(sql.toString());
      while (rs.next())
      { // get the service tokens and return them
	ServiceToken token = env.getSCM().getTokenForIndex(ServiceControl.SVCGRP_COMMUNITY,rs.getInt(1));
	if (token!=null)
	  services.add(token);

      } // end while

    } // end try
    catch (SQLException e)
    { // translate into a nicer DataException
      logger.error("DB error reading community data: " + e.getMessage(),e);
      throw new DataException("unable to load community 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 CommunityCoreData(EnvEngine env, int cid, java.util.Date creation,
			      String name, String alias, int host_uid, String language, String synopsis,
			      String rules, String joinkey, boolean hide_dir, boolean hide_search,
			      int[] levels, Set services)
  {
    if (logger.isDebugEnabled())
      logger.debug("new CommunityCoreData for BRAND NEW COMMUNITY " + cid);
    EnvCommunityData new_env = new EnvCommunityData(env,this);
    this.env = new_env;
    this.cid = cid;
    this.created = creation;
    this.last_access = creation;
    this.last_update = creation;
    this.read_level = levels[0];
    this.write_level = levels[1];
    this.create_level = levels[2];
    this.delete_level = levels[3];
    this.join_level = levels[4];
    this.contactid = -1;
    this.host_uid = host_uid;
    this.category_id = 0;
    this.hidden_directory = hide_dir;
    this.hidden_search = hide_search;
    this.members_only = true;
    this.is_admin = false;
    this.name = name;
    this.language = language;
    this.synopsis = synopsis;
    this.rules = rules;
    this.alias = alias;
    this.public_comm = StringUtil.isStringEmpty(joinkey);
    this.flags = new OptionSet();
    this.services = new HashSet(services);
    this.initial_service = env.getSCM().getTokenForIndex(ServiceControl.SVCGRP_COMMUNITY,0);
    if (env.getEngine().getParamBoolean(EngineBackend.BP_POSTPICTURES))
      flags.set(BP_POSTPICTURES);

  } // end constructor

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

  private final synchronized void loadData(ResultSet rs) throws SQLException
  {
    // "cid" field is skipped
    created = SQLUtil.getFullDateTime(rs,"createdate");
    last_access = SQLUtil.getFullDateTime(rs,"lastaccess");
    last_update = SQLUtil.getFullDateTime(rs,"lastupdate");
    read_level = rs.getInt("read_lvl");
    write_level = rs.getInt("write_lvl");
    create_level = rs.getInt("create_lvl");
    delete_level = rs.getInt("delete_lvl");
    join_level = rs.getInt("join_lvl");
    contactid = rs.getInt("contactid");
    host_uid = rs.getInt("host_uid");
    category_id = rs.getInt("catid");
    hidden_directory = rs.getBoolean("hide_dir");
    hidden_search = rs.getBoolean("hide_search");
    members_only = rs.getBoolean("membersonly");
    is_admin = rs.getBoolean("is_admin");
    name = rs.getString("signame");
    language = rs.getString("language");
    synopsis = rs.getString("synopsis");
    rules = rs.getString("rules");
    if (!is_admin)
    { // figure out whether we need a "join key" (password)
      String foo = rs.getString("joinkey");
      public_comm = StringUtil.isStringEmpty(foo);

    } // end if
    else  // Admin is NEVER a public community!
      public_comm = false;
    alias = rs.getString("alias");
    initial_service = env.getSCM().getTokenForIndex(ServiceControl.SVCGRP_COMMUNITY,rs.getShort("init_ftr"));

    if (logger.isDebugEnabled())
      logger.debug("Loaded data for community \"" + name + "\" (ID " + cid + ", alias '" + alias
		   + "') successfully");

  } // end loadData

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

    try
    { // create and execute the Update statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      last_update = now;

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

    } // end finally

  } // end touchUpdate

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

    try
    { // generate start of SQL statement
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("INSERT INTO propcomm (cid, ndx, data) VALUES ");

      // append Property 0: flags
      sql.append('(').append(cid).append(", ").append(PROP_FLAGS).append(", '");
      sql.append(flags.asString()).append("')");

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

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

    } // end finally

  } // end newProperties

  private final synchronized 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 propcomm SET data = '").append(flags.asString()).append("' WHERE cid = ");
	sql.append(cid).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 community properties: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 final synchronized void updateProperties() throws DataException
  {
    updateProperties(ALL_PROPS);

  } // end updateProperties

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

  } // end createProperties

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

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

    return rc;

  } // end storeProperties

  /*--------------------------------------------------------------------------------
   * Implementations from interface CommunityData
   *--------------------------------------------------------------------------------
   */

  public int getID()
  {
    return cid;

  } // end getID

  public String getName()
  {
    return name;

  } // end getName

  public String getAlias()
  {
    return alias;

  } // end getAlias

  public boolean isPublicCommunity()
  {
    return public_comm;

  } // end isPublicCommunity

  public int getCategoryID()
  {
    return category_id;

  } // end getCategoryID

  public String getSynopsis()
  {
    return synopsis;

  } // end getSynopsis

  public int getHostUID()
  {
    return host_uid;

  } // end getHostUID

  public String getLanguage()
  {
    return language;

  } // end getLanguage

  public String getRules()
  {
    return rules;

  } // end getRules

  public int getContactID()
  {
    return contactid;

  } // end getContactID

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

  } // end getCreationDate

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

  } // end getLastAccessDate

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

  } // end getLastUpdateDate

  public void testMembership(int level, boolean is_member) throws DataException, AccessError
  {
    if (deleted)
      throw new DataException("This community has been deleted.");
    if (env.testPermission(EnvCommunityData.PERM_NOJOINREQUIRED,level))
      return;
    if (members_only && !is_member)
    { // the membership test failed
      logger.error("not a community member");
      throw new AccessError("You are not a member of this community.");

    } // end if

  } // end testMembership

  public boolean checkMembership(int level, boolean is_member)
  {
    if (deleted)
      return false;
    if (env.testPermission(EnvCommunityData.PERM_NOJOINREQUIRED,level))
      return true;
    return !members_only || is_member;

  } // end checkMembership

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

  } // end canReadCommunitySubObjects

  public boolean canModifyCommunityProfile(int level)
  {
    if (deleted)
      return false;
    return (level>=write_level);

  } // end canModifyCommunityProfile

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

  } // end canCreateCommunitySubObjects

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

  } // end canDeleteCommunity

  public boolean canJoinCommunity(int uid, int level)
  {
    if (deleted || (level<join_level))
      return false;  // rejected on a level basis

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

      // check to see if the UID is listed in "sigban" table...
      StringBuffer sql = new StringBuffer("SELECT by_uid FROM sigban WHERE sigid = ");
      sql.append(cid).append(" AND uid = ").append(uid).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (rs.next())
	return false;
    
    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.warn("DB error looking up ban info: " + e.getMessage(),e);

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

    } // end finally

    return true;

  } // end canJoinCommunity

  public synchronized void putContactInfo(EnvCommunity outer, ContactInfo ci) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    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 new contact ID (" + contactid + ") for this community");

      } // end if

      touchUpdate(conn);
      ar = outer.newAudit(AuditRecord.COMMUNITY_CONTACT_INFO,"contactid=" + contactid);

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

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

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

    } // end finally

  } // end putContactInfo

  public Set getServices()
  {
    return Collections.unmodifiableSet((Set)(services.clone()));

  } // end getServices

  public Set getServices(EnvCommunity outer)
  {
    HashSet tmp = new HashSet();
    Iterator it = services.iterator();
    while (it.hasNext())
    { // get the service associated with the token
      ServiceToken token = (ServiceToken)(it.next());
      CommService svc = (CommService)(env.getSCM().getServiceForToken(token));

      // Check the permission first.
      String perm = svc.getPermission();
      if (perm!=null)
      { // test the permission against the outer environment
	if (!(outer.testPermission(perm)))
	  continue;  // permission failed - just go on to the next one

      } // end if

      if (outer.satisfy(svc.getRole()))
	tmp.add(token);

    } // end getServices

    if (tmp.isEmpty())
      return Collections.EMPTY_SET;
    else
      return Collections.unmodifiableSet(tmp);

  } // end getServices

  public synchronized void setServiceEnable(EnvCommunity outer, ServiceToken token, boolean enable)
      throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    if (enable && services.contains(token))
      return;
    if (!enable && !(services.contains(token)))
      return;

    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 sigftrs WRITE;");

      try
      { // execute the right statement
	StringBuffer sql;
	if (enable)
	{ // create an INSERT statement
	  sql = new StringBuffer("INSERT INTO sigftrs (sigid, ftr_code) VALUES (");
	  sql.append(cid).append(", ").append(token.getIndex()).append(");");

	} // end if
	else
	{ // create a DELETE statement
	  sql = new StringBuffer("DELETE FROM sigftrs WHERE sigid = ");
	  sql.append(cid).append(" AND ftr_code = ").append(token.getIndex()).append(';');

	} // end else

	stmt.executeUpdate(sql.toString());
	if (enable)
	  services.add(token);
	else
	  services.remove(token);

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

      } // end finally

      touchUpdate(conn);
      ar = outer.newAudit(AuditRecord.COMMUNITY_FEATURE_SET);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community feature set: " + e.getMessage(),e);
      throw new DataException("unable to update feature set: " + 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 setServiceEnable

  public synchronized void setName(EnvCommunity outer, String name) throws DataException
  {
    if (deleted)
      throw new DataException("This community 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();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET signame = '");
      sql.append(SQLUtil.encodeString(name)).append("', lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      this.name = name;
      last_update = now;
      ar = outer.newAudit(AuditRecord.COMMUNITY_NAME,"name=" + name);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setAlias(EnvCommunity outer, String alias) throws DataException
  {
    if (deleted)
      throw new DataException("This community 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();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET alias = '");
      sql.append(alias).append("', lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      this.alias = alias;
      last_update = now;
      ar = outer.newAudit(AuditRecord.COMMUNITY_ALIAS,"alias=" + alias);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setAlias

  public synchronized void setCategoryID(EnvCommunity outer, int catid) throws DataException
  {
    if (deleted)
      throw new DataException("This community 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();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET catid = ");
      sql.append(catid).append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      this.category_id = catid;
      last_update = now;
      ar = outer.newAudit(AuditRecord.COMMUNITY_CATEGORY,"catid=" + catid);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setCategoryID

  public synchronized void setSynopsis(String synopsis) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET synopsis = ");
      sql.append(SQLUtil.encodeStringArg(synopsis)).append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      this.synopsis = synopsis;
      last_update = now;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setSynopsis

  public synchronized void setLanguage(String language) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET language = '");
      sql.append(SQLUtil.encodeString(language)).append("', lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      this.language = language;
      last_update = now;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setLanguage

  public synchronized void setRules(String rules) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET rules = ");
      sql.append(SQLUtil.encodeStringArg(rules)).append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      this.rules = rules;
      last_update = now;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setRules

  public synchronized void touch() throws DataException
  {
    if (deleted)
      return;  // no need to touch anything

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET lastaccess = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      last_access = now;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 touch

  public boolean getHideDirectory()
  {
    return hidden_directory;

  } // end getHideDirectory

  public boolean getHideSearch()
  {
    return hidden_search;

  } // end getHideSearch

  public synchronized void setHideFlags(EnvCommunity outer, boolean directory, boolean search)
      throws DataException
  {
    if (deleted)
      throw new DataException("This community 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();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET hide_dir = ");
      sql.append(directory ? '1' : '0').append(", hide_search = ").append(search ? '1' : '0');
      sql.append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      hidden_directory = directory;
      hidden_search = search;
      last_update = now;
      ar = outer.newAudit(AuditRecord.COMMUNITY_HIDE_INFO,"dir=" + directory + ",search=" + search);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setHideFlags

  public boolean getMembersOnly()
  {
    return members_only;

  } // end getMembersOnly

  public synchronized void setMembersOnly(EnvCommunity outer, boolean flag) throws DataException
  {
    if (deleted)
      throw new DataException("This community 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();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET membersonly = ");
      sql.append(flag ? '1' : '0').append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      members_only = flag;
      last_update = now;
      ar = outer.newAudit(AuditRecord.COMMUNITY_MEMBERS_ONLY,"flag=" + flag);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setMembersOnly

  public ServiceToken getDefaultService()
  {
    return initial_service;

  } // end getDefaultService

  public synchronized void setDefaultService(ServiceToken token) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET init_ftr = ");
      sql.append(token.getIndex()).append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      initial_service = token;
      last_update = now;

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setDefaultService

  public String getJoinKey() throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT joinkey FROM sigs WHERE sigid = ");
      sql.append(cid).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
      { // can't find the join key?
	logger.error("community " + cid + "appears to not be there");
	throw new DataException("Unable to find join key for community");

      } // end if

      return rs.getString("joinkey");

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

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

    } // end finally

  } // end getJoinKey

  public synchronized void setJoinKey(EnvCommunity outer, String key) throws DataException
  {
    if (deleted)
      throw new DataException("This community 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();
      StringBuffer sql = new StringBuffer("UPDATE sigs SET joinkey = ");
      sql.append(SQLUtil.encodeStringArg(key)).append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');
      stmt.executeUpdate(sql.toString());
      public_comm = StringUtil.isStringEmpty(key);
      last_update = now;
      ar = outer.newAudit(AuditRecord.COMMUNITY_JOIN_KEY);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 setJoinKey

  public int getReadLevel()
  {
    return read_level;

  } // end getReadLevel

  public int getWriteLevel()
  {
    return write_level;

  } // end getWriteLevel

  public int getCreateLevel()
  {
    return create_level;

  } // end getCreateLevel

  public int getDeleteLevel()
  {
    return delete_level;

  } // end getDeleteLevel

  public int getJoinLevel()
  {
    return join_level;

  } // end getJoinLevel

  public synchronized void setSecurityLevels(EnvCommunity outer, int read, int write, int create, int delete,
					     int join) throws DataException
  {
    if (deleted)
      throw new DataException("This community 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 sigs SET read_lvl = ");
      sql.append(read).append(", write_lvl = ").append(write).append(", create_lvl = ").append(create);
      sql.append(", delete_lvl = ").append(delete).append(", join_lvl = ").append(join);
      sql.append(", lastupdate = '");
      java.util.Date now = new java.util.Date();
      sql.append(SQLUtil.encodeDate(now)).append("' WHERE sigid = ").append(cid).append(';');

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

      // save copies of the data to our local variables
      read_level = read;
      write_level = write;
      create_level = create;
      delete_level = delete;
      join_level = join;
      last_update = now;

      // create an audit record reflecting what was done
      ar = outer.newAudit(AuditRecord.COMMUNITY_SECURITY);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 boolean isAdminCommunity()
  {
    return is_admin;

  } // end isAdminCommunity

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

    Connection conn = null;  // database connection
    Statement stmt = null;
    AuditRecord ar = null;   // successful audit record
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      stmt.executeUpdate("LOCK TABLES sigmember WRITE;");
      boolean did_it = false;

      try
      { // perform the actual manipulation of the SIGMEMBER table...
	StringBuffer sql = new StringBuffer();
	if (grant_level>=0)
	{ // start by seeing if the user already has a record...
	  sql.append("SELECT sigid FROM sigmember WHERE sigid = ").append(cid).append(" AND uid = ");
	  sql.append(uid).append(';');
	  ResultSet rs = stmt.executeQuery(sql.toString());

	  sql.setLength(0);
	  if (rs.next())
	  { // build an UPDATE statement
	    sql.append("UPDATE sigmember SET granted_lvl = ").append(grant_level).append(", locked = ");
	    sql.append(locked ? '1' : '0').append(", hidden = ").append(hidden ? '1' : '0');
	    sql.append(" WHERE sigid = ").append(cid).append(" AND uid = ").append(uid).append(';');

	  } // end if
	  else
	  { // build an INSERT statement
	    sql.append("INSERT INTO sigmember (sigid, uid, granted_lvl, locked, hidden) VALUES (");
	    sql.append(cid).append(", ").append(uid).append(", ").append(grant_level).append(", ");
	    sql.append(locked ? '1' : '0').append(", ").append(hidden ? '1' : '0').append(");");

	  } // end else

	  SQLUtil.shutdown(rs);
	  stmt.executeUpdate(sql.toString());  // update me!
	  did_it = true;

	} // end if
	else
	{ // delete the membership reference (which may not exist, but oh well)
	  sql.append("DELETE FROM sigmember WHERE sigid = ").append(cid).append(" AND uid = ");
	  sql.append(uid).append(';');
	  did_it = (stmt.executeUpdate(sql.toString())>0);

	} // end else

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

      } // end finally

      if (did_it)
      { // update the community data and generate an audit record
	touchUpdate(conn);
	ar = outer.newAudit(AuditRecord.SET_MEMBERSHIP,"uid=" + uid,"level=" + grant_level);
			     
      } // end if

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error storing community data: " + e.getMessage(),e);
      throw new DataException("unable to save community 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 int getMemberCount(boolean include_hidden) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    Connection conn = null;  // database connection
    Statement stmt = null;
      
    try
    { // get a database connection and create the appropriate SELECT statement
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM sigmember WHERE sigid = ");
      sql.append(cid);
      if (!include_hidden)
	sql.append(" AND hidden = 0");
      sql.append(';');

      // execute the query
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
	throw new InternalStateError("getMemberCount query failure - must have ONE row!");

      return rs.getInt(1);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error getting community data: " + e.getMessage(),e);
      throw new DataException("unable to retrieve community 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 getMemberCount

  public boolean hasService(ServiceToken token)
  {
    if (deleted)
      return false;
    return services.contains(token);

  } // end hasService

  public synchronized ConferenceCommunityContext getConferenceDataObject(int confid) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    if (logger.isDebugEnabled())
      logger.debug("getConferenceDataObject(" + confid + ")...");

    try
    { // delegate to the conf_objcache object
      return (ConferenceCommunityContext)(conf_objcache.getOrCreate(new Integer(confid)));

    } // end try
    catch (ObjectFactoryException e)
    { // this may be a DataException, or it may not
      Throwable e2 = e.getException();
      if (e2 instanceof DataException)
	throw (DataException)e2;
      else
	throw new InternalStateError("unknown creation exception thrown in getConferenceDataObject: "
				     + e2.getClass().getName(),e2);

    } // end catch

  } // end getConferenceDataObject

  public synchronized void detachConferenceDataObject(int confid)
  {
    if (logger.isDebugEnabled())
      logger.debug("detachConferenceDataObject(" + confid + ")...");

    conf_objcache.detach(new Integer(confid));

  } // end detachConferenceDataObject

  public ConferenceCommunityContext createConference(EnvCommunity outer, String name, String alias,
						     String description, boolean pvt, boolean hide_list)
      throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    ReturnConfSeq rcs = ConferenceCoreData.createConference(outer,env,name,alias,description,pvt,hide_list,
							    host_uid);
    ConferenceData cdata = rcs.getConference();
    if (outer.getUserID()!=host_uid)  // make the creating user a conference host too
      cdata.setMembership(outer,outer.getUserID(),
			  outer.getConferenceDefaultRole("Conference.Creator").getLevel());

    // Wrap the returned ConferenceData object in a ConferenceCommunityContextImpl object.
    ConferenceCommunityContextImpl conf =
        new ConferenceCommunityContextImpl(env,rcs.getSequence(),hide_list,cdata);
    rcs = null;

    conf_objcache.register(new Integer(conf.getConfID()),conf);  // register this object with our local cache

    return conf;  // pass it up to the next level

  } // end createConference

  public List searchForMembers(int field, int mode, String term, int offset, int count,
			       boolean include_hidden) throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    if (logger.isDebugEnabled())
      logger.debug("Member search: community = " + cid + ", field = " + field + ", mode = " + mode
		   + ", term '" + term + "', offset = " + offset + ", count = " + count);

    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, sigmember m WHERE "
					  + "u.contactid = c.contactid AND u.uid = m.uid AND m.sigid = ");
      sql.append(cid).append(" AND ");
      switch (field)
      { // determine which field to search on
	case SearchMode.FIELD_USER_NAME:
	  sql.append("u.username ");
	  break;

	case SearchMode.FIELD_USER_DESCRIPTION:
	  sql.append("u.description ");
	  break;

	case SearchMode.FIELD_USER_GIVEN_NAME:
	  sql.append("c.given_name ");
	  break;

	case SearchMode.FIELD_USER_FAMILY_NAME:
	  sql.append("c.family_name ");
	  break;

	default:
	  throw new DataException("invalid search field specified");

      } // end switch

      switch (mode)
      { // compose SQL in different ways depending on the search term
	case SearchMode.SEARCH_PREFIX:
	  sql.append("LIKE '").append(SQLUtil.encodeStringWildcards(term)).append("%'");
	  break;

	case SearchMode.SEARCH_SUBSTRING:
	  sql.append("LIKE '%").append(SQLUtil.encodeStringWildcards(term)).append("%'");
	  break;

	case SearchMode.SEARCH_REGEXP:
	  sql.append("REGEXP '").append(SQLUtil.encodeString(term)).append('\'');
	  break;

	default:
	  throw new DataException("invalid search mode specified");

      } // end switch

      if (!include_hidden)
	sql.append(" AND m.hidden = 0");
      sql.append(" AND u.is_anon = 0 ORDER BY u.username LIMIT ").append(offset).append(", ");
      sql.append(count+1).append(';');

      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 searchForMembers

  public int getSearchMemberCount(int field, int mode, String term, boolean include_hidden)
      throws DataException
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    if (logger.isDebugEnabled())
      logger.debug("User search: community = " + cid + ", field = " + field + ", mode = " + mode + ", term '"
		   + term + "'");

    Connection conn = null;      // pooled database connection
    Statement stmt = null;
      
    try
    { // get a database connection
      conn = env.getConnection();
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT COUNT(*) FROM users u, contacts c, sigmember m WHERE "
					  + "u.contactid = c.contactid AND u.uid = m.uid AND m.sigid = ");
      sql.append(cid).append(" AND ");

      switch (field)
      { // determine which field to search on
	case SearchMode.FIELD_USER_NAME:
	  sql.append("u.username ");
	  break;

	case SearchMode.FIELD_USER_DESCRIPTION:
	  sql.append("u.description ");
	  break;

	case SearchMode.FIELD_USER_GIVEN_NAME:
	  sql.append("c.given_name ");
	  break;

	case SearchMode.FIELD_USER_FAMILY_NAME:
	  sql.append("c.family_name ");
	  break;

	default:
	  throw new DataException("invalid search field specified");

      } // end switch

      switch (mode)
      { // compose SQL in different ways depending on the search term
	case SearchMode.SEARCH_PREFIX:
	  sql.append("LIKE '").append(SQLUtil.encodeStringWildcards(term)).append("%'");
	  break;

	case SearchMode.SEARCH_SUBSTRING:
	  sql.append("LIKE '%").append(SQLUtil.encodeStringWildcards(term)).append("%'");
	  break;

	case SearchMode.SEARCH_REGEXP:
	  sql.append("REGEXP '").append(SQLUtil.encodeString(term)).append('\'');
	  break;

	default:
	  throw new DataException("invalid search mode specified");

      } // end switch

      if (!include_hidden)
	sql.append(" AND m.hidden = 0");
      sql.append(" AND u.is_anon = 0;");

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

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

      if (!(rs.next()))
	throw new InternalStateError("getSearchMemberCount failure (MUST have 1 row!)");

      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

  } // end getSearchMemberCount

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

    if (logger.isDebugEnabled())
      logger.debug("Member list: community = " + cid);

    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, sigmember m WHERE "
					  + "u.contactid = c.contactid AND u.uid = m.uid AND m.sigid = ");
      sql.append(cid);
      if (!include_hidden)
	sql.append(" AND m.hidden = 0");
      sql.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 community has been deleted.");

    if (logger.isDebugEnabled())
      logger.debug("Member level: community = " + cid + ", user = " + uid);

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

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

      // a relatively simple search
      StringBuffer sql = new StringBuffer("SELECT granted_lvl FROM sigmember WHERE sigid = ");
      sql.append(cid).append(" AND uid = ").append(uid).append(';');
      if (logger.isDebugEnabled())
	logger.debug("SQL: " + sql.toString());
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (rs.next())
	return rs.getInt(1);
      else
	return -1;

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading member level: " + 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

  } // end getMemberLevel

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

    if (logger.isDebugEnabled())
      logger.debug("Delete community: community = " + cid);

    Connection conn = null;      // database connection
    Statement stmt = null;
    AuditRecord ar = null;       // audit record
    int conf_count, conf_max;

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

      // lock the tables we need to reference immediately
      stmt.executeUpdate("LOCK TABLES sigs WRITE, sigmember WRITE, confhotlist WRITE, sigtoconf READ;");
      try
      { // first delete the community record itself
	stmt.executeUpdate("DELETE FROM sigs WHERE sigid = " + cid + ";");

	// now delete the members list
	stmt.executeUpdate("DELETE FROM sigmember WHERE sigid = " + cid + ";");

	// We need to make sure this community's conferences are removed from everyone's hotlist,
	// if they're present.  (Bug #415474)
	stmt.executeUpdate("DELETE FROM confhotlist WHERE sigid = " + cid + ";");

	// determine the number of conferences in this community, and the maximum conference ID
	ResultSet rs = stmt.executeQuery("SELECT COUNT(*), MAX(confid) FROM sigtoconf WHERE sigid = "
					 + cid + ";");
	if (!(rs.next()))
	  throw new InternalStateError("CommunityCoreData.delete screwup on conference SELECT");
	conf_count = rs.getInt(1);
	conf_max = rs.getInt(2);
	SQLUtil.shutdown(rs);

	// record that we're now deleted
	created = null;
	last_access = null;
	last_update = null;
	contactid = -1;
	name = null;
	language = null;
	synopsis = null;
	rules = null;
	alias = null;
	deleted = true;

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

      } // end finally

      // create an audit record indicating what happened
      ar = outer.newAudit(AuditRecord.DELETE_COMMUNITY);
    
    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error deleting community: " + e.getMessage(),e);
      throw new DataException("unable to delete community: " + 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; use another thread to do it.
    BackgroundCommunityPurge purger = new BackgroundCommunityPurge(outer,cid,conf_count,conf_max,
								   conf_objcache);
    Thread thrd = new Thread(purger);
    thrd.setPriority(Thread.NORM_PRIORITY-1);
    thrd.start();

  } // end delete

  public CommunityProperties getProperties()
  {
    return createProperties();

  } // end getProperties

  public void setProperties(CommunityProperties props) throws DataException
  {
    CommunityProperties 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 List getMassMailList() throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("getMassMailList() for community " + cid);
    if (deleted)
      throw new DataException("This community has been deleted.");

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

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

      // create the SQL statement
      StringBuffer sql =
	  new StringBuffer("SELECT c.email FROM contacts c, users u, sigmember m, propuser p "
			   + "WHERE c.contactid = u.contactid AND u.uid = m.uid AND m.sigid = ");
      sql.append(cid).append(" AND u.is_anon = 0 AND p.uid = u.uid AND p.ndx = ");
      sql.append(UserContextImpl.PROP_FLAGS).append(" AND p.data NOT LIKE '%");
      sql.append(OptionSet.getOptionChar(UserContextImpl.BF_MASSMAIL_OPTOUT)).append("%';");
      if (logger.isDebugEnabled())
	logger.debug("SQL: " + sql.toString());

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

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

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

    } // end finally

    return rc;

  } // end getMassMailList

  /*--------------------------------------------------------------------------------
   * Implementations from interface CommunityDataBackend
   *--------------------------------------------------------------------------------
   */

  public int realCommunityID()
  {
    return cid;

  } // end realCommunityID

  public boolean getParamBoolean(int selector)
  {
    return flags.get(selector);

  } // end getParamBoolean

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

  static CommunityData createCommunity(EnvUser env, String name, String alias, int host_uid, String language,
				       String synopsis, String rules, String joinkey, boolean hide_dir,
				       boolean hide_search) throws DataException, AccessError
  {
    Connection conn = null;    // database connection
    Statement stmt = null;
    int new_cid;               // ID of the new community
    java.util.Date creation;   // creation date!
    AuditRecord ar = null;     // the audit record
    CommunityCoreData comm;    // the new community that gets returned

    try
    { // get a database connection and create the appropriate SELECT statement
      conn = env.getConnection();
      stmt = conn.createStatement();
      stmt.executeUpdate("LOCK TABLES sigs WRITE, sigftrs WRITE, propcomm WRITE, sigmember WRITE;");

      try
      { // first, we need to check if the community alias already exists
	StringBuffer sql = new StringBuffer("SELECT sigid FROM sigs WHERE alias = ");
	sql.append(SQLUtil.encodeStringArg(alias)).append(';');
	ResultSet rs = stmt.executeQuery(sql.toString());
	if (rs.next())  // we should get 0 rows back on this one
	  throw new AccessError("A community with that alias already exists.");
	SQLUtil.shutdown(rs);

	// Now, here comes the BIG INSERT to create the community!
	sql.setLength(0);
	sql.append("INSERT INTO sigs (createdate, lastaccess, lastupdate, read_lvl, write_lvl, create_lvl, "
		   + "delete_lvl, join_lvl, host_uid, hide_dir, hide_search, signame, language, synopsis, "
		   + "rules, joinkey, alias) VALUES ('");
	creation = new java.util.Date();
	String creation_str = SQLUtil.encodeDate(creation);
	int[] levels = new int[5];
	levels[0] = env.getCommunityDefaultRole("Community.Read").getLevel();
	levels[1] = env.getCommunityDefaultRole("Community.Write").getLevel();
	levels[2] = env.getCommunityDefaultRole("Community.Create").getLevel();
	levels[3] = env.getCommunityDefaultRole("Community.Delete").getLevel();
	levels[4] = env.getCommunityDefaultRole("Community.Join").getLevel();
	sql.append(creation).append("', '").append(creation).append("', '").append(creation).append("', ");
	sql.append(levels[0]).append(", ").append(levels[1]).append(", ").append(levels[2]).append(", ");
	sql.append(levels[3]).append(", ").append(levels[4]).append(", ").append(host_uid).append(", ");
	sql.append(hide_dir ? '1' : '0').append(", ").append(hide_search ? '1' : '0').append(", ");
	sql.append(SQLUtil.encodeStringArg(name)).append(", ").append(SQLUtil.encodeStringArg(language));
	sql.append(", ").append(SQLUtil.encodeStringArg(synopsis)).append(", ");
	sql.append(SQLUtil.encodeStringArg(rules)).append(", ").append(SQLUtil.encodeStringArg(joinkey));
	sql.append(", ").append(SQLUtil.encodeStringArg(alias)).append(");");

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

	stmt.executeUpdate(sql.toString()); // here we go!

	// Get the community ID of the new community!
	rs = stmt.executeQuery("SELECT LAST_INSERT_ID();");
	if (rs.next())
	  new_cid = rs.getInt(1);
	else
	{ // we should have gotten a community ID back!
	  logger.error("readback of new community ID failed!");
	  throw new InternalStateError("failure on createCommunity return query - must have 1 row!");

	} // end else

	SQLUtil.shutdown(rs);

	// Assign the new community the default services for new communities.
	Set default_service = env.getSCM().getDefaultServices(ServiceControl.SVCGRP_COMMUNITY);
	if (default_service.size()>0)
	{ // build the communty's defined service set
	  sql.setLength(0);
	  Iterator it = default_service.iterator();
	  while (it.hasNext())
	  { // appeand each clause to the string in turn
	    ServiceToken token = (ServiceToken)(it.next());
	    if (sql.length()==0)
	      sql.append("INSERT INTO sigftrs (sigid, ftr_code) VALUES ");
	    else
	      sql.append(", ");
	    sql.append('(').append(new_cid).append(", ").append(token.getIndex()).append(')');
	    
	  } // end while

	  // close out the SQL statement
	  sql.append(';');
	  if (logger.isDebugEnabled())
	    logger.debug("SQL: " + sql.toString());

	  stmt.executeUpdate(sql.toString());  // do it!

	} // end if

	// Lastly, we need to give the "host UID" host privs in the community.  The host's membership
	// is "locked" so they can't unjoin and leave the community hostless.
	sql.setLength(0);
	sql.append("INSERT INTO sigmember(sigid, uid, granted_lvl, locked) VALUES (").append(new_cid);
	sql.append(", ").append(host_uid).append(", ");
	sql.append(env.getCommunityDefaultRole("Community.Creator").getLevel()).append(", 1);");
	if (logger.isDebugEnabled())
	  logger.debug("SQL: " + sql.toString());
	stmt.executeUpdate(sql.toString());

	// Create the CommunityCoreData object representing this community and register it with the engine's
	// community data object cache.
	comm = new CommunityCoreData(env,new_cid,creation,name,alias,host_uid,language,synopsis,rules,joinkey,
				     hide_dir,hide_search,levels,default_service);
	comm.newProperties(conn);

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

      } // end finally

      env.getEngine().registerNewCommunity(comm);
    
      // create an audit record indicating we were successful
      ar = new AuditRecord(AuditRecord.CREATE_COMMUNITY,host_uid,env.getUser().userRemoteAddress(),new_cid,
			   "name=" + name,"alias=" + alias);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("DB error creating community: " + e.getMessage(),e);
      throw new DataException("unable to create community: " + 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 comm;

  } // end createCommunity

  /*--------------------------------------------------------------------------------
   * 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 CommunityCoreData
