/*
 * 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.io.*;
import java.security.acl.AclNotFoundException;
import java.util.*;
import java.util.regex.*;
import org.apache.commons.collections.*;
import org.apache.log4j.Logger;
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.*;

class MessageImpl implements UniStoreMessage
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

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

  private static final Integer NO_READS = new Integer(0);

  private static Pattern NEWLINES;

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

  private MessageOps m_ops;                 // database operations object
  private NamespaceCache m_nscache;         // namespace cache
  private SecurityReferenceMonitor m_srm;   // security reference monitor
  private UserManagement m_users;           // user manager
  private PostDynamicUpdate m_post;         // dynamic update poster
  private long m_id;                        // the message ID
  private long m_parentid;                  // the parent message ID
  private int m_seq;                        // sequence within parent
  private int m_creator;                    // UID of creator
  private java.util.Date m_posted;          // date message was posted
  private int m_aclid = -1;                 // ACL id
  private ReferenceMap m_properties;        // properties cache
  private int m_text_count = -1;            // number of text parts
  private int m_binary_count = -1;          // number of binary parts
  private ReferenceMap m_part_to_text;      // mapping from part index to text part
  private ReferenceMap m_pk_to_text;        // mapping from property key to text part
  private ReferenceMap m_part_to_binary;    // mapping from part index to binary part
  private ReferenceMap m_pk_to_binary;      // mapping from property key to binary part

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

  MessageImpl(MessageOps ops, NamespaceCache nscache, SecurityReferenceMonitor srm, UserManagement users,
	      PostDynamicUpdate post, Map params)
  {
    m_ops = ops;
    m_nscache = nscache;
    m_srm = srm;
    m_users = users;
    m_post = post;
    m_id = ((Long)(params.get(ManagerOps.PARAM_MSGID))).longValue();
    m_parentid = ((Long)(params.get(ManagerOps.PARAM_PARENT))).longValue();
    m_seq = ((Integer)(params.get(ManagerOps.PARAM_SEQ))).intValue();
    m_creator = ((Integer)(params.get(ManagerOps.PARAM_CREATOR))).intValue();
    m_posted = (java.util.Date)(params.get(ManagerOps.PARAM_POSTED));
    Integer tmp = (Integer)(params.get(ManagerOps.PARAM_ACLID));
    if (tmp!=null)
      m_aclid = tmp.intValue();
    m_properties = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_part_to_text = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_pk_to_text = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_part_to_binary = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);
    m_pk_to_binary = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);

  } // end constructor

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

  private static final int getLineCount(String s)
  {
    Matcher m = NEWLINES.matcher(s);
    int rc = 1;
    while (m.find())
      rc++;
    return rc;

  } // end getLineCount

  private final UniStoreTextPart createTextPart(int nsid, String name, String mimetype, int charcount, int linecount,
						String text) throws DatabaseException
  {
    // Call down to the database to create the part.
    int partnum = m_ops.createTextPart(m_id,nsid,name,mimetype,charcount,linecount,text);

    // Fake up a parameter buffer to create the part object.
    Integer key1 = new Integer(partnum);
    PropertyKey key2 = new PropertyKey(nsid,name);
    HashMap params = new HashMap();
    params.put(MessageOps.PARAM_PART,key1);
    params.put(MessageOps.PARAM_IDENTITY,key2);
    if (mimetype!=null)
      params.put(MessageOps.PARAM_MIMETYPE,mimetype);
    params.put(MessageOps.PARAM_SIZE,new Integer(charcount));
    params.put(MessageOps.PARAM_LINECOUNT,new Integer(linecount));
    params.put(MessageOps.PARAM_READS,NO_READS);
    TextPartImpl rc = new TextPartImpl(m_ops.getTextPartOps(),m_nscache,m_post,this,params);
    rc.precacheText(text);

    synchronized (this)
    { // Add the text part to the internal caches.
      m_part_to_text.put(key1,rc);
      m_pk_to_text.put(key2,rc);
      if (m_text_count>=0)
	m_text_count++;

    } // end synchronized block

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

  } // end createTextPart

  private final UniStoreBinaryPart createBinaryPart(int nsid, String name, String mimetype, String filename,
						    int length, InputStream data) throws DatabaseException
  {
    // Call down to the database to create the part.
    int partnum = m_ops.createBinaryPart(m_id,nsid,name,mimetype,filename,length,data);

    // Fake up a parameter buffer to create the part object.
    Integer key1 = new Integer(partnum);
    PropertyKey key2 = new PropertyKey(nsid,name);
    HashMap params = new HashMap();
    params.put(MessageOps.PARAM_PART,key1);
    params.put(MessageOps.PARAM_IDENTITY,key2);
    if (mimetype!=null)
      params.put(MessageOps.PARAM_MIMETYPE,mimetype);
    params.put(MessageOps.PARAM_SIZE,new Integer(length));
    if (filename!=null)
      params.put(MessageOps.PARAM_FILENAME,filename);
    params.put(MessageOps.PARAM_READS,NO_READS);
    BinaryPartImpl rc = new BinaryPartImpl(m_ops.getBinaryPartOps(),m_nscache,m_post,this,params);

    synchronized (this)
    { // Add the binary part to the internal caches.
      m_part_to_binary.put(key1,rc);
      m_pk_to_binary.put(key2,rc);
      if (m_binary_count>=0)
	m_binary_count++;

    } // end synchronized block

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

  } // end createBinaryPart

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

  public String toString()
  {
    if (m_ops==null)
      return "(deleted message)";
    return "message " + m_id;

  } // end toString

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

  /**
   * Retrieves an object from this message'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 (m_ops==null)
      throw new NoSuchObjectException(this.toString(),namespace,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)
	    m_properties.put(key,rc);

	} // end if

      } // end synchronized block

      if (rc==null)
	throw new NoSuchObjectException(this.toString(),namespace,name);
      return rc;

    } // end try
    catch (DatabaseException e)
    { // translate into our NoSuchObjectException but retain the DatabaseException
      throw new NoSuchObjectException(this.toString(),namespace,name,e);

    } // end catch

  } // end getObject

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

  /**
   * Sets an object into this message'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 message's properties.
   * @return The previous object that was set into the message'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 message's properties.
   */
  public Object setObject(DynamoUser caller, String namespace, String name, Object value)
      throws DatabaseException, DynamoSecurityException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    testPermission(caller,namespace,"set.property","no.setProperty");
    Object 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);

    } // end synchronized block

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

  } // end setObject

  /**
   * Removes an object from this message'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 message'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 message's properties.
   */
  public Object removeObject(DynamoUser caller, String namespace, String name)
      throws DatabaseException, DynamoSecurityException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    testPermission(caller,namespace,"remove.property","no.removeProperty");
    Object 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);

    } // end synchronized block

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

  } // end removeObject

  /**
   * Returns a collection of all object namespaces that have been set into this message'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
  {
    if (m_ops==null)
      return Collections.EMPTY_LIST;

    // 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 message'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
  {
    if (m_ops==null)
      return Collections.EMPTY_LIST;

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

  public long getMessageID()
  {
    return m_id;

  } // end getMessageID

  public long getParentMessageID()
  {
    return m_parentid;

  } // end getParentMessageID

  public void setParentMessageID(DynamoUser caller, long id) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"set.parent","no.setParent");
    synchronized (this)
    { // reset the parent message ID
      m_ops.setParentMessageID(m_id,id);
      m_parentid = id;

    } // end synchronized block

    m_post.postUpdate(new MessagePositionUpdateEvent(this,m_parentid,m_seq));

  } // end setParentMessageID

  public int getSequence()
  {
    return m_seq;

  } // end getSequence

  public void setSequence(DynamoUser caller, int seq) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"set.parent","no.setParent");
    synchronized (this)
    { // reset the sequence number
      m_ops.setSequence(m_id,seq);
      m_seq = seq;

    } // end if

    m_post.postUpdate(new MessagePositionUpdateEvent(this,m_parentid,m_seq));

  } // end setSequence

  public int getCreatorUID()
  {
    return m_creator;

  } // end getCreatorUID

  public DynamoUser getCreator() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    return (m_users==null) ? null : m_users.getUser(m_creator);

  } // end getCreator

  public java.util.Date getPostDate()
  {
    return m_posted;

  } // end getPostDate

  public DynamoAcl getAcl() throws DatabaseException, AclNotFoundException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    if ((m_aclid<0) || (m_srm==null))
      return null;
    else
      return m_srm.getAcl(m_aclid);

  } // end getAcl

  public void setAcl(DynamoUser caller, DynamoAcl acl) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"set.ACL","no.setACL");
    int aclid = -1;
    if (acl!=null)
      aclid = acl.getAclID();
    synchronized (this)
    { // reset the ACL ID
      m_ops.setAclID(m_id,aclid);
      m_aclid = aclid;

    } // end synchronized block

    m_post.postUpdate(new MessageSecurityUpdateEvent(this,acl));

  } // end setAcl

  public synchronized int getNumTextParts() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    if (m_text_count<0)
      m_text_count = m_ops.getNumTextParts(m_id);
    return m_text_count;

  } // end getNumTextParts

  public synchronized int getNumBinaryParts() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    if (m_binary_count<0)
      m_binary_count = m_ops.getNumBinaryParts(m_id);
    return m_binary_count;

  } // end getNumBinaryParts

  public UniStoreTextPart getTextPart(int index) throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    Integer key = new Integer(index);
    TextPartImpl rc = null;
    synchronized (this)
    { // look in the part-to-text map first
      rc = (TextPartImpl)(m_part_to_text.get(key));
      if (rc==null)
      { // OK, look up the part in the database
	Map params = m_ops.loadTextPart(m_id,index);
	PropertyKey otherkey = (PropertyKey)(params.get(MessageOps.PARAM_IDENTITY));
	rc = (TextPartImpl)(m_pk_to_text.get(otherkey));
	if (rc==null)
	{ // need to create a new object
	  rc = new TextPartImpl(m_ops.getTextPartOps(),m_nscache,m_post,this,params);
	  m_part_to_text.put(key,rc);
	  m_pk_to_text.put(otherkey,rc);

	} // end if
	else // feed back into the parts map
	  m_part_to_text.put(key,rc);

      } // end if
      else
      { // need to reinsert into property key map
	QualifiedNameKey qnk = rc.getPartIdentity();
	PropertyKey pk = new PropertyKey(m_nscache.namespaceNameToId(qnk.getNamespace()),qnk.getName());
	m_pk_to_text.put(pk,rc);

      } // end else

    } // end synchronized block

    return rc;

  } // end getTextPart

  public UniStoreTextPart getTextPart(String namespace, String name) throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
    TextPartImpl rc = null;
    synchronized (this)
    { // look in the PK-to-text map first
      rc = (TextPartImpl)(m_pk_to_text.get(key));
      if (rc==null)
      { // OK, look up the part in the database
	Map params = m_ops.loadTextPart(m_id,key);
	Integer otherkey = (Integer)(params.get(MessageOps.PARAM_PART));
	rc = (TextPartImpl)(m_part_to_text.get(otherkey));
	if (rc==null)
	{ // OK, need to create the object
	  rc = new TextPartImpl(m_ops.getTextPartOps(),m_nscache,m_post,this,params);
	  m_part_to_text.put(otherkey,rc);
	  m_pk_to_text.put(key,rc);

	} // end if
	else // feed back into the PKs map
	  m_pk_to_text.put(key,rc);

      } // end if
      else  // reinsert into part key map
	m_part_to_text.put(new Integer(rc.getPartIndex()),rc);

    } // end synchronized block

    return rc;

  } // end getTextPart

  public UniStoreBinaryPart getBinaryPart(int index) throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    Integer key = new Integer(index);
    BinaryPartImpl rc = null;
    synchronized (this)
    { // look in the part-to-binary map first
      rc = (BinaryPartImpl)(m_part_to_binary.get(key));
      if (rc==null)
      { // OK, look up the part in the database
	Map params = m_ops.loadBinaryPart(m_id,index);
	PropertyKey otherkey = (PropertyKey)(params.get(MessageOps.PARAM_IDENTITY));
	rc = (BinaryPartImpl)(m_pk_to_binary.get(otherkey));
	if (rc==null)
	{ // need to create a new object
	  rc = new BinaryPartImpl(m_ops.getBinaryPartOps(),m_nscache,m_post,this,params);
	  m_part_to_binary.put(key,rc);
	  m_pk_to_binary.put(otherkey,rc);

	} // end if
	else // feed back into the parts map
	  m_part_to_binary.put(key,rc);

      } // end if
      else
      { // need to reinsert into property key map
	QualifiedNameKey qnk = rc.getPartIdentity();
	PropertyKey pk = new PropertyKey(m_nscache.namespaceNameToId(qnk.getNamespace()),qnk.getName());
	m_pk_to_binary.put(pk,rc);

      } // end else

    } // end synchronized block

    return rc;

  } // end getBinaryPart

  public UniStoreBinaryPart getBinaryPart(String namespace, String name) throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
    BinaryPartImpl rc = null;
    synchronized (this)
    { // look in the PK-to-binary map first
      rc = (BinaryPartImpl)(m_pk_to_binary.get(key));
      if (rc==null)
      { // OK, look up the part in the database
	Map params = m_ops.loadBinaryPart(m_id,key);
	Integer otherkey = (Integer)(params.get(MessageOps.PARAM_PART));
	rc = (BinaryPartImpl)(m_part_to_binary.get(otherkey));
	if (rc==null)
	{ // OK, need to create the object
	  rc = new BinaryPartImpl(m_ops.getBinaryPartOps(),m_nscache,m_post,this,params);
	  m_part_to_binary.put(otherkey,rc);
	  m_pk_to_binary.put(key,rc);

	} // end if
	else // feed back into the PKs map
	  m_pk_to_binary.put(key,rc);

      } // end if
      else  // reinsert into part key map
	m_part_to_binary.put(new Integer(rc.getPartIndex()),rc);

    } // end synchronized block

    return rc;

  } // end getBinaryPart

  public List getTextParts() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    int n = this.getNumTextParts();
    ArrayList rc = new ArrayList(n);
    for (int i=1; i<=n; i++)
      rc.add(this.getTextPart(i));
    return Collections.unmodifiableList(rc);

  } // end getTextParts

  public List getBinaryParts() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    int n = this.getNumBinaryParts();
    ArrayList rc = new ArrayList(n);
    for (int i=1; i<=n; i++)
      rc.add(this.getBinaryPart(i));
    return Collections.unmodifiableList(rc);

  } // end getBinaryParts

  public UniStoreTextPart createTextPart(DynamoUser caller, String namespace, String name, String mimetype,
					 HTMLChecker data) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"create.textPart","no.createPart");
    return createTextPart(m_nscache.namespaceNameToId(namespace),name,mimetype,data.getLength(),data.getLines(),
			  data.getValue());

  } // end createTextPart

  public UniStoreTextPart createTextPart(DynamoUser caller, String namespace, String name, String mimetype,
					 String data) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"create.textPart","no.createPart");
    return createTextPart(m_nscache.namespaceNameToId(namespace),name,mimetype,data.length(),getLineCount(data),data);

  } // end createTextPart

  public UniStoreBinaryPart createBinaryPart(DynamoUser caller, String namespace, String name, DataItem data)
      throws IOException, DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"create.binaryPart","no.createPart");
    return createBinaryPart(m_nscache.namespaceNameToId(namespace),name,data.getMimeType(),data.getName(),
			    data.getSize(),data.getDataStream());

  } // end createBinaryPart

  public UniStoreBinaryPart createBinaryPart(DynamoUser caller, String namespace, String name, String mimetype,
					     String filename, int length, InputStream data)
      throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"create.binaryPart","no.createPart");
    return createBinaryPart(m_nscache.namespaceNameToId(namespace),name,mimetype,filename,length,data);

  } // end createBinaryPart

  public synchronized void delete(DynamoUser caller) throws DatabaseException, DynamoSecurityException
  {
    testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"delete.message","no.deleteMessage");

    // We need to have lists of the text and binary parts on hand before we delete everything, so that we can
    // send out notifications.  However, not every one of the parts will be in our cache, so we'll need to create
    // some temporary instances, but use the cached ones whereever feasible.
    Map pmap = m_ops.listTextParts(m_id);
    ArrayList text_parts = new ArrayList(pmap.size());
    Iterator it = pmap.entrySet().iterator();
    while (it.hasNext())
    { // look for a matching TextPartImpl
      Map.Entry ntry = (Map.Entry)(it.next());
      TextPartImpl p = (TextPartImpl)(m_part_to_text.get(ntry.getKey()));
      if (p==null)
	p = (TextPartImpl)(m_pk_to_text.get(ntry.getValue()));
      if (p==null)
      { // create a "scratch" instance
	PropertyKey pk = (PropertyKey)(ntry.getValue());
	QualifiedNameKey qname = new QualifiedNameKey(m_nscache.namespaceIdToName(pk.getNamespaceID()),pk.getName());
	p = new TextPartImpl(m_post,this,((Integer)(ntry.getKey())).intValue(),qname);

      } // end if

      text_parts.add(p);

    } // end while

    pmap = m_ops.listBinaryParts(m_id);
    ArrayList binary_parts = new ArrayList(pmap.size());
    it = pmap.entrySet().iterator();
    while (it.hasNext())
    { // look for a matching BinaryPartImpl
      Map.Entry ntry = (Map.Entry)(it.next());
      BinaryPartImpl p = (BinaryPartImpl)(m_part_to_binary.get(ntry.getKey()));
      if (p==null)
	p = (BinaryPartImpl)(m_pk_to_binary.get(ntry.getValue()));
      if (p==null)
      { // create a "scratch" instance
	PropertyKey pk = (PropertyKey)(ntry.getValue());
	QualifiedNameKey qname = new QualifiedNameKey(m_nscache.namespaceIdToName(pk.getNamespaceID()),pk.getName());
	p = new BinaryPartImpl(m_post,this,((Integer)(ntry.getKey())).intValue(),qname);

      } // end if

      binary_parts.add(p);

    } // end while

    // Delete the message from the database.
    m_ops.delete(m_id);

    // Cut loose most of our data before we start notifying.
    m_ops = null;
    m_nscache = null;
    m_srm = null;
    m_users = null;
    m_parentid = -1;
    m_seq = -1;
    m_creator = -1;
    m_posted = null;
    m_aclid = -1;
    m_properties.clear();
    m_text_count = -1;
    m_binary_count = -1;
    m_part_to_text.clear();
    m_pk_to_text.clear();
    m_part_to_binary.clear();
    m_pk_to_binary.clear();

    // Send out the deletion notifications (and clear the data) for all parts.
    it = text_parts.iterator();
    while (it.hasNext())
    { // make sure all of these parts are BALEETED!
      TextPartImpl p = (TextPartImpl)(it.next());
      p.baleeted();

    } // end while

    text_parts.clear();
    it = binary_parts.iterator();
    while (it.hasNext())
    { // make sure all of these parts are BALEETED!
      BinaryPartImpl p = (BinaryPartImpl)(it.next());
      p.baleeted();

    } // end while

    binary_parts.clear();

    // Send our own "BALEETED!" notification.
    m_post.postUpdate(new MessageDeletedEvent(this));

    // Now cut loose the rest of our data.
    m_post = null;
    m_id = -1;

  } // end delete

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  void testPermission(DynamoUser caller, String perm_namespace, String perm_name, String fail_message)
      throws DatabaseException, DynamoSecurityException
  {
    if (m_ops==null)
      throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted");
    if (caller.equals(m_srm.getAdminUser()))
      return;  // Administrator can do anything
    if (m_aclid==-1)
    { // no ACL - rely on fallback method
      if (caller.getUID()==m_creator)
	return;  // the creator can do anything, but no one else can

    } // end if
    else
    { // test against the ACL
      try
      { // look up the ACL
	if (m_srm.getAcl(m_aclid).testPermission(caller,perm_namespace,perm_name))
	  return;

      } // end try
      catch (AclNotFoundException e)
      { // ACL not found - go to fallback mechanism
	logger.warn("ACL " + m_aclid + " not found while testing message " + m_id);
	if (caller.getUID()==m_creator)
	  return;  // the creator can do anything, but no one else can

      } // end catch

    } // end else

    // Throw a DynamoSecurityException indicating what you're not permitted to do.
    DynamoSecurityException de = new DynamoSecurityException(MessageImpl.class,"UniStoreMessages",fail_message);
    de.setParameter(0,String.valueOf(m_id));
    throw de;

  } // end testPermission

  void zeroCounts()
  {
    m_text_count = 0;
    m_binary_count = 0;

  } // end zeroCounts

  void deletedTextPart(int partnum, QualifiedNameKey identity) throws DatabaseException
  {
    PropertyKey pk = new PropertyKey(m_nscache.namespaceNameToId(identity.getNamespace()),identity.getName());
    synchronized (this)
    { // Remove the entry from the cache reference maps.
      m_part_to_text.remove(new Integer(partnum));
      m_pk_to_text.remove(pk);
      if (m_text_count>=0)
	m_text_count--;

      // All entries with a part number higher than the deleted part number have to be renumbered downwards.
      // First, scan through the keyset to find all the appropriate part numbers, get their values, and lock them
      // into a hard HashMap to keep them in memory while we do this.
      HashMap temp = new HashMap();
      Iterator it = m_part_to_text.keySet().iterator();
      while (it.hasNext())
      { // get each key in turn and check it
	Integer key = (Integer)(it.next());
	if (key.intValue()>partnum)
	{ // now see if the object's in memory
	  TextPartImpl obj = (TextPartImpl)(m_part_to_text.get(key));
	  if (obj!=null)
	    temp.put(key,obj);

	} // end if

      } // end while

      // Now go through, poke new part numbers into each of these parts, and get them into the parts
      // mapping correctly.
      it = temp.entrySet().iterator();
      while (it.hasNext())
      { // get each part in turn and deal with it
	Map.Entry ntry = (Map.Entry)(it.next());
	m_part_to_text.remove(ntry.getKey());
	int new_partnum = ((Integer)(ntry.getKey())).intValue() - 1;
	TextPartImpl obj = (TextPartImpl)(ntry.getValue());
	obj.resetPartNumber(new_partnum);
	m_part_to_text.put(new Integer(new_partnum),obj);

      } // end while

      temp.clear();  // release the extra references
      
    } // end synchronized block

  } // end deletedTextPart

  void deletedBinaryPart(int partnum, QualifiedNameKey identity) throws DatabaseException
  {
    PropertyKey pk = new PropertyKey(m_nscache.namespaceNameToId(identity.getNamespace()),identity.getName());
    synchronized (this)
    { // Remove the entry from the cache reference maps.
      m_part_to_binary.remove(new Integer(partnum));
      m_pk_to_binary.remove(pk);
      if (m_binary_count>=0)
	m_binary_count--;

      // All entries with a part number higher than the deleted part number have to be renumbered downwards.
      // First, scan through the keyset to find all the appropriate part numbers, get their values, and lock them
      // into a hard HashMap to keep them in memory while we do this.
      HashMap temp = new HashMap();
      Iterator it = m_part_to_binary.keySet().iterator();
      while (it.hasNext())
      { // get each key in turn and check it
	Integer key = (Integer)(it.next());
	if (key.intValue()>partnum)
	{ // now see if the object's in memory
	  BinaryPartImpl obj = (BinaryPartImpl)(m_part_to_binary.get(key));
	  if (obj!=null)
	    temp.put(key,obj);

	} // end if

      } // end while

      // Now go through, poke new part numbers into each of these parts, and get them into the parts
      // mapping correctly.
      it = temp.entrySet().iterator();
      while (it.hasNext())
      { // get each part in turn and deal with it
	Map.Entry ntry = (Map.Entry)(it.next());
	m_part_to_binary.remove(ntry.getKey());
	int new_partnum = ((Integer)(ntry.getKey())).intValue() - 1;
	BinaryPartImpl obj = (BinaryPartImpl)(ntry.getValue());
	obj.resetPartNumber(new_partnum);
	m_part_to_binary.put(new Integer(new_partnum),obj);

      } // end while

      temp.clear();  // release the extra references
      
    } // end synchronized block

  } // end deletedBinaryPart

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  {
    try
    { // set up our patterns
      NEWLINES = Pattern.compile("\\r?\\n?"); // matches CR, LF, or CRLF

    } // end try
    catch (PatternSyntaxException e)
    { // just log the error
      logger.fatal("Pattern compile error in MessageImpl",e);

    } // end catch

  } // end static initializer

} // end class MessageImpl
