com.silverwrist.dynamo.unistore.MessageImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.silverwrist.dynamo.unistore.MessageImpl.java

Source

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