/*
 * 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.dynamo.unistore;

import java.util.*;
import org.apache.log4j.Logger;
import org.w3c.dom.*;
import com.silverwrist.util.*;
import com.silverwrist.util.xml.*;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.db.NamespaceCache;
import com.silverwrist.dynamo.db.UserManagement;
import com.silverwrist.dynamo.event.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.security.SecurityReferenceMonitor;
import com.silverwrist.dynamo.util.*;

public class UniStoreManager implements NamedObject, ComponentInitialize, ComponentShutdown, UniStore
{
  /*--------------------------------------------------------------------------------
   * Internal class for resolving message references
   *--------------------------------------------------------------------------------
   */

  private class UniStoreResolver implements IndexedObjectResolver
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

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

    /*====================================================================
     * Implementations from interface IndexedObjectResolver
     *====================================================================
     */

    public String getResolverTag(Object obj) throws IndexException
    {
      if (obj instanceof UniStoreMessage)
	return String.valueOf(((UniStoreMessage)obj).getMessageID());
      else if (obj instanceof UniStorePart)
	return String.valueOf(((UniStorePart)obj).getMessageID());
      else
	throw new IndexException(UniStoreManager.class,"UniStoreMessages","resolver.must.be.message");

    } // end getResolverTag

    public Object resolveObject(String tag) throws IndexException
    {
      try
      { // parse tag into longint, get the message
	return UniStoreManager.this.getMessage(Long.parseLong(tag));

      } // end try
      catch (NumberFormatException nfe)
      { // not a valid number
	throw new IndexException(UniStoreManager.class,"UniStoreMessages","invalid.tag");

      } // end catch
      catch (DatabaseException de)
      { // just translate it straight across
	throw new IndexException(de);

      } // end catch

    } // end resolveObject

  } // end class UniStoreResolver

  /*--------------------------------------------------------------------------------
   * Internal class for resolving message part references
   *--------------------------------------------------------------------------------
   */

  private class UniStorePartResolver implements IndexedObjectResolver
  {
    /*====================================================================
     * Constructor
     *====================================================================
     */

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

    /*====================================================================
     * Implementations from interface IndexedObjectResolver
     *====================================================================
     */

    public String getResolverTag(Object obj) throws IndexException
    {
      if (obj instanceof UniStoreTextPart)
      { // return the message ID and the part namespace and name
	QualifiedNameKey qname = ((UniStorePart)obj).getPartIdentity();
	return String.valueOf(((UniStorePart)obj).getMessageID()) + "|" + qname.getNamespace() + "|" + qname.getName();

      } // end if
      else
	throw new IndexException(UniStoreManager.class,"UniStoreMessages","resolver.must.be.textpart");

    } // end getResolverTag

    public Object resolveObject(String tag) throws IndexException
    {
      try
      { // break up the tag and resolve it into a UniStoreTextPart
	String[] bits = StringUtils.split1(tag,'|',3);
	UniStoreMessage msg = UniStoreManager.this.getMessage(Long.parseLong(bits[0]));
	return msg.getTextPart(bits[1],bits[2]);
	
      } // end try
      catch (NumberFormatException nfe)
      { // not a valid number
	throw new IndexException(UniStoreManager.class,"UniStoreMessages","invalid.tag");

      } // end catch
      catch (DatabaseException de)
      { // just translate it straight across
	throw new IndexException(de);

      } // end catch

    } // end resolveObject

  } // end class UniStorePartResolver

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

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

  private static final Long NO_PARENT = new Long(0);
  private static final Integer NO_SEQ = new Integer(0);

  private static int DEFAULT_MSGCACHE_HARD = 100;
  private static int DEFAULT_MSGCACHE_SOFT = 1000;

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

  private String m_name;                       // name of this object
  private NamespaceCache m_ns_cache;           // namespace cache object
  private ManagerOps m_ops;                    // database operations object
  private SecurityReferenceMonitor m_srm;      // security reference monitor
  private UserManagement m_users;              // user management object
  private PostDynamicUpdate m_post;            // dynamic update posting
  private HardSoftCache m_msgcache;            // message cache
  private ComponentShutdown m_res1;
  private ComponentShutdown m_res2;

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

  public UniStoreManager()
  { // do nothing
  } // end constructor

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

  public String getName()
  {
    return m_name;

  } // end getName

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

  /**
   * Initialize the component.
   *
   * @param config_root Pointer to the section of the Dynamo XML configuration file that configures this
   *                    particular component.  This is to be considered "read-only" by the component.
   * @param services An implementation of {@link com.silverwrist.dynamo.iface.ServiceProvider ServiceProvider}
   *                 which provides initialization services to the component.  This will include an implementation
   *                 of {@link com.silverwrist.dynamo.iface.ObjectProvider ObjectProvider} which may be used to
   *                 get information about other objects previously initialized by the application.
   * @exception com.silverwrist.dynamo.except.ConfigException If an error is encountered in the component
   *            configuration.
   */
  public void initialize(Element config_root, ServiceProvider services) throws ConfigException
  {
    logger.info("UniStoreManager initializing");
    XMLLoader loader = XMLLoader.get();
    String conn_name = null;
    String nscache_name = null;
    String srm_name = null;
    String users_name = null;
    int msgcache_hard = DEFAULT_MSGCACHE_HARD, msgcache_soft = DEFAULT_MSGCACHE_SOFT;
    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
      DOMElementHelper config_root_h = new DOMElementHelper(config_root);
      Element elt = loader.getSubElement(config_root_h,"database");
      conn_name = loader.getAttribute(elt,"connection");
      nscache_name = loader.getAttribute(elt,"namespaces");

      // get the security reference monitor name
      elt = loader.getSubElement(config_root_h,"security");
      srm_name = loader.getAttribute(elt,"object");

      // get the user management object name
      elt = loader.getSubElement(config_root_h,"user-manager");
      users_name = loader.getAttribute(elt,"object");

      // get the message cache configuration
      elt = config_root_h.getSubElement("message-cache");
      if (elt!=null)
      { // load the limits
	msgcache_hard = loader.getAttributeInt(elt,"hardlimit",DEFAULT_MSGCACHE_HARD);
	msgcache_soft = loader.getAttributeInt(elt,"softlimit",DEFAULT_MSGCACHE_SOFT);

      } // end if

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

    } // end catch

    // Create the message cache.
    m_msgcache = new HardSoftCache(msgcache_hard,msgcache_soft);

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

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

    // Get the security reference monitor and user manager object..
    m_srm = (SecurityReferenceMonitor)(GetObjectUtils.getDynamoComponent(services,SecurityReferenceMonitor.class,
									 srm_name));
    m_users = (UserManagement)(GetObjectUtils.getDynamoComponent(services,UserManagement.class,users_name));
    m_post = (PostDynamicUpdate)(services.queryService(PostDynamicUpdate.class));

    // Register our index resolvers.
    IndexManagerConfig imcfg = (IndexManagerConfig)(services.queryService(IndexManagerConfig.class));
    m_res1 = imcfg.registerResolver(Namespaces.DEFAULT_INDEX_RESOLVERS_NAMESPACE,"unistore.message",
				    new UniStoreResolver());
    m_res2 = imcfg.registerResolver(Namespaces.DEFAULT_INDEX_RESOLVERS_NAMESPACE,"unistore.message.part",
				    new UniStorePartResolver());

  } // end initialize

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

  public void shutdown()
  {
    m_res1.shutdown();
    m_res1 = null;
    m_res2.shutdown();
    m_res2 = null;
    m_msgcache.clear();
    m_post = null;
    m_srm = null;
    m_users = null;
    m_ns_cache = null;
    m_ops.dispose();
    m_ops = null;

  } // end shutdown

  /*--------------------------------------------------------------------------------
   * Implementations from interface UniStore
   *--------------------------------------------------------------------------------
   */

  public synchronized UniStoreMessage getMessage(long msgid) throws DatabaseException
  {
    Long key = new Long(msgid);
    MessageImpl rc = (MessageImpl)(m_msgcache.get(key));
    if (rc==null)
    { // need to look it up in the database!
      Map params = m_ops.getMessageData(msgid);
      rc = new MessageImpl(m_ops.getMessageOps(),m_ns_cache,m_srm,m_users,m_post,params);
      params.clear();
      m_msgcache.put(key,rc);

    } // end if
    else if (rc.getMessageID()!=msgid)
    { // This only happens when the message has been deleted but its object still lingers in the cache.  Make
      // sure the cache entry is removed and throw an error.
      m_msgcache.remove(key);
      DatabaseException de = new DatabaseException(UniStoreManager.class,"UniStoreMessages","no.message");
      de.setParameter(0,String.valueOf(msgid));
      throw de;

    } // end else if.

    return rc;

  } // end getMessage

  public UniStoreMessage createMessage(DynamoUser caller) throws DatabaseException
  {
    // Call down to the database to create the record.
    java.util.Date tick = new java.util.Date();
    long newid = m_ops.createMessage(caller.getUID(),tick);

    // Fake up a parameter buffer and use it to create the MessageImpl object.
    Long key = new Long(newid);
    HashMap params = new HashMap();
    params.put(ManagerOps.PARAM_MSGID,key);
    params.put(ManagerOps.PARAM_PARENT,NO_PARENT);
    params.put(ManagerOps.PARAM_SEQ,NO_SEQ);
    params.put(ManagerOps.PARAM_CREATOR,new Integer(caller.getUID()));
    params.put(ManagerOps.PARAM_POSTED,tick);
    MessageImpl rc = new MessageImpl(m_ops.getMessageOps(),m_ns_cache,m_srm,m_users,m_post,params);
    rc.zeroCounts();

    synchronized (this)
    { // add the new message object to the cache
      m_msgcache.put(key,rc);

    } // end synchronized block

    m_post.postUpdate(new MessageCreatedEvent(rc));
    return rc;

  } // end createMessage

} // end class UniStoreManager
