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

import java.security.Principal;
import java.security.acl.AclNotFoundException;
import java.util.*;
import org.apache.commons.collections.*;
import org.w3c.dom.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.db.NamespaceCache;
import com.silverwrist.dynamo.db.UserProxyManagement;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

public class SRMObject implements NamedObject, ComponentInitialize, ComponentShutdown, SecurityReferenceMonitor
{
  /*--------------------------------------------------------------------------------
   * Internal class implementing ACE caching
   *--------------------------------------------------------------------------------
   */

  private class MainAceCache implements AceCache, RegisterNewAce
  {
    /*====================================================================
     * Attributes
     *====================================================================
     */

    private ReferenceMap m_ace_cache;

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

    MainAceCache()
    {
      m_ace_cache = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);

    } // end constructor

    /*====================================================================
     * Implementations from class AceCache
     *====================================================================
     */

    public synchronized DynamoAce getAce(int aceid) throws DatabaseException
    {
      Integer key = new Integer(aceid);
      DynamoAce rc = (DynamoAce)(m_ace_cache.get(key));
      if (rc==null)
      { // get the ACE data from the database, then create a new ACE object
	Map ace_data = m_ops.getAceData(aceid);
	AceObject aobj = new AceObject(m_ops.getAceOperations(),m_ns_cache,m_proxy,this,ace_data);
	aobj.loadPermissions();
	rc = aobj;
	m_ace_cache.put(key,rc);

      } // end if

      return rc;

    } // end getAce

    /*====================================================================
     * Implementations from class RegisterNewAce
     *====================================================================
     */

    public synchronized void registerNewAce(DynamoAce ace)
    {
      m_ace_cache.put(new Integer(ace.getAceID()),ace);

    } // end registerNewAce

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

    synchronized DynamoAce createAce() throws DatabaseException
    {
      int new_id = m_ops.createAce();
      AceObject aobj = new AceObject(m_ops.getAceOperations(),m_ns_cache,m_proxy,this,new_id);
      m_ace_cache.put(new Integer(new_id),aobj);
      return aobj;

    } // end createAce

  } // end class MainAceCache

  /*--------------------------------------------------------------------------------
   * Internal class allowing us to serialize ACL values
   *--------------------------------------------------------------------------------
   */

  private class AclSerialize implements PropertySerializer
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

    AclSerialize()
    { // do nothing
    } // end constructor

    /*====================================================================
     * Implementations from interface PropertySerializer
     *====================================================================
     */

    public String serializeProperty(Object value)
    {
      if (value instanceof DynamoAcl)
	return "Acl:" + String.valueOf(((DynamoAcl)value).getAclID());
      return null;

    } // end serializeProperty

    public Object deserializeProperty(String value)
    {
      if (value.startsWith("Acl:"))
      { // try to get the ACL corresponding to this ID
	try
	{ // get the ID, then load the ACL
	  return getAcl(Integer.parseInt(value.substring(4)));

	} // end try
	catch (NumberFormatException e)
	{ // ACL ID could not be parsed
	  return null;

	} // end catch
	catch (DatabaseException e)
	{ // something went doo-lally in the database
	  return null;

	} // end catch
	catch (AclNotFoundException e)
	{ // the ACL was not found
	  return null;

	} // end catch

      } // end if

      return null;

    } // end deserializeProperty

  } // end class AclSerialize

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

  private String m_name;                                    // object name
  private NamespaceCache m_ns_cache;                        // namespace cache object
  private UserProxyManagement m_proxy;                      // proxy management for users
  private SRMOperations m_ops;                              // operations object
  private ReferenceMap m_cache;                             // ACL cache
  private Object m_cache_sync = new Object();               // synchronization for above
  private MainAceCache m_ace;                               // ACE cache
  private int m_admin_user;                                 // UID of admin user
  private int m_admin_group;                                // GID of admin group
  private DynamoAcl m_global_acl;                           // global ACL
  private int m_all_gid;                                    // GID of "all users" group
  private int m_verified_gid;                               // GID of "verified users" group
  private ComponentShutdown m_shut_psz;                     // shutdown our property serializer

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

  public SRMObject()
  {
    m_cache = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_ace = new MainAceCache();

  } // end constructor

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

  public String getName()
  {
    return m_name;

  } // end getName

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentInitialize
   *--------------------------------------------------------------------------------
   */

  public void initialize(Element config_root, ServiceProvider services) throws ConfigException
  {
    String conn_name = null;
    String nscache_name = null;
    String proxy_name = null;
    XMLLoader loader = XMLLoader.get();
    try
    { // verify the right node name
      loader.verifyNodeName(config_root,"object");

      // get the object's name
      m_name = loader.getAttribute(config_root,"name");

      // get the database configuration connection
      Element elt = loader.getSubElement(config_root,"database");
      conn_name = loader.getAttribute(elt,"connection");
      nscache_name = loader.getAttribute(elt,"namespaces");

      // get the user manager (for proxy generation)
      elt = loader.getSubElement(config_root,"user-manager");
      proxy_name = loader.getAttribute(elt,"cpoint");

    } // end try
    catch (XMLLoadException e)
    { // error loading XML config data
      throw new ConfigException(e);

    } // end catch

    // Get the database connection pool and the namespace cache.
    DBConnectionPool pool = GetObjectUtils.getDatabaseConnection(services,conn_name);
    m_ns_cache =
        (NamespaceCache)(GetObjectUtils.getDynamoComponent(services,NamespaceCache.class,nscache_name));

    // Get the user proxy management object.
    ConnectionFrontEnd frontend = (ConnectionFrontEnd)(services.queryService(ConnectionFrontEnd.class));
    m_proxy = (UserProxyManagement)(frontend.getInterface(proxy_name,UserProxyManagement.class));

    // Get the operations object.
    m_ops = SRMOperations.get(pool);

    int gacl_id = -1;
    try
    { // get the global security info out of the database
      int[] global_data = m_ops.getGlobalSecurityData();
      m_admin_user = global_data[SRMOperations.NDX_ADMIN_UID];
      m_admin_group = global_data[SRMOperations.NDX_ADMIN_GID];
      m_global_acl = getAcl(gacl_id = global_data[SRMOperations.NDX_GLOBAL_ACLID]);
      m_all_gid = global_data[SRMOperations.NDX_ALL_GID];
      m_verified_gid = global_data[SRMOperations.NDX_VERIFIED_GID];

    } // end try
    catch (DatabaseException de)
    { // turn DatabaseException into ConfigException
      ConfigException ce = new ConfigException(SRMObject.class,"SecurityMessages","globaldata.load",de);
      ce.setParameter(0,de.getMessage());
      throw ce;

    } // end catch
    catch (AclNotFoundException ae)
    { // this is not good either - throw an exception
      ConfigException ce = new ConfigException(SRMObject.class,"SecurityMessages","acl.notFound",ae);
      ce.setParameter(0,String.valueOf(gacl_id));
      throw ce;

    } // end catch

    PropertySerializerRegistration pszreg =
        (PropertySerializerRegistration)(services.queryService(PropertySerializerRegistration.class));
    m_shut_psz = pszreg.registerPropertySerializer(new AclSerialize());

  } // end initialize

  /*--------------------------------------------------------------------------------
   * Implementations from interface ComponentShutdown
   *--------------------------------------------------------------------------------
   */

  public void shutdown()
  {
    m_shut_psz.shutdown();
    m_shut_psz = null;
    m_global_acl = null;
    m_ops.dispose();
    m_ops = null;
    m_proxy = null;
    m_ns_cache = null;

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface SecurityReferenceMonitor
   *--------------------------------------------------------------------------------
   */

  public DynamoAcl getAcl(int aclid) throws DatabaseException, AclNotFoundException
  {
    Integer key = new Integer(aclid);
    DynamoAcl rc = null;
    synchronized (m_cache_sync)
    { // look in the ACL cache first
      rc = (DynamoAcl)(m_cache.get(key));
      if (rc==null)
      { // call down to the database to get the basic information
	Map acl_data = m_ops.getAclData(aclid);
	rc = new AclObject(m_ops.getAclOperations(),m_ns_cache,m_proxy,m_ace,acl_data);
	m_cache.put(key,rc);  // cache the result
	acl_data.clear();
	
      } // end if

    } // end synchronized block

    return rc;

  } // end getAcl

  public DynamoAcl createAcl(String name, Principal owner) throws DatabaseException
  {
    int owner_id = 0;
    boolean owner_group = false;
    if (owner instanceof DynamoUser)
      owner_id = ((DynamoUser)owner).getUID();
    else if (owner instanceof DynamoGroup)
    { // get the group ID
      owner_id = ((DynamoGroup)owner).getGID();
      owner_group = true;

    } // end else if
    else
      throw new IllegalArgumentException("owner must be DynamoUser or DynamoGroup");

    // create the ACL in the database, then create a new ACL object
    Map acl_data = m_ops.createAcl(name,owner_id,owner_group);
    DynamoAcl rc = new AclObject(m_ops.getAclOperations(),m_ns_cache,m_proxy,m_ace,acl_data);
    synchronized (m_cache_sync)
    { // add the new ACL to the cache - note that an Integer representing the ACL id is
      // already in our returned map!
      m_cache.put(acl_data.get(SRMOperations.HMKEY_ACLID),rc);

    } // end synchronized block

    acl_data.clear();
    return rc;

  } // end createAcl

  public DynamoAce createAce() throws DatabaseException
  {
    return m_ace.createAce();

  } // end createAce

  public DynamoPermission createPermission(String namespace, String name) throws DatabaseException
  {
    return new PermObject(namespace,name,m_ns_cache);

  } // end createPermission

  public boolean testPermission(int aclid, DynamoUser user, String perm_namespace, String perm_name)
      throws DatabaseException
  {
    return m_ops.testUserPermission(aclid,user.getUID(),
				    new PropertyKey(m_ns_cache.namespaceNameToId(perm_namespace),perm_name));

  } // end testPermission

  public DynamoUser getAdminUser()
  {
    return m_proxy.getUserProxy(m_admin_user);

  } // end getAdminUser

  public DynamoGroup getAdminGroup()
  {
    return m_proxy.getGroupProxy(m_admin_group);

  } // end getAdminGroup

  public DynamoAcl getGlobalAcl()
  {
    return m_global_acl;

  } // end getGlobalAcl

  public boolean isOwnerOfAcl(int aclid, DynamoUser user) throws DatabaseException
  {
    return m_ops.testAclOwner(aclid,new PrincipalID(user));

  } // end isOwnerOfAcl

  public DynamoGroup getAllUsersGroup()
  {
    return m_proxy.getGroupProxy(m_all_gid);

  } // end getAllUsersGroup

  public DynamoGroup getVerifiedUsersGroup()
  {
    return m_proxy.getGroupProxy(m_verified_gid);

  } // end getVerifiedUsersGroup

} // end class SRMObject
