/*
 * 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.*;
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;
import com.silverwrist.venice.security.Role;
import com.silverwrist.venice.svc.*;

class CommunityUserContextImpl implements CommunityContext, CommunityBackend
{
  /*--------------------------------------------------------------------------------
   * Internal class for caching the community name and alias
   *--------------------------------------------------------------------------------
   */

  class CommunitySimpleDataCache
  {
    private String name;
    private String alias;

    public CommunitySimpleDataCache(String name, String alias)
    {
      this.name = name;
      this.alias = alias;

    } // end constructor

    public final String getName()
    {
      return name;

    } // end getName

    public final String getAlias()
    {
      return alias;

    } // end getAlias

  } // end class CommunitySimpleDataCache

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

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

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

  private EnvCommunity env;           // the environment
  private int cid;                    // ID of the underlying community
  private int level;                  // access level we have to the community
  private boolean is_member;          // are we a member of the community?
  private boolean show_admin;         // do we show up as an admin?
  private boolean locked;             // is our membership locked?
  private CommunitySimpleDataCache cache;   // cache object for name and alias
  private CommunityData data = null;  // the actual community data
  private boolean deleted = false;    // has this community been deleted?
  private ServiceToken x_conf_token;  // token for Conferences service

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

  protected CommunityUserContextImpl(EnvUser env, int cid, int granted_level, boolean locked, String name,
				     String alias)
  {
    if (logger.isDebugEnabled())
      logger.debug("CommunityUserContextImpl constructor:member");
    this.env = new EnvCommunity(env,this);
    this.cid = cid;
    setMemberValues(granted_level,true,locked);
    this.cache = new CommunitySimpleDataCache(name,alias);
    this.x_conf_token = env.getSCM().getTokenForSymbol(ServiceControl.SVCGRP_COMMUNITY,"Conference");
    
  } // end constructor

  protected CommunityUserContextImpl(EnvUser env, int cid, String name, String alias)
  {
    if (logger.isDebugEnabled())
      logger.debug("CommunityUserContextImpl constructor:ordinary");
    this.env = new EnvCommunity(env,this);
    this.cid = cid;
    this.level = env.getUser().realBaseLevel();
    this.is_member = false;
    this.show_admin = false;
    this.locked = false;
    this.cache = new CommunitySimpleDataCache(name,alias);
    this.x_conf_token = env.getSCM().getTokenForSymbol(ServiceControl.SVCGRP_COMMUNITY,"Conference");

  } // end constructor

  CommunityUserContextImpl(EnvUser env, CommunityData data)
  {
    if (logger.isDebugEnabled())
      logger.debug("CommunityUserContextImpl constructor:newCommunity");
    EnvCommunity new_env = new EnvCommunity(env,this);
    this.env = new_env;
    this.cid = data.getID();
    this.cache = null;  // no cache required - we have the CommunityData
    this.data = data;
    setMemberValues(new_env.getDefaultRole("Community.Creator").getLevel(),true,true);
    this.x_conf_token = env.getSCM().getTokenForSymbol(ServiceControl.SVCGRP_COMMUNITY,"Conference");

  } // end constructor

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

  private final void setMemberValues(int granted_level, boolean member, boolean locked)
  {
    if (logger.isDebugEnabled())
      logger.debug("setMemberValues(" + String.valueOf(granted_level) + ", " + String.valueOf(member)
		   + ", " + String.valueOf(locked) + ")");

    this.level = Math.max(env.getUser().realBaseLevel(),granted_level);
    this.is_member = member;
    this.show_admin = env.isLevelAdmin(granted_level);
    this.locked = locked;

  } // end setMemberValues

  private final CommunityData getData() throws DataException
  {
    if (data==null)
    { // attempt to load the CommunityData object
      if (deleted)
	throw new DataException("This community has been deleted.");
      data = env.getEngine().getCommunityDataObject(cid);
      if (data!=null)
	env.getUser().saveMRU("community",data);

      // clear cache when we get the real data
      cache = null;

    } // end if

    return data;

  } // end getData

  private final CommunityData getDataNE()
  {
    if (data==null)
    { // we need to load the CommunityData...
      if (deleted)
	return null;  // we're deleted
      try
      { // attempt to load the CommunityDataObject
	data = env.getEngine().getCommunityDataObject(cid);
	if (data!=null)
	  env.getUser().saveMRU("community",data);

      } // end try
      catch (DataException e)
      { // there was a data failure loading the community
	logger.error("** Got DataException in getDataNE: " + e.getMessage(),e);
	return null;

      } // end catch

      // clear cache when we get the real data
      cache = null;

    } // end if

    return data;

  } // end getDataNE

  private final void testConferenceAccess() throws DataException, AccessError
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    if (!(getData().hasService(x_conf_token)))
    { // the community doesn't use conferencing
      logger.error("the community doesn't use conferencing");
      throw new AccessError("This community does not use the conferencing feature.");

    } // end if

    getData().testMembership(level,is_member);
    CommService svc = (CommService)(env.getSCM().getServiceForToken(x_conf_token));
    String perm = svc.getPermission();
    if (perm!=null)
    { // test the permission
      if (!(env.testPermission(perm)))
      { // you can't access the conferences!
	logger.error("user not permitted to read confs from this community");
	throw new AccessError("You are not permitted to access this community's conferences.");
	
      } // end if

    } // end if

    Role r = svc.getRole();
    if (!(r.isSatisfiedBy(level)))
    { // you can't access the conferences!
      logger.error("user not permitted to read confs from this community");
      throw new AccessError("You are not permitted to access this community's conferences.");

    } // end if

  } // end testConferenceAccess

  private static final CommunityUserContextImpl getCommunityPrivate(EnvUser env, Connection conn, int cid)
      throws DataException
  {
    Statement stmt = null;

    try
    { // create the query to find the community in the table
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT signame, alias FROM sigs WHERE sigid = ");
      sql.append(cid).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
      { // the community entry was not found
	logger.error("Community " + String.valueOf(cid) + " not found in database");
	throw new DataException("Community #" + String.valueOf(cid) + " was not found in the database.");

      } // end if

      // initialize the object and check membership info
      CommunityUserContextImpl sc = new CommunityUserContextImpl(env,cid,rs.getString(1),rs.getString(2));
      SQLUtil.shutdown(rs);
      sc.checkMembership(conn);
      return sc;

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

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

    } // end finally

  } // end getCommunityPrivate

  /*--------------------------------------------------------------------------------
   * Implementations from class CommunityContext
   *--------------------------------------------------------------------------------
   */

  public int getCommunityID()
  {
    return cid;

  } // end getCommunityID

  public String getName()
  {
    if (cache!=null)
    { // return cached name if possible
      if (cache.getName()!=null)
	return cache.getName();

    } // end if

    CommunityData d = getDataNE();
    if (d!=null)
      return d.getName();
    else
      return null;

  } // end getName

  public String getAlias()
  {
    if (cache!=null)
    { // return cached alias if possible
      if (cache.getAlias()!=null)
	return cache.getAlias();

    } // end if

    CommunityData d = getDataNE();
    if (d!=null)
      return d.getAlias();
    else
      return null;

  } // end getAlias

  public boolean isMember()
  {
    return is_member;

  } // end isMember

  public boolean isAdmin()
  {
    return show_admin;

  } // end isAdmin

  public boolean isPublicCommunity()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.isPublicCommunity();
    else
      return false;

  } // end isPublicCommunity

  public int getCategoryID()
  {
    if (env.getEngine().getParamBoolean(EngineBackend.BP_NOCATEGORIES))
      return 0;
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getCategoryID();
    else
      return -1;

  } // end getCategoryID

  public CategoryDescriptor getCategory() throws DataException
  {
    if (env.getEngine().getParamBoolean(EngineBackend.BP_NOCATEGORIES))
      throw new DataException("The category system has been disabled on this site.");
    if (deleted)
      throw new DataException("This community has been deleted.");
    return new CategoryDescriptorImpl(env,getData().getCategoryID(),
				      !(env.testPermission(EnvUser.PERM_SHOWHIDDENCATS)));

  } // end getCategory

  public String getSynopsis()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getSynopsis();
    else
      return null;

  } // end getSynopsis

  public int getHostUID()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getHostUID();
    else
      return -1;

  } // end getHostUID

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

    Connection conn = null;      // pooled database connection

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

      // load the profile for the user
      return new UserProfileImpl(env,conn,getData().getHostUID());

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      throw new DataException("unable to retrieve host contact information: " + e.getMessage(),e);

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

    } // end finally

  } // end getHostProfile

  public String getLanguageCode()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getLanguage();
    else
      return null;

  } // end getLanguageCode

  public String getLanguageFullName()
  {
    String code = getLanguageCode();
    if (code!=null)
    { // translate the language code to a name
      Language l = International.get().getLanguageForCode(code);
      return ((l==null) ? null : l.getName());

    } // end if
    else
      return null;

  } // end getLanguageFullName

  public String getRules()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getRules();
    else
      return null;

  } // end getRules

  public java.util.Date getCreationDate()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getCreationDate();
    else
      return null;

  } // end getCreationDate

  public java.util.Date getLastAccessDate()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getLastAccessDate();
    else
      return null;

  } // end getLastAccessDate

  public java.util.Date getLastUpdateDate()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getLastUpdateDate();
    else
      return null;

  } // end getLastUpdateDate

  public ContactInfo getContactInfo() throws DataException
  {
    int id = getData().getContactID();
    ContactInfo rc;
    if (id>=0)
      rc = new ContactInfoImpl(env,id,true);
    else
      rc = new ContactInfoImpl(getData().getHostUID(),cid,true);
    getData().touch();
    return rc;

  } // end getContactInfo

  public void putContactInfo(ContactInfo ci) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (ci.getOwnerCommunityID()!=cid)
    { // this isn't a valid contact info for the community
      logger.error("ContactInfo wrong ownership (was " + String.valueOf(ci.getOwnerCommunityID())
		   + ", should be " + String.valueOf(cid) + ")");
      throw new DataException("invalid contact information record");

    } // end if

    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community contact info
      logger.error("user not permitted to modify the community's contact info");
      throw new AccessError("You are not permitted to modify the community's contact information.");

    } // end if

    getData().putContactInfo(env,ci);

  } // end putContactInfo

  public Set getServices()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getServices(env);
    else
      return null;

  } // end getServices

  public Set getConfiguredServices()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getServices();
    else
      return null;

  } // end getConfiguredServices

  public void setServiceEnable(ServiceToken token, boolean enable) throws AccessError, DataException
  {
    if (token.getGroup()!=ServiceToken.SVCGRP_COMMUNITY)
      throw new IllegalArgumentException("Not a community service token.");
    Service svc = env.getSCM().getServiceForToken(token);
    if (svc==null)
      throw new IllegalArgumentException("Invalid service token.");

    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    if (svc.isLocked())
      return;  // can't modify the state of a locked service

    getData().setServiceEnable(env,token,enable);

  } // end setServiceEnable

  public void setName(String name) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    getData().setName(env,name);

  } // end setName

  public void setAlias(String alias) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    getData().setAlias(env,alias);

  } // end setAlias

  public void setCategoryID(int catid) throws DataException, AccessError
  {
    if (env.getEngine().getParamBoolean(EngineBackend.BP_NOCATEGORIES))
      throw new DataException("The category system has been disabled on this site.");
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    if (getData().isAdminCommunity())
    { // the Admin Community cannot change its security
      logger.error("trying to change category for the ADMIN community");
      throw new AccessError("The administrative community's category cannot be modified.");

    } // end if

    getData().setCategoryID(env,catid);

  } // end setCategoryID

  public void setCategory(CategoryDescriptor cat) throws DataException, AccessError
  {
    setCategoryID(cat.getCategoryID());

  } // end setCategory

  public void setSynopsis(String synopsis) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    getData().setSynopsis(synopsis);

  } // end setSynopsis

  public void setLanguage(String language) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    getData().setLanguage(language);

  } // end setLanguage

  public void setRules(String rules) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    getData().setRules(rules);

  } // end setRules

  public int getHideMode()
  {
    CommunityData d = getDataNE();
    if (d==null)
      return HIDE_NONE;
    if (d.getHideSearch())
      return HIDE_BOTH;
    else if (d.getHideDirectory())
      return HIDE_DIRECTORY;
    else
      return HIDE_NONE;

  } // end getHideMode

  public void setHideMode(int mode) throws DataException, AccessError
  {
    if ((mode!=HIDE_NONE) && (mode!=HIDE_DIRECTORY) && (mode!=HIDE_BOTH))
    { // the mode is not valid
      logger.error("mode value " + mode + " is not valid");
      throw new IllegalArgumentException("invalid hide mode value");

    } // end if

    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    if (getData().isAdminCommunity())
    { // the Admin Community cannot change its security
      logger.error("trying to change security info for the ADMIN community");
      throw new AccessError("The administrative community's security information cannot be modified.");

    } // end if

    boolean hide_dir = (mode!=HIDE_NONE);
    boolean hide_search = (mode==HIDE_BOTH);
    getData().setHideFlags(env,hide_dir,hide_search);

  } // end setHideMode

  public boolean getMembersOnly()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getMembersOnly();
    else
      return true;

  } // end getMembersOnly

  public void setMembersOnly(boolean flag) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    if (getData().isAdminCommunity())
    { // the Admin community cannot change its security
      logger.error("trying to change security info for the ADMIN community");
      throw new AccessError("The administrative community's security information cannot be modified.");

    } // end if

    getData().setMembersOnly(env,flag);

  } // end setMembersOnly

  public ServiceToken getDefaultService()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.getDefaultService();
    else
      return null;

  } // end getDefaultService

  public void setDefaultService(ServiceToken token) throws AccessError, DataException
  {
    if (token.getGroup()!=ServiceToken.SVCGRP_COMMUNITY)
      throw new IllegalArgumentException("Not a community service token.");
    Service svc = env.getSCM().getServiceForToken(token);
    if (svc==null)
      throw new IllegalArgumentException("Invalid service token.");
    if (!(getData().hasService(token)))
      throw new DataException("This service is not enabled for this community.");
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    getData().setDefaultService(token);

  } // end setDefaultService

  public String getJoinKey() throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to retrieve join key");
      throw new AccessError("You are not permitted to retrieve the join key for this community.");

    } // end if

    return getData().getJoinKey();

  } // end getJoinKey

  public void setJoinKey(String key) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    if (getData().isAdminCommunity())
    { // the Admin community cannot change its security
      logger.error("trying to change security info for the ADMIN community");
      throw new AccessError("The administrative community's security information cannot be modified.");

    } // end if

    getData().setJoinKey(env,key);

  } // end setJoinKey

  public int getReadLevel() throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to retrieve security info");
      throw new AccessError("You are not permitted to retrieve security information for this community.");

    } // end if

    return getData().getReadLevel();

  } // end getReadLevel

  public int getWriteLevel() throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to retrieve security info");
      throw new AccessError("You are not permitted to retrieve security information for this community.");

    } // end if

    return getData().getWriteLevel();

  } // end getWriteLevel

  public int getCreateLevel() throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to retrieve security info");
      throw new AccessError("You are not permitted to retrieve security information for this community.");

    } // end if

    return getData().getCreateLevel();

  } // end getCreateLevel

  public int getDeleteLevel() throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to retrieve security info");
      throw new AccessError("You are not permitted to retrieve security information for this community.");

    } // end if

    return getData().getDeleteLevel();

  } // end getDeleteLevel

  public int getJoinLevel() throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to retrieve security info");
      throw new AccessError("You are not permitted to retrieve security information for this community.");

    } // end if

    return getData().getJoinLevel();

  } // end getJoinLevel

  public void setSecurityLevels(int read, int write, int create, int delete, int join)
      throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    if (getData().isAdminCommunity())
    { // the Admin community cannot change its security
      logger.error("trying to change security info for the ADMIN community");
      throw new AccessError("The administrative community's security information cannot be modified.");

    } // end if

    if (read>write)
    { // correct the write level...
      if (logger.isDebugEnabled())
	logger.debug("read level (" + String.valueOf(read) + ") greater than write level ("
		     + String.valueOf(write) + "); correcting write level");
      write = read;

    } // end if

    if (write>delete)
    { // correct the delete level...
      if (logger.isDebugEnabled())
	logger.debug("write level (" + String.valueOf(write) + ") greater than delete level ("
		     + String.valueOf(delete) + "); correcting delete level");
      delete = write;

    } // end if

    if (create>delete)
    { // correct the create level...
      if (logger.isDebugEnabled())
	logger.debug("create level (" + String.valueOf(create) + ") greater than delete level ("
		     + String.valueOf(delete) + "); correcting create level");
      create = delete;

    } // end if

    getData().setSecurityLevels(env,read,write,create,delete,join);

  } // end setSecurityLevels

  public boolean canAdministerCommunity()
  {
    CommunityData d = getDataNE();
    if (d==null)
      return false;

    if (d.canDeleteCommunity(level))
      return true;
    if (d.canModifyCommunityProfile(level))
      return true;
    if (d.canCreateCommunitySubObjects(level))
      return true;

    return false;
    
  } // end canAdministerCommunity

  public boolean canModifyProfile()
  {
    CommunityData d = getDataNE();
    if (d==null)
      return false;

    return d.canModifyCommunityProfile(level);

  } // end canModifyProfile

  public boolean isAdminCommunity()
  {
    CommunityData d = getDataNE();
    if (d!=null)
      return d.isAdminCommunity();
    else
      return false;

  } // end isAdminCommunity

  public void join(String joinkey) throws DataException, AccessError
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    if (is_member)
    { // we're already a member!
      logger.info("attempting to join community, but already a member");
      return;  // this becomes a no-op

    } // end if

    if (!(getData().canJoinCommunity(env.getUserID(),level)))
    { // this user can't join up!
      logger.error("user not permitted to join community");
      throw new AccessError("You are not permitted to join this community.");

    } // end if

    if (!(getData().isPublicCommunity()))
    { // this is a private community - but admins can join anyway
      if (!(env.testPermission(EnvCommunity.PERM_NOKEYREQUIRED)))
      { // we need to test the join key against the one they supply
	String real_key = getData().getJoinKey();
	if (!(real_key.equals(joinkey)))
	{ // the join key was not the one stored in the community
	  logger.error("invalid join key specified");
	  throw new AccessError("The join key you specified was incorrect.");

	} // end if

      } // end if
      // else the admin person can join, that's cool

    } // end if
    // else we can join without specifying a key

    // actually set the data in the database
    Role new_role = env.getDefaultRole("Community.NewUser");
    getData().setMembership(env,env.getUserID(),new_role.getLevel(),false,false);

    // and update our internal data store
    setMemberValues(new_role.getLevel(),true,false);

    // and that's it!  You expected lightning bolts maybe?

  } // end join

  public void unjoin() throws DataException, AccessError
  {
    if (deleted)
      throw new DataException("This community has been deleted.");

    if (!is_member)
    { // we're already a member!
      logger.info("attempting to unjoin community, but not currently a member");
      return;  // this becomes a no-op

    } // end if

    if (locked)
    { // we're locked - we can't possibly unjoin!
      logger.info("membership in community is locked, cannot remove");
      throw new AccessError("You are not permitted to unjoin this community.");

    } // end if

    // actually set the data in the database
    getData().setMembership(env,env.getUserID(),-1,false,false);

    // and update our internal data store
    setMemberValues(-1,false,false);

  } // end unjoin

  public int getMemberCount() throws DataException
  {
    return getData().getMemberCount(env.testPermission(EnvCommunity.PERM_SHOWHIDDENMEMBERS));

  } // end getMemberCount

  public boolean canUnjoin()
  {
    return !deleted && is_member && !locked;

  } // end canUnjoin

  public boolean canJoin()
  {
    if (is_member || deleted)
      return false;
    CommunityData d = getDataNE();
    if (d!=null)
      return d.canJoinCommunity(env.getUserID(),level);
    else
      return false;
    
  } // end canJoin

  public List getConferences() throws DataException, AccessError
  {
    testConferenceAccess();
    return ConferenceUserContextImpl.getCommunityConferences(env);

  } // end getConferences

  public ConferenceContext getConferenceContext(int confid) throws DataException, AccessError
  {
    testConferenceAccess();
    return ConferenceUserContextImpl.getConference(env,confid);

  } // end getConferenceContext

  public ConferenceContext getConferenceContext(String alias) throws DataException, AccessError
  {
    testConferenceAccess();
    return ConferenceUserContextImpl.getConference(env,alias);

  } // end getConferenceContext

  public ConferenceContext createConference(String name, String alias, String description, boolean pvt,
					    boolean hide_list) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().hasService(x_conf_token)))
    { // we can't create a conference in this community
      logger.error("this community does not use conferencing");
      throw new AccessError("You are not permitted to create conferences in this community.");

    } // end if

    if (!(getData().canCreateCommunitySubObjects(level)))
    { // this user can't create a conference
      logger.error("user not permitted to create a conference");
      throw new AccessError("You are not permitted to create conferences in this community.");

    } // end if

    // call down to the community core data to create the conference
    ConferenceCommunityContext cdata = getData().createConference(env,name,alias,description,pvt,hide_list);

    // wrap the returned object in a conference user context object and release the extra reference
    return new ConferenceUserContextImpl(env,cdata);

  } // end createConference

  public boolean canCreateConference()
  {
    CommunityData d = getDataNE();
    if (d==null)
      return false;
    if (!(d.checkMembership(level,is_member)))
      return false;
    if (d.hasService(x_conf_token))
      return d.canCreateCommunitySubObjects(level);
    else
      return false;

  } // end canCreateConference

  public List searchForMembers(int field, int mode, String term, int offset, int count)
      throws DataException
  {
    return getData().searchForMembers(field,mode,term,offset,count,
				      env.testPermission(EnvCommunity.PERM_SHOWHIDDENMEMBERS));

  } // end searchForMembers

  public int getSearchMemberCount(int field, int mode, String term) throws DataException
  {
    return getData().getSearchMemberCount(field,mode,term,
					  env.testPermission(EnvCommunity.PERM_SHOWHIDDENMEMBERS));

  } // end getSearchMemberCount

  public List getMemberList() throws DataException
  {
    return getData().getMemberList(env.testPermission(EnvCommunity.PERM_SHOWHIDDENMEMBERS));

  } // end getMemberList

  public boolean canManageConferences()
  {
    CommunityData d = getDataNE();
    if (d==null)
      return false;
    if (!(d.checkMembership(level,is_member)))
      return false;
    if (d.hasService(x_conf_token))
      return d.canCreateCommunitySubObjects(level);
    else
      return false;

  } // end canManageConferences

  public int getMemberLevel(int uid) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);

    return getData().getMemberLevel(uid);

  } // end getMemberLevel

  public void setMembership(int uid, int new_level) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's membership");
      if (logger.isDebugEnabled())
	logger.debug("(your level = " + level + ")");
      throw new AccessError("You are not permitted to modify the community's membership.");

    } // end if

    // actually set the data in the database
    getData().setMembership(env,uid,new_level,false,false);

    if (uid==env.getUserID())  // and update our internal data store
      setMemberValues(new_level,(new_level>0),false);

  } // end setMembership

  public boolean canDelete()
  {
    CommunityData d = getDataNE();
    if (d==null)
      return false;
    if (!(d.checkMembership(level,is_member)))
      return false;
    return d.canDeleteCommunity(level);

  } // end canDelete

  public void delete() throws DataException, AccessError
  {
    CommunityData my_comm = getData();
    my_comm.testMembership(level,is_member);
    if (!(my_comm.canDeleteCommunity(level)))
    { // no, repeat NO, way can we delete the community!
      logger.error("user not permitted to delete community");
      throw new AccessError("You are not permitted to delete this community.");

    } // end if

    // call the methods required to delete the community
    my_comm.delete(env);
    env.getEngine().detachCommunityDataObject(cid);

    // flag that we've been deleted
    cache = null;
    deleted = true;

    // detach our references from the lower-level object
    data = null;

  } // end delete

  public void deleteCommunity() throws DataException, AccessError
  {
    this.delete();

  } // end delete

  public void sendInvitation(String address, String personal_message)
      throws AccessError, DataException, EmailException
  {
    if (env.getUser().userIsAnonymous())
      throw new AccessError("You must be logged in to send an invitation.");

    CommunityData my_comm = getData();
    my_comm.testMembership(level,is_member);

    // Prepare the subject line to be sent to the user.
    String subject = env.getEngine().getStockMessage("subj-invite");
    HashMap vars = new HashMap(5);
    vars.put("community.name",my_comm.getName());
    subject = StringUtil.replaceAllVariables(subject,vars);

    // Prepare the message text to be sent to the user.
    String msg;
    if (my_comm.isPublicCommunity())
      msg = env.getEngine().getStockMessage("invite-public");
    else
    { // get the private invite message and set the join key variable
      msg = env.getEngine().getStockMessage("invite-private");
      vars.put("joinkey",my_comm.getJoinKey());

    } // end else

    // Set the remaining variables and replace them.
    vars.put("community.alias",my_comm.getAlias());
    vars.put("personal",personal_message);
    vars.put("fullname",env.getUser().realFullName());
    String uname = env.getUser().realUserName();
    vars.put("username",uname);
    msg = StringUtil.replaceAllVariables(msg,vars);
    StringBuffer msg_buf = new StringBuffer(msg);
    msg_buf.append("\n\n--\n").append(env.getEngine().getStockMessage("signature"));

    // Get a Emailer object, set it up, and send it.
    Emailer em = env.getEngine().createEmailer();
    em.setFrom(uname,env.getUser().realEmailAddress());
    em.setTo(address);
    em.setSubject(subject);
    em.setText(msg_buf.toString());
    em.send();

  } // end sendInvitation

  public boolean canSendInvitation()
  {
    if (env.getUser().userIsAnonymous())
      return false;
    CommunityData d = getDataNE();
    if (d==null)
      return false;
    return d.checkMembership(level,is_member);

  } // end canSendInvitation

  public List getAuditRecords(int offset, int count) throws AccessError, DataException
  {
    CommunityData d = getData();
    if (   !d.canDeleteCommunity(level) && !d.canModifyCommunityProfile(level)
	&& !d.canCreateCommunitySubObjects(level))
    { // user not permitted to retrieve audit records - naughty naughty
      logger.error("user not permitted to retrieve audit records for community");
      throw new AccessError("You are not permitted to retrieve audit records for this community.");

    } // end if

    Connection conn = null;
    List rc = null;

    try
    { // retrieve a connection from the data pool and get the audit records
      conn = env.getConnection();
      rc = AuditRecord.getAuditRecords(conn,cid,offset,count);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("error loading audit records: " + e.getMessage(),e);
      throw new DataException("unable to load audit records: " + e.getMessage(),e);

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

    } // end finally

    return rc;

  } // end getAuditRecords

  public int getAuditRecordCount() throws AccessError, DataException
  {
    CommunityData d = getData();
    if (   !d.canDeleteCommunity(level) && !d.canModifyCommunityProfile(level)
	&& !d.canCreateCommunitySubObjects(level))
    { // user not permitted to retrieve audit records - naughty naughty
      logger.error("user not permitted to retrieve audit records for community");
      throw new AccessError("You are not permitted to retrieve audit records for this community.");

    } // end if

    Connection conn = null;
    int rc = -1;

    try
    { // retrieve a connection from the data pool and get the audit records
      conn = env.getConnection();
      rc = AuditRecord.getAuditRecordCount(conn,cid);

    } // end try
    catch (SQLException e)
    { // database error - this is a DataException
      logger.error("error loading audit record count: " + e.getMessage(),e);
      throw new DataException("unable to load audit record count: " + e.getMessage(),e);

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

    } // end finally

    return rc;

  } // end getAuditRecordCount

  public CommunityProperties getProperties() throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    // call down to do it
    return getData().getProperties();

  } // end getProperties

  public void setProperties(CommunityProperties props) throws DataException, AccessError
  {
    getData().testMembership(level,is_member);
    if (!(getData().canModifyCommunityProfile(level)))
    { // this user can't modify the community feature set
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to modify the community's information.");

    } // end if

    // call down to do it
    getData().setProperties(props);

  } // end setProperties

  public SecurityInfo getSecurityInfo()
  {
    return env.getSecurityInfo();

  } // end getSecurityInfo

  public boolean canMassMail()
  {
    return env.testPermission("Community.MassMail");

  } // end canMassMail

  public void massMail(String subject, String text) throws AccessError, DataException
  {
    if (!(env.testPermission("Community.MassMail")))
    { // no can do, man!
      logger.error("user not permitted to modify the community's information");
      throw new AccessError("You are not permitted to send E-mail to all community members.");
      
    } // end if

    // get the mailing list
    List mail_list = getData().getMassMailList();
    if (mail_list.size()>0)
    { // prepare the E-mail message text; append the disKlaimer at the end
      HashMap vars = new HashMap();
      vars.put("community.name",env.getCommunityName());
      String disklaimer = env.getStockMessage("mass-community-notice");
      StringBuffer buf = new StringBuffer(text);
      buf.append("\n\n").append(StringUtil.replaceAllVariables(disklaimer,vars));

      // send the mail in the background!
      MailerAgent agent = new MailerAgent(env,env.getUser().realUserName(),env.getUser().realEmailAddress(),
					  mail_list,subject,buf.toString());
      agent.start();

    } // end if

  } // end massMail

  public List searchPosts(String search_terms, int offset, int count) throws AccessError, DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("searchPosts('" + search_terms + "', " + offset + ", " + count + ") entry");
    if (search_terms==null)
      throw new NullPointerException("invalid search terms");
    if (offset<0)
      throw new IllegalArgumentException("invalid offset parameter");
    if (count<=0)
      throw new IllegalArgumentException("invalid count parameter");
    testConferenceAccess();

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

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

      // create the SQL statement
      StringBuffer sql =
	  new StringBuffer("SELECT c.confid, t.topicid, t.num, p.postid, p.num, p.creator_uid, p.posted, "
			   + "p.linecount, d.data FROM confs c, sigtoconf s, topics t, posts p, postdata d, "
			   + "users u LEFT JOIN sigmember m ON (s.sigid = m.sigid AND u.uid = m.uid) "
			   + "LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid) "
			   + "WHERE u.uid = ");
      sql.append(env.getUserID()).append(" AND c.confid = s.confid AND s.sigid = ");
      sql.append(cid);
      sql.append(" AND GREATEST(u.base_lvl,IFNULL(m.granted_lvl,0),s.granted_lvl,IFNULL(x.granted_lvl,0)) "
		 + ">= c.read_lvl AND t.confid = c.confid AND t.topicid = p.topicid AND "
		 + "p.scribble_uid IS NULL AND p.postid = d.postid AND MATCH(d.data) AGAINST (");
      sql.append(SQLUtil.encodeStringArg(search_terms)).append(") LIMIT ").append(offset).append(", ");
      sql.append(count+1).append(';');

      // execute the query and load the results into the return arraylist
      ResultSet rs = stmt.executeQuery(sql.toString());
      while (rs.next())
	rc.add(new TopicMessageFoundImpl(helper,cid,rs.getInt(1),rs.getInt(2),rs.getInt(3),rs.getLong(4),
					 rs.getInt(5),rs.getInt(6),SQLUtil.getFullDateTime(rs,7),rs.getInt(8),
					 rs.getString(9)));

    } // end try
    catch (SQLException e)
    { // this becomes a DataException
      logger.error("DB error searching for posts: " + e.getMessage(),e);
      throw new DataException("unable to search: " + e.getMessage(),e);

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

    } // end finally

    if (rc.isEmpty())
      return Collections.EMPTY_LIST;
    else
      return Collections.unmodifiableList(rc);

  } // end searchPosts

  public int getSearchPostCount(String search_terms) throws AccessError, DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("getSearchPostCount('" + search_terms + ") entry");
    if (search_terms==null)
      throw new NullPointerException("invalid search terms");
    testConferenceAccess();

    Connection conn = null;
    Statement stmt = null;

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

      // create the SQL statement
      StringBuffer sql =
	  new StringBuffer("SELECT COUNT(*) FROM confs c, sigtoconf s, topics t, posts p, postdata d, "
			   + "users u LEFT JOIN sigmember m ON (s.sigid = m.sigid AND u.uid = m.uid) "
			   + "LEFT JOIN confmember x ON (c.confid = x.confid AND u.uid = x.uid) "
			   + "WHERE u.uid = ");
      sql.append(env.getUserID()).append(" AND c.confid = s.confid AND s.sigid = ");
      sql.append(cid);
      sql.append(" AND GREATEST(u.base_lvl,IFNULL(m.granted_lvl,0),s.granted_lvl,IFNULL(x.granted_lvl,0)) "
		 + ">= c.read_lvl AND t.confid = c.confid AND t.topicid = p.topicid AND "
		 + "p.scribble_uid IS NULL AND p.postid = d.postid AND MATCH(d.data) AGAINST (");
      sql.append(SQLUtil.encodeStringArg(search_terms)).append(");");

      // execute the query and load the results
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
	throw new InternalStateError("query failure in CommunityUserContextImpl.getSearchPostCount!");
      return rs.getInt(1);

    } // end try
    catch (SQLException e)
    { // this becomes a DataException
      logger.error("DB error searching for posts: " + e.getMessage(),e);
      throw new DataException("unable to search: " + e.getMessage(),e);

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

    } // end finally

  } // end getSearchPostCount

  /*--------------------------------------------------------------------------------
   * Implementations from interface CommunityBackend
   *--------------------------------------------------------------------------------
   */

  public CommunityContext selfCommunity()
  {
    return this;

  } // end selfCommunity

  public int realCommunityID()
  {
    return cid;

  } // end realCommunityID

  public boolean userHideHiddenConferences()
  {
    return !(env.testPermission(EnvCommunity.PERM_SHOWHIDDENOBJECTS));

  } // end userHideHiddenConferences

  public int realCommunityLevel()
  {
    return level;

  } // end realCommunityLevel

  public ConferenceCommunityContext getConferenceDataObject(int confid) throws DataException
  {
    return getData().getConferenceDataObject(confid);

  } // end getConferenceDataObject

  public boolean userCanCreateSubobjects()
  {
    CommunityData d = getDataNE();
    if (d==null)
      return false;
    if (d.checkMembership(level,is_member))
      return d.canCreateCommunitySubObjects(level);
    else
      return false;

  } // end userCanCreateSubObjects

  public String realCommunityAlias()
  {
    return getAlias();

  } // end realCommunityAlias

  public void detachConferenceDataObject(int confid) throws DataException
  {
    getData().detachConferenceDataObject(confid);

  } // end detachConferenceDataObject

  public boolean userCanDeleteSubobjects()
  {
    return userCanCreateSubobjects();

  } // end userCanDeleteSubobjects

  public CommunityDataBackend getDataBackend() throws DataException
  {
    return (CommunityDataBackend)(getData());

  } // end getDataBackend

  public boolean env_testPermission(String symbol)
  {
    if (deleted)
      return false;
    CommunityData d = getDataNE();
    if (d==null)
      return false;

    if (symbol.equals(EnvCommunity.PERM_JOIN))
      return (is_member ? false : d.canJoinCommunity(env.getUserID(),level));

    if (!(d.checkMembership(level,is_member)))
      return false;

    if (symbol.equals(EnvCommunity.PERM_READ))
      return d.canReadCommunitySubObjects(level);
    if (symbol.equals(EnvCommunity.PERM_WRITE))
      return d.canModifyCommunityProfile(level);
    if (symbol.equals(EnvCommunity.PERM_CREATE))
      return d.canCreateCommunitySubObjects(level);
    if (symbol.equals(EnvCommunity.PERM_DELETE))
      return d.canDeleteCommunity(level);

    return false;

  } // end env_testPermission

  public String env_getCommunityName()
  {
    return this.getName();

  } // end env.getCommunityName()

  /*--------------------------------------------------------------------------------
   * Static operations for use within the implementation package
   *--------------------------------------------------------------------------------
   */

  static final List getMemberCommunityEntries(EnvUser env) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("getMemberCommunityEntries for user #" + String.valueOf(env.getUserID()));
    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();
      StringBuffer sql = new StringBuffer("SELECT sm.sigid, sm.granted_lvl, sm.locked, s.signame, s.alias "
					  + "FROM sigmember sm, sigs s WHERE sm.sigid = s.sigid "
					  + "AND sm.uid = ");
      sql.append(env.getUserID()).append(" ORDER BY s.signame;");
      ResultSet rs = stmt.executeQuery(sql.toString());
      while (rs.next())
      { // create the user contexts and add them to the return vector
	int the_cid = rs.getInt(1);
	if (logger.isDebugEnabled())
	  logger.debug("...found community #" + the_cid);
	CommunityContext tmp = new CommunityUserContextImpl(env,the_cid,rs.getInt(2),rs.getBoolean(3),
							    rs.getString(4),rs.getString(5));
	rc.add(tmp);

      } // end while

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading community entries: " + e.getMessage(),e);
      throw new DataException("unable to retrieve user community 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 getMemberCommunityEntries

  static final CommunityContext getCommunityContext(EnvUser env, int cid) throws DataException
  {
    Connection conn = null;      // pooled database connection

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

      // return the community we want
      return getCommunityPrivate(env,conn,cid);

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

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

    } // end finally

  } // end getCommunityContext

  static final CommunityContext getCommunityContext(EnvUser env, String alias) throws DataException
  {
    Connection conn = null;      // pooled database connection
    Statement stmt = null;

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

      // create the query to find the community in the table
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT sigid, signame FROM sigs WHERE alias = '");
      sql.append(SQLUtil.encodeString(alias)).append("';");
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (!(rs.next()))
      { // no community entry found...
	logger.error("Community '" + alias + "' not found in the database");
	throw new DataException("The community '" + alias + "' was not found in the database.");

      } // end if

      // initialize the object and check membership info
      CommunityUserContextImpl c = new CommunityUserContextImpl(env,rs.getInt(1),rs.getString(2),alias);
      SQLUtil.shutdown(rs);
      c.checkMembership(conn);
      return c;

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading community entry: " + e.getMessage(),e);
      throw new DataException("unable to retrieve community 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 getCommunityContext

  static final CommunityBackend getCommunityBackend(EnvUser env, Connection conn, int cid) throws DataException
  {
    return getCommunityPrivate(env,conn,cid);

  } // end getCommunityBackend

  static final List searchForCommunities(EnvUser env, int field, int mode, String term, int offset, int count)
      throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("Community search: 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();
      StringBuffer sql = new StringBuffer("SELECT sigid, signame, alias FROM sigs WHERE ");

      switch (field)
      {
	case SearchMode.FIELD_COMMUNITY_NAME:
	  sql.append("signame ");
	  break;

	case SearchMode.FIELD_COMMUNITY_SYNOPSIS:
	  sql.append("synopsis ");
	  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 (!(env.testPermission(EnvUser.PERM_SEARCHHIDDENCOMMUNITIES)))
	sql.append(" AND hide_search = 0");
      sql.append(" ORDER BY signame LIMIT ").append(offset).append(", ").append(count+1).append(';');

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

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

      while (rs.next())
      { // load all matching communities into the recordset
	CommunityUserContextImpl c = new CommunityUserContextImpl(env,rs.getInt(1),rs.getString(2),
								  rs.getString(3));
	c.checkMembership(conn);
	CommunityContext tmp = c;
	rc.add(tmp);

      } // end while

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading community entries: " + e.getMessage(),e);
      throw new DataException("unable to retrieve user community 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 searchForCommunities

  static final int getSearchCommunityCount(EnvUser env, int field, int mode, String term)
      throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("Community search: 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 sigs WHERE ");

      switch (field)
      {
	case SearchMode.FIELD_COMMUNITY_NAME:
	  sql.append("signame ");
	  break;

	case SearchMode.FIELD_COMMUNITY_SYNOPSIS:
	  sql.append("synopsis ");
	  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 (!(env.testPermission(EnvUser.PERM_SEARCHHIDDENCOMMUNITIES)))
	sql.append(" AND hide_search = 0");
      sql.append(';');

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

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

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

      return rs.getInt(1);

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading community entries: " + e.getMessage(),e);
      throw new DataException("unable to retrieve user community 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 getSearchCommunityCount

  static final List getCommunitiesInCategory(EnvUser env, int catid, int offset, int count)
      throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("reading communities in category " + catid + ", 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();
      StringBuffer sql = new StringBuffer("SELECT sigid, signame, alias FROM sigs WHERE catid = ");
      sql.append(catid);
      if (!(env.testPermission(EnvUser.PERM_SHOWHIDDENCOMMUNITIES)))
	sql.append(" AND hide_dir = 0");
      sql.append(" ORDER BY signame LIMIT ").append(offset).append(", ").append(count+1).append(';');
      
      if (logger.isDebugEnabled())
	logger.debug("SQL: " + sql.toString());

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

      while (rs.next())
      { // load all matching communities into the recordset
	CommunityUserContextImpl c = new CommunityUserContextImpl(env,rs.getInt(1),rs.getString(2),
								  rs.getString(3));
	c.checkMembership(conn);
	CommunityContext tmp = c;
	rc.add(tmp);

      } // end while

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading communities entries: " + e.getMessage(),e);
      throw new DataException("unable to retrieve user community 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 getCommunitiesInCategory

  static final int getNumCommunitiesInCategory(EnvUser env, int catid) throws DataException
  {
    if (logger.isDebugEnabled())
      logger.debug("reading communities in category " + catid);

    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 sigs WHERE catid = ");
      sql.append(catid);
      if (!(env.testPermission(EnvUser.PERM_SHOWHIDDENCOMMUNITIES)))
	sql.append(" AND hide_dir = 0");
      sql.append(';');

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

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

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

      return rs.getInt(1);

    } // end try
    catch (SQLException e)
    { // turn SQLException into data exception
      logger.error("DB error reading community entries: " + e.getMessage(),e);
      throw new DataException("unable to retrieve user community 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 getNumCommunitiesInCategory

  /*--------------------------------------------------------------------------------
   * Operations for use within the class only
   *--------------------------------------------------------------------------------
   */

  protected void checkMembership(Connection conn) throws SQLException
  {
    Statement stmt = null;

    try
    { // look up the membership data
      stmt = conn.createStatement();
      StringBuffer sql = new StringBuffer("SELECT granted_lvl, locked FROM sigmember WHERE sigid = ");
      sql.append(cid).append(" AND uid = ").append(env.getUserID()).append(';');
      ResultSet rs = stmt.executeQuery(sql.toString());
      if (rs.next())
      { // we are a member...
	if (logger.isDebugEnabled())
	  logger.debug("we are a member of this community");
	setMemberValues(rs.getInt(1),true,rs.getBoolean(2));

      } // end if
      else
      { // we're NOT a member...
	if (logger.isDebugEnabled())
	  logger.debug("we are NOT a member of this community");
	setMemberValues(0,false,false);

      } // end else

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

    } // end finally

  } // end checkMembership

} // end class CommunityUserContextImpl
