/*
* 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 .
*
* 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 ,
* 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
* null
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
* null
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=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