/*
 * 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) 2003 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.venice.community;

import java.security.Principal;
import java.security.acl.AclNotFoundException;
import java.util.*;
import org.apache.commons.collections.*;
import org.apache.log4j.Logger;
import com.silverwrist.dynamo.db.NamespaceCache;
import com.silverwrist.dynamo.db.UserManagement;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.security.SecurityReferenceMonitor;
import com.silverwrist.dynamo.util.*;
import com.silverwrist.venice.CommunityVisibility;
import com.silverwrist.venice.VeniceNamespaces;
import com.silverwrist.venice.event.*;
import com.silverwrist.venice.except.*;
import com.silverwrist.venice.iface.*;

/**
 * Concrete implementation of the {@link com.silverwrist.venice.iface.VeniceCommunity VeniceCommunity} interface,
 * which provides all the basic community functions.
 *
 * @author Eric J. Bowersox &lt;erbo@silcom.com&gt;
 * @version X
 */
class CommunityImpl implements VeniceCommunity
{
  /*--------------------------------------------------------------------------------
   * Internal join cleanup task class.
   *--------------------------------------------------------------------------------
   */

  private class JoinCleanupTask
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private int m_ugid;
    private boolean m_is_group;

    /*====================================================================
     * Constructor
     *====================================================================
     */

    JoinCleanupTask(int ugid, boolean is_group)
    {
      m_ugid = ugid;
      m_is_group = is_group;

    } // end constructor

    /*====================================================================
     * External operations
     *====================================================================
     */

    void run() throws DatabaseException
    {
      m_ops.deleteSingleUseAuthData(m_id,m_ugid,m_is_group);

    } // end run

  } // end class JoinCleanupTask

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

  private static Logger logger = Logger.getLogger(CommunityImpl.class);

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

  private CommunityManager m_base;          // base object
  private CommunityOps m_ops;               // community operations object
  private NamespaceCache m_nscache;         // namespace cache object
  private SecurityReferenceMonitor m_srm;   // security reference monitor
  private UserManagement m_users;           // user management object
  private AuthenticatorLookup m_alook;      // authenticator lookup object
  private PostDynamicUpdate m_post;         // where we post dynamic update events
  private CategoryService m_cats;           // category service object
  private int m_id;                         // community ID
  private int m_member_gid;                 // member group ID
  private int m_host_gid;                   // hosts group ID
  private int m_host_uid;                   // primary host UID
  private int m_aclid;                      // ID of the community ACL
  private int m_catid;                      // category ID for the community
  private CommunityVisibility m_visibility; // community visibility
  private String m_name;                    // community name
  private String m_alias;                   // community alias
  private java.util.Date m_created;         // date created
  private java.util.Date m_lastaccessed;    // date last accessed
  private java.util.Date m_lastupdate;      // date last updated
  private ReferenceMap m_properties;        // properties cache
  private List m_service_list = null;       // cached service list

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

  /**
   * Constructs a new concrete community implementation.
   *
   * @param ops The {@link com.silverwrist.venice.community.CommunityOps CommunityOps} object providing access to the
   *            database.
   * @param nscache The {@link com.silverwrist.dynamo.db.NamespaceCache NamespaceCache} object providing conversion
   *                between namespace URIs and integer namespace identifiers.
   * @param srm The {@link com.silverwrist.dynamo.security.SecurityReferenceMonitor SecurityReferenceMonitor} object
   *            through which security is tested.
   * @param users The {@link com.silverwrist.dynamo.db.UserManagement UserManagetment} object used to retrieve user
   *              and group objects.
   * @param alook The {@link com.silverwrist.dynamo.iface.AuthenticatorLookup AuthenticatorLookup} interface used
   *              to find registered {@link com.silverwrist.dynamo.iface.Authenticator Authenticator} objects.
   * @param post The {@link com.silverwrist.dynamo.iface.PostDynamicUpdate PostDynamicUpdate} interface used
   *             to post dynamic update events.
   * @param cats The {@link com.silverwrist.venice.community.CategoryService CategoryService} interface used to
   *             retrieve category objects.
   * @param data A {@link java.util.Map Map} object containing the initial data for the community, with keys
   *             corresponding to static fields of the
   *             {@link com.silverwrist.venice.community.CommunityManagerOps CommunityManagerOps} object.
   */
  CommunityImpl(CommunityManager base, CommunityOps ops, NamespaceCache nscache, SecurityReferenceMonitor srm,
		UserManagement users, AuthenticatorLookup alook, PostDynamicUpdate post, CategoryService cats,
		Map data)
  {
    m_base = base;
    m_ops = ops;
    m_nscache = nscache;
    m_srm = srm;
    m_users = users;
    m_alook = alook;
    m_post = post;
    m_cats = cats;
    m_id = ((Integer)(data.get(CommunityManagerOps.KEY_CID))).intValue();
    m_member_gid = ((Integer)(data.get(CommunityManagerOps.KEY_MEMBER_GID))).intValue();
    m_host_gid = ((Integer)(data.get(CommunityManagerOps.KEY_HOST_GID))).intValue();
    m_host_uid = ((Integer)(data.get(CommunityManagerOps.KEY_HOST_UID))).intValue();
    m_aclid = ((Integer)(data.get(CommunityManagerOps.KEY_ACLID))).intValue();
    m_catid = ((Integer)(data.get(CommunityManagerOps.KEY_CATID))).intValue();
    m_visibility = (CommunityVisibility)(data.get(CommunityManagerOps.KEY_VISIBILITY));
    m_name = (String)(data.get(CommunityManagerOps.KEY_NAME));
    m_alias = (String)(data.get(CommunityManagerOps.KEY_ALIAS));
    m_created = (java.util.Date)(data.get(CommunityManagerOps.KEY_CREATE));
    m_lastaccessed = (java.util.Date)(data.get(CommunityManagerOps.KEY_ACCESS));
    m_lastupdate = (java.util.Date)(data.get(CommunityManagerOps.KEY_UPDATE));
    m_properties = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);

  } // end constructor

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

  /**
   * Performs a permission check on the community's ACL, as well as the global ACL.  Returns without
   * incident if the permission check succeeds; throws a
   * {@link com.silverwrist.dynamo.except.DynamoSecurityException DynamoSecurityException} if it fails.
   *
   * @param caller The user makingt he call, to test the permission.
   * @param perm_namespace The namespace of the permission to be tested.
   * @param perm_name The name of the permission to be tested.
   * @param fail_message The message to be thrown as a <CODE>DynamoSecurityException</CODE> if the test fails.
   *                     Interpreted as a message identifier within the <CODE>CommunityMessages</CODE> resource
   *                     bundle, with one parameter: the name of the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was a database failure testing the permission.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the permission test failed.
   */
  private final void testPermission(DynamoUser caller, String perm_namespace, String perm_name,
				    String fail_message) throws DatabaseException, DynamoSecurityException
  {
    try
    { // perform security tests...
      if (caller.equals(m_srm.getAdminUser()))
	return;  // Administrator can do anything
      if (m_srm.getAcl(m_aclid).testPermission(caller,perm_namespace,perm_name))
	return;  // we have the right permission in the community ACL
      if (m_srm.getGlobalAcl().testPermission(caller,perm_namespace,perm_name))
	return;  // or we can be authorized by the global ACL

    } // end try
    catch (AclNotFoundException e)
    { // just fall through and throw the exception
    } // end catch

    // throw the DynamoSecurityException
    DynamoSecurityException d = new DynamoSecurityException(CommunityImpl.class,"CommunityMessages",fail_message);
    d.setParameter(0,m_name);
    throw d;

  } // end testPermission

  /*--------------------------------------------------------------------------------
   * Overrides from class Object
   *--------------------------------------------------------------------------------
   */

  /**
   * Indicates whether some other object is "equal to" this one.
   *
   * @param o The reference object with which to compare.
   * @return <CODE>true</CODE> if this object is the same as the <CODE>o</CODE> argument; <CODE>false</CODE> otherwise.
   */
  public boolean equals(Object o)
  {
    if (o==null)
      return false;
    if (o instanceof VeniceCommunity)
      return (((VeniceCommunity)o).getCID()==m_id);
    return false;

  } // end equals

  /**
   * Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those
   * provided by {@link java.util.Hashtable Hashtable}.
   *
   * @return A hash code value for this object.
   */
  public int hashCode()
  {
    return m_id;

  } // end hashCode

  /**
   * Returns a string representation of the object. In general, the <CODE>toString</CODE> method returns a string
   * that "textually represents" this object. 
   *
   * @return A string representation of the object.
   */
  public String toString()
  {
    return "Community \"" + m_name + "\"";

  } // end toString

  /*--------------------------------------------------------------------------------
   * Implementations from interface NamedObject
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns the name of this community.
   *
   * @return The name of this community.
   */
  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ObjectProvider
   *--------------------------------------------------------------------------------
   */

  /**
   * Retrieves an object from the community's properties.
   *
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be retrieved.
   * @return The object reference specified.
   */
  public Object getObject(String namespace, String name)
  {
    if (logger.isDebugEnabled())
      logger.debug("CommunityImpl.getObject: namespace = " + namespace + ", name = " + name);
    try
    { // convert the namespace name to an ID here
      PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
      Object rc = null;
      synchronized (this)
      { // start by looking in the properties map
	rc = m_properties.get(key);
	if (rc==null)
	{ // no use - need to try the database
	  rc = m_ops.getProperty(m_id,key);
	  if (rc!=null)
          { // found in the database
	    m_properties.put(key,rc);
	    logger.debug("value found in database");

	  } // end if

	} // end if
	else
	  logger.debug("value found in cache");

      } // end synchronized block

      if (rc==null)
      { // the object was not found
	logger.debug("value not found");
	throw new NoSuchObjectException(this.toString(),namespace,name);

      } // end if

      return rc;

    } // end try
    catch (DatabaseException e)
    { // translate into our NoSuchObjectException but retain the DatabaseException
      logger.debug("Database exception while doing find",e);
      throw new NoSuchObjectException(this.toString(),namespace,name,e);

    } // end catch

  } // end getObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface SecureObjectStore
   *--------------------------------------------------------------------------------
   */

  /**
   * Sets an object into the community's properties.
   *
   * @param caller The user performing the operation.
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be set.
   * @param value The object to set into the community's properties.
   * @return The previous object that was set into the community's properties under this namespace and name, or
   *         <CODE>null</CODE> if there was no such object.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error setting the object value.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            set this object value into this community's properties.
   */
  public Object setObject(DynamoUser caller, String namespace, String name, Object value)
      throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,namespace,"set.property","auth.setProperty");
    DateObjectPair rc = null;

    // convert the namespace name to an ID here
    PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
    synchronized (this)
    { // start by setting the database value
      rc = m_ops.setProperty(m_id,key,value);
      
      // and cache it, too
      m_properties.put(key,value);

      // and save off the update date/time
      m_lastaccessed = m_lastupdate = rc.getDate();

    } // end synchronized block

    m_post.postUpdate(new CommunityPropertyUpdateEvent(this,namespace,name));
    return rc.getObject();

  } // end setObject

  /**
   * Removes an object from this community's properties.
   *
   * @param caller The user performing the operation.
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be removed.
   * @return The previous object that was set into the community's properties under this namespace and name, or
   *         <CODE>null</CODE> if there was no such object.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error removing the object value.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            remove this object value from this community's properties.
   */
  public Object removeObject(DynamoUser caller, String namespace, String name)
      throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,namespace,"remove.property","sec.removeGlobalProperty");
    DateObjectPair rc = null;

    // convert the namespace name to an ID here
    PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
    synchronized (this)
    { // start by killing the database value
      rc = m_ops.removeProperty(m_id,key);

      // and remove the cached value, too
      m_properties.remove(key);

      if (rc!=null)  // save off update date.time
	m_lastaccessed = m_lastupdate = rc.getDate();

    } // end synchronized block

    m_post.postUpdate(new CommunityPropertyUpdateEvent(this,namespace,name));
    return (rc==null) ? null : rc.getObject();

  } // end removeObject

  /**
   * Returns a collection of all object namespaces that have been set into this community's properties.
   *
   * @return A {@link java.util.Collection Collection} containing {@link java.lang.String String} objects specifying
   *         all the object namespaces.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the namespace list.
   */
  public Collection getNamespaces() throws DatabaseException
  {
    // call through to the database to get the list of namespace IDs
    int[] ids = m_ops.getPropertyNamespaceIDs(m_id);

    ArrayList rc = new ArrayList(ids.length);
    for (int i=0; i<ids.length; i++)
      rc.add(m_nscache.namespaceIdToName(ids[i]));
    return Collections.unmodifiableList(rc);

  } // end getNamespaces

  /**
   * Returns a collection of all object names that have been set into this community's properties under
   * a given namespace.
   *
   * @param namespace The namespace to look for names under.
   * @return A {@link java.util.Collection Collection} containing {@link java.lang.String String} objects
   *         specifying all the object names for this namespace.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the object name list.
   */
  public Collection getNamesForNamespace(String namespace) throws DatabaseException
  {
    // call through to the database to get the data for this namespace
    int nsid = m_nscache.namespaceNameToId(namespace);
    Map data = m_ops.getAllProperties(m_id,nsid);

    // we both create the return value and cache the data values
    ArrayList rc = new ArrayList(data.size());
    synchronized (this)
    { // do the transfer...
      Iterator it = data.entrySet().iterator();
      while (it.hasNext())
      { // copy one entry at a time
	Map.Entry ntry = (Map.Entry)(it.next());
	rc.add(ntry.getKey().toString());
	m_properties.put(new PropertyKey(nsid,ntry.getKey().toString()),ntry.getValue());

      } // end while

    } // end synchronized block

    return Collections.unmodifiableList(rc);

  } // end getNamesForNamespace

  /*--------------------------------------------------------------------------------
   * Implementations from interface VeniceCommunity
   *--------------------------------------------------------------------------------
   */

  /**
   * Returns the community ID of this community.  The community ID is set when the community is created and may
   * not be altered.
   *
   * @return The community ID of this community.
   */
  public int getCID()
  {
    return m_id;

  } // end getCID

  /**
   * Returns the GID of the <EM>member group</EM> of this community.  The member group specifies all
   * members of the community.
   *
   * @return The GID of the member group for this community.
   */
  public int getMemberGID()
  {
    return m_member_gid;

  } // end getMemberGID

  /**
   * Returns a reference to the <EM>member group</EM> of this community.  The member group specifies all
   * members of the community.
   *
   * @return A reference to the member group of this community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the member group.
   */
  public DynamoGroup getMemberGroup() throws DatabaseException
  {
    return m_users.getGroup(m_member_gid);

  } // end getMemberGroup

  /**
   * Returns the GID of the <EM>host group</EM> of this community.  The host group specifies all
   * hosts of the community, who generally have power to change most attributes of a community.
   *
   * @return The GID of the host group for this community.
   */
  public int getHostGID()
  {
    return m_host_gid;

  } // end getHostGID

  /**
   * Returns a reference to the <EM>host group</EM> of this community.  The host group specifies all
   * hosts of the community, who generally have power to change most attributes of a community.
   *
   * @return A reference to the host group of this community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the host group.
   */
  public DynamoGroup getHostGroup() throws DatabaseException
  {
    return m_users.getGroup(m_host_gid);

  } // end getHostGroup

  /**
   * Returns the UID of the <EM>host user</EM> of this community.  The host user is the primary host of the
   * community, who is the only one who has power to add or remove additional hosts.
   *
   * @return The UID of the host user of the community.
   */
  public int getHostUID()
  {
    return m_host_uid;

  } // end getHostUID

  /**
   * Returns a reference to the <EM>host user</EM> of this community.  The host user is the primary host of the
   * community, who is the only one who has power to add or remove additional hosts.
   *
   * @return A reference to the host user of the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the host user.
   */
  public DynamoUser getHostUser() throws DatabaseException
  {
    return m_users.getUser(m_host_uid);

  } // end getHostUser

  /**
   * Returns a reference to the community's access control list.
   *
   * @return A reference to the community's access control list.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the ACL.
   * @exception java.security.acl.AclNotFoundException If the community ACL could not be found.
   */
  public DynamoAcl getAcl() throws DatabaseException, AclNotFoundException
  {
    return m_srm.getAcl(m_aclid);

  } // end getAcl

  /**
   * Returns the category ID of the community, specifying its location in the community directory.
   *
   * @return The category ID of the community.
   */
  public int getCategoryID()
  {
    return m_catid;

  } // end getCategoryID

  /**
   * Returns the category of the community, specifying its location in the community directory.
   *
   * @return The category of the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the category object.
   */
  public VeniceCategory getCategory() throws DatabaseException
  {
    return m_cats.getCategory(m_catid);

  } // end getCategory

  /**
   * Changes the category ID of the community, specifying its location in the community directory.
   *
   * @param caller The user attempting to change the category ID.
   * @param catid The new category ID for the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error resetting the category.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            change the category ID.
   */
  public synchronized void setCategoryID(DynamoUser caller, int catid)
      throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"set.category","auth.setCategory");
    java.util.Date update = m_ops.setCategoryID(m_id,catid);
    m_catid = catid;
    m_lastaccessed = m_lastupdate = update;
    m_post.postUpdate(new CommunityBaseUpdateEvent(this,"category"));

  } // end setCategoryID

  /**
   * Changes the category of the community, specifying its location in the community directory.
   *
   * @param caller The user attempting to change the category.
   * @param catid The new category for the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error resetting the category.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            change the category.
   */
  public void setCategory(DynamoUser caller, VeniceCategory cat) throws DatabaseException, DynamoSecurityException
  {
    this.setCategoryID(caller,cat.getCategoryID());

  } // end setCategory

  /**
   * Returns the current {@link com.silverwrist.venice.CommunityVisibility visibility} of the community.
   *
   * @return The current visibility of the community.
   */
  public CommunityVisibility getVisibility()
  {
    return m_visibility;

  } // end getVisibility

  /**
   * Sets the current {@link com.silverwrist.venice.CommunityVisibility visibility} of the community.
   *
   * @param caller The user attempting to change the community's visibility.
   * @param vis The new visibility for the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error resetting the visibility.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            change the community visibility.
   */
  public synchronized void setVisibility(DynamoUser caller, CommunityVisibility vis)
      throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"set.visibility","auth.setVisibility");
    java.util.Date update = m_ops.setVisibility(m_id,vis);
    m_visibility = vis;
    m_lastaccessed = m_lastupdate = update;
    m_post.postUpdate(new CommunityBaseUpdateEvent(this,"visibility"));

  } // end setVisibility

  /**
   * Sets the name of the community.
   *
   * @param caller The user attempting to change the community's name.
   * @param name The new name for the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error resetting the name.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            change the community name.
   */
  public synchronized void setName(DynamoUser caller, String name) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"set.name","auth.setName");
    java.util.Date update = m_ops.setName(m_id,name);
    m_name = name;
    m_lastaccessed = m_lastupdate = update;
    m_post.postUpdate(new CommunityBaseUpdateEvent(this,"name"));

  } // end setName

  /**
   * Returns the <EM>alias</EM> of the community.  The alias is a unique identifier for the community that must
   * conform to the rules for Dynamo identifiers.
   *
   * @return The alias for the community.
   */
  public String getAlias()
  {
    return m_alias;

  } // end getAlias

  /**
   * Sets the <EM>alias</EM> of the community.  The alias is a unique identifier for the community that must
   * conform to the rules for Dynamo identifiers.
   *
   * @param caller The user attempting to change the community's alias.
   * @param alias The new alias for the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error resetting the alias.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            change the community alias.
   */
  public synchronized void setAlias(DynamoUser caller, String alias) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PROFILE_NAMESPACE,"set.alias","auth.setAlias");
    java.util.Date update = m_ops.setAlias(m_id,alias);
    m_alias = alias;
    m_lastaccessed = m_lastupdate = update;
    m_post.postUpdate(new CommunityBaseUpdateEvent(this,"alias"));

  } // end setAlias

  /**
   * Returns the date and time at which this community was created.
   *
   * @return The creation date and time for this community.
   */
  public java.util.Date getCreationDate()
  {
    return m_created;

  } // end getCreationDate

  /**
   * Returns the date and time at which this community was last accessed.
   *
   * @return The last access date and time for this community.
   */
  public java.util.Date getLastAccessDate()
  {
    return m_lastaccessed;

  } // end getLastAccessDate

  /**
   * Sets the date and time at which this community was last accessed.
   *
   * @param caller The user attempting to change the community's access date/time.
   * @param date The new access date/time for the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error resetting the access date/time.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            change the community access date/time.
   */
  public synchronized void setLastAccessDate(DynamoUser caller, java.util.Date date) throws DatabaseException
  {
    m_ops.setLastAccessDate(m_id,date);
    m_lastaccessed = date;

  } // end setLastAccessedDate

  /**
   * Returns the date and time at which this community was last updated.
   *
   * @return The last update date and time for this community.
   */
  public java.util.Date getLastUpdateDate()
  {
    return m_lastupdate;

  } // end getLastUpdateDate

  /**
   * Sets the date and time at which this community was last updated.
   *
   * @param caller The user attempting to change the community's update date/time.
   * @param date The new update date/time for the community.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error resetting the update date/time.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            change the community update date/time.
   */
  public synchronized void setLastUpdateDate(DynamoUser caller, java.util.Date date) throws DatabaseException
  {
    m_ops.setLastUpdateDate(m_id,date);
    m_lastaccessed = m_lastupdate = date;

  } // end setLastUpdateDate

  /**
   * Grants permission to join the community to a specific user or group.  This permission may be contingent upon
   * the user authenticating with the community in some fashion, and it may also be single-use, meaning the permission
   * is automatically revoked whenever a user uses it to join the community.
   *
   * @param caller The person attempting to grant access to the community.
   * @param subject The {@link com.silverwrist.dynamo.iface.DynamoUser user} or
   *                {@link com.silverwrist.dynamo.iface.DynamoGroup group} that we want to grant access to.
   * @param auth_namespace The namespace of the authenticator to use to grant access.  If both this parameter and
   *                       <EM>auth_name</EM> are <CODE>null</CODE>, no authentication is required.
   * @param auth_name The name of the authenticator to use to grant access.  If both this parameter and
   *                  <EM>auth_namespace</EM> are <CODE>null</CODE>, no authentication is required.
   * @param source_info The source information for the authenticator.  If <EM>auth_namespace</EM> and
   *                    <EM>auth_name</EM> are <CODE>null</CODE>, this parameter is ignored.
   * @param auth_info The authentication information (such as a password) for the authenticator.  If
   *                  <EM>auth_namespace</EM> and <EM>auth_name</EM> are <CODE>null</CODE>, this parameter is ignored.
   * @param single_use If this parameter is <CODE>true</CODE>, the permission will automatically be revoked after the
   *                   first time it is successfully used by a user to join the community.
   * @exception com.silverwrist.dynamo.except.AuthenticationException If the named authenticator is not a valid one.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error registering the access grant.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to grant
   *            access to this community.
   */
  public void grantAccess(DynamoUser caller, Principal subject, String auth_namespace, String auth_name,
			  String source_info, String auth_info, boolean single_use)
      throws AuthenticationException, DatabaseException, DynamoSecurityException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PERMS_NAMESPACE,"grant.revoke.access","auth.grantAccess");

    // Translate the subject into an ID and group flag.
    int ugid = -1;
    boolean is_group = false;
    if (subject instanceof DynamoUser)
      ugid = ((DynamoUser)subject).getUID();
    else if (subject instanceof DynamoGroup)
    { // it's a group
      ugid = ((DynamoGroup)subject).getGID();
      is_group = true;

    } // end else if
    else
      throw new IllegalArgumentException("invalid subject");

    // Make sure the authenticator is valid.
    int auth_nsid = 0;
    Authenticator auth = null;
    if ((auth_namespace!=null) || (auth_name!=null))
    { // look up the authenticator to make sure it's registered
      auth = m_alook.findAuthenticator(auth_namespace,auth_name);
      if (auth==null)
	throw new IllegalArgumentException("invalid authenticator");
      auth_nsid = m_nscache.namespaceNameToId(auth_namespace);

    } // end if
    else  // force-map source info and auth info to null
      source_info = auth_info = null;

    // Preprocess the authentication information through the authenticator.
    String real_auth_info = auth_info;
    if ((auth_info!=null) && (auth!=null))
      real_auth_info = auth.processInputData(auth_info);

    synchronized (this)
    { // update the access table and touch the community
      java.util.Date update = m_ops.grantAccess(m_id,ugid,is_group,single_use,auth_nsid,auth_name,source_info,
						real_auth_info);
      m_lastaccessed = m_lastupdate = update;

    } // end synchronized block

  } // end grantAccess

  /**
   * Revokes permission to join the community from a specific user or group.  This does not affect any user who
   * has already joined the community.
   *
   * @param caller The person attempting to revoke access to the community.
   * @param subject The {@link com.silverwrist.dynamo.iface.DynamoUser user} or
   *                {@link com.silverwrist.dynamo.iface.DynamoGroup group} that we want to revoke access from.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error registering the access revoke.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to revoke
   *            access to this community.
   */
  public void revokeAccess(DynamoUser caller, Principal subject) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PERMS_NAMESPACE,"grant.revoke.access","auth.revokeAccess");

    // Translate the subject into an ID and group flag.
    int ugid = -1;
    boolean is_group = false;
    if (subject instanceof DynamoUser)
      ugid = ((DynamoUser)subject).getUID();
    else if (subject instanceof DynamoGroup)
    { // it's a group
      ugid = ((DynamoGroup)subject).getGID();
      is_group = true;

    } // end else if
    else
      throw new IllegalArgumentException("invalid subject");

    synchronized (this)
    { // update the access table and touch the community
      java.util.Date update = m_ops.revokeAccess(m_id,ugid,is_group);
      if (update!=null)
	m_lastaccessed = m_lastupdate = update;

    } // end synchronized block

  } // end revokeAccess

  /**
   * Tests to see what requirements must be fulfilled by a user to join this community.  Returns values as follows:
   * <UL>
   *   <LI>If the user is not permitted to join the community under any circumstances, this method returns
   *     <CODE>null</CODE>.</LI>
   *   <LI>If the user is already a member of the community, this method returns <CODE>Boolean.FALSE</CODE>.</LI>
   *   <LI>If the user may join the community without any authentication, this method returns
   *     <CODE>Boolean.TRUE</CODE>.</LI>
   *   <LI>Otherwise, the method returns a {@link java.util.Set Set} of
   *     {@link com.silverwrist.dynamo.util.QualifiedNameKey QualifiedNameKey} objects, each of which specifies the
   *     namespace and name of an authenticator which can be used to authenticate the user in order to join this
   *     community.</LI>
   * </UL>
   *
   * @param joiner The user to test atainst the current community.
   * @return See above.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the authenticator
   *            information for this user.
   */
  public Object getJoinRequirement(DynamoUser joiner) throws DatabaseException
  {
    if (logger.isDebugEnabled())
      logger.debug("CommunityImpl.getJoinRequirement: " + joiner.getName() + " wants to join " + m_name);

    // If the user is already a member, return the value that dictates that we are already a member.
    if (m_users.getGroup(m_member_gid).isMember(joiner))
      return Boolean.FALSE;

    // The Admin user is Superman and can do anything.
    if (joiner.equals(m_srm.getAdminUser()))
      return Boolean.TRUE;

    // Anyone with the global "join any" permission can join without authorization.
    if (m_srm.getGlobalAcl().testPermission(joiner,VeniceNamespaces.COMMUNITY_PERMS_NAMESPACE,"join.any"))
      return Boolean.TRUE;

    // Get the database's verdict.  The return value from the low-level operation will either be a null,
    // a Boolean object, or a Set of PropertyKey objects.
    Object rc = m_ops.getJoinRequirement(m_id,joiner.getUID());
    if (rc==null)
      return null;
    else if (rc instanceof Boolean)
      return rc;
    assert (rc instanceof Set);

    // The output must be translated to a Set of QualifiedNameKeys by resolving the namespace IDs.
    Set rcin = (Set)rc;
    HashSet rcout = new HashSet();
    Iterator it = rcin.iterator();
    while (it.hasNext())
    { // convert each PropertyKey to a QualifiedNameKey
      PropertyKey k = (PropertyKey)(it.next());
      rcout.add(new QualifiedNameKey(m_nscache.namespaceIdToName(k.getNamespaceID()),k.getName()));

    } // end while

    return Collections.unmodifiableSet(rcout);

  } // end getJoinRequirement

  public synchronized boolean join(DynamoUser joiner, String auth_namespace, String auth_name, String source_info,
				   String auth_info) throws DatabaseException, DynamoSecurityException
  {
    if (logger.isDebugEnabled())
      logger.debug("CommunityImpl.join: " + joiner.getName() + " joining " + m_name);

    // If the user is already a member, return the value that dictates that we are already a member.
    DynamoGroup mbr_group = m_users.getGroup(m_member_gid);
    if (mbr_group.isMember(joiner))
      return false;

    boolean ok_to_join = false;
    JoinCleanupTask task = null;
    if (joiner.equals(m_srm.getAdminUser()))
      ok_to_join = true;
    else if (m_srm.getGlobalAcl().testPermission(joiner,VeniceNamespaces.COMMUNITY_PERMS_NAMESPACE,"join.any"))
      ok_to_join = true;
    else
    { // Look to see if we can find the right entry to let the user in with.
      boolean need_auth = false;
      Object which = m_ops.getJoinEntryNull(m_id,joiner.getUID());
      if ((which==null) && (auth_namespace!=null) && (auth_name!=null))
      { // OK, we may need to authenticate
	need_auth = true;
	which = m_ops.getJoinEntry(m_id,joiner.getUID(),m_nscache.namespaceNameToId(auth_namespace),auth_name,
				   source_info);

      } // end which

      if (which!=null)
      { // We can get in...see if we need to check authentication.
	if (need_auth)
	{ // Pull in the authentication data.
          String stored_auth_data;
	  if (which instanceof Boolean)
	    stored_auth_data = m_ops.getAuthData(m_id,joiner.getUID(),false);
	  else
	    stored_auth_data = m_ops.getAuthData(m_id,((Integer)which).intValue(),true);

	  // Get the authenticator.
	  Authenticator auth = m_alook.findAuthenticator(auth_namespace,auth_name);
	  if (auth==null)
	    throw new IllegalArgumentException("invalid authenticator");

	  // Checkitout checkitout checkitout checkitout checkitout checkitout checkitout....
	  try
	  { // attempt to authenticate
	    ok_to_join = auth.authenticate(stored_auth_data,auth_info);

	  } // end try
	  catch (AuthenticationException e)
	  { // something went wrong...
	    DynamoSecurityException de = new DynamoSecurityException(CommunityImpl.class,"CommunityMessages",
								     "join.authFail",e);
	    de.setParameter(0,m_name);
	    throw de;

	  } // end catch

	  if (!ok_to_join)
	  { // authentication failed - throw an error
	    DynamoSecurityException de = new DynamoSecurityException(CommunityImpl.class,"CommunityMessages",
								     "join.authFail");
	    de.setParameter(0,m_name);
	    throw de;

	  } // end if

	} // end if
	else  // we're automatically OK to join
	  ok_to_join = true;

	// Create the cleanup task.
	if (which instanceof Boolean)
	  task = new JoinCleanupTask(joiner.getUID(),false);
	else
	  task = new JoinCleanupTask(((Integer)which).intValue(),true);

      } // end if
      // else we can't join the group - fall through and eventually throw a DynamoSecurityException

    } // end else

    if (ok_to_join)
    { // we're OK to join this group!
      mbr_group.addMember(m_users.getUser(m_host_uid),joiner);
      if (task!=null)
	task.run();
      return true;

    } // end if

    // We cannot join - throw an exception.
    DynamoSecurityException de = new DynamoSecurityException(CommunityImpl.class,"CommunityMessages",
							     "join.disallowed");
    de.setParameter(0,m_name);
    throw de;

  } // end join

  public boolean join(DynamoUser joiner) throws DatabaseException, DynamoSecurityException
  {
    return this.join(joiner,null,null,null,null);

  } // end join

  public void unjoin(DynamoUser unjoiner) throws DatabaseException, DynamoSecurityException
  {
    DynamoGroup mbr_group = m_users.getGroup(m_member_gid);
    if (!(mbr_group.isMember(unjoiner)))
      return;  // if you're not a member, unjoining is a no-op
    testPermission(unjoiner,VeniceNamespaces.COMMUNITY_PERMS_NAMESPACE,"unjoin","auth.unjoin");
    mbr_group.removeMember(m_users.getUser(m_host_uid),unjoiner);

  } // end unjoin

  public boolean isAdministrator(DynamoUser user)
  {
    try
    { // must be either the host UID or a member of the admin group
      if (user.getUID()==m_host_uid)
	return true;
      return m_users.getGroup(m_host_gid).isMember(user);

    } // end try
    catch (DatabaseException e)
    { // gotta catch it, even if we can't do anything with it
      logger.warn("CommunityImpl.isAdministrator caught DatabaseException",e);
      return false;

    } // end catch

  } // end isAdministrator

  public List getServices() throws DatabaseException
  {
    if (m_service_list==null)
    { // need to fake up the service list
      int[] svcids = m_ops.getServiceIDs(m_id);
      if (svcids.length>0)
      { // convert this to a list of ServiceDescriptors
	ArrayList rc = new ArrayList(svcids.length);
	for (int i=0; i<svcids.length; i++)
	  rc.add(m_base.getServiceForID(svcids[i]));
	Collections.sort(rc,new Comparator()
	  {
	    public int compare(Object o1, Object o2)
	    {
	      String s1 = ((CommunityServiceDescriptor)o1).getDescription();
	      String s2 = ((CommunityServiceDescriptor)o2).getDescription();
	      return s1.compareTo(s2);

	    } // end compare

	    public boolean equals(Object obj)
	    {
	      return false;

	    } // end equals

	  }); // end Comparator object
	m_service_list = Collections.unmodifiableList(rc);

      } // end if
      else // just use the empty list as a return value
	m_service_list = Collections.EMPTY_LIST;

    } // end if

    return m_service_list;

  } // end getServices

  public boolean isUsingService(String namespace, String name) throws DatabaseException
  {
    return m_ops.isUsingService(m_id,m_nscache.namespaceNameToId(namespace),name);

  } // end isUsingService

  public synchronized void addService(DynamoUser caller, Request req, String namespace, String name)
      throws DatabaseException, DynamoSecurityException, CommunityServiceException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PERMS_NAMESPACE,"manage.services","auth.addService");
    int nsid = m_nscache.namespaceNameToId(namespace);
    if (m_ops.isUsingService(m_id,nsid,name))
    { // we're already using this service - throw an exception
      CommunityServiceException cse = new CommunityServiceException(CommunityImpl.class,"CommunityMessages",
								    "svc.already.using");
      cse.setParameter(0,namespace);
      cse.setParameter(1,name);
      throw cse;

    } // end if

    CommunityServiceDescriptor descr = m_base.getServiceForName(namespace,name);
    if (descr==null)
    { // this service is not defined - throw an exception
      CommunityServiceException cse = new CommunityServiceException(CommunityImpl.class,"CommunityMessages",
								    "svc.notFound");
      cse.setParameter(0,namespace);
      cse.setParameter(1,name);
      throw cse;

    } // end if

    descr.getController().addServiceToCommunity(req,this);
    m_ops.addService(m_id,nsid,name);
    m_service_list = null;

  } // end addService

  public void removeService(DynamoUser caller, Request req, String namespace, String name)
      throws DatabaseException, DynamoSecurityException, CommunityServiceException
  {
    testPermission(caller,VeniceNamespaces.COMMUNITY_PERMS_NAMESPACE,"manage.services","auth.removeService");
    int nsid = m_nscache.namespaceNameToId(namespace);
    synchronized (this)
    { // see if we're using this service
      if (!(m_ops.isUsingService(m_id,nsid,name)))
      { // we're not using this service - throw an exception
	CommunityServiceException cse = new CommunityServiceException(CommunityImpl.class,"CommunityMessages",
								      "svc.not.using");
	cse.setParameter(0,namespace);
	cse.setParameter(1,name);
	throw cse;

      } // end if

      CommunityServiceDescriptor descr = m_base.getServiceForName(namespace,name);
      if (descr==null)
      { // this service is not defined - throw an exception
	CommunityServiceException cse = new CommunityServiceException(CommunityImpl.class,"CommunityMessages",
								      "svc.notFound");
	cse.setParameter(0,namespace);
	cse.setParameter(1,name);
	throw cse;

      } // end if

      descr.getController().removeServiceFromCommunity(req,this);
      m_ops.removeService(m_id,nsid,name);
      m_service_list = null;

    } // end synchronized block

  } // end removeService

} // end class CommunityImpl

