diff --git a/src/dynamo-framework/com/silverwrist/dynamo/event/MessageCreatedEvent.java b/src/dynamo-framework/com/silverwrist/dynamo/event/MessageCreatedEvent.java new file mode 100644 index 0000000..47c49a5 --- /dev/null +++ b/src/dynamo-framework/com/silverwrist/dynamo/event/MessageCreatedEvent.java @@ -0,0 +1,46 @@ +/* + * 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.event; + +import com.silverwrist.dynamo.iface.UniStoreMessage; + +public class MessageCreatedEvent extends MessageUpdateEvent +{ + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public MessageCreatedEvent(UniStoreMessage src) + { + super(src); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Overrides from class MessageUpdateEvent + *-------------------------------------------------------------------------------- + */ + + public String toString() + { + return "MessageCreatedEvent: new message ID#" + getMessage().getMessageID(); + + } // end toString + +} // end class MessageCreatedEvent diff --git a/src/dynamo-framework/com/silverwrist/dynamo/event/MessageDeletedEvent.java b/src/dynamo-framework/com/silverwrist/dynamo/event/MessageDeletedEvent.java new file mode 100644 index 0000000..b9a32e4 --- /dev/null +++ b/src/dynamo-framework/com/silverwrist/dynamo/event/MessageDeletedEvent.java @@ -0,0 +1,46 @@ +/* + * 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.event; + +import com.silverwrist.dynamo.iface.UniStoreMessage; + +public class MessageDeletedEvent extends MessageUpdateEvent +{ + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public MessageDeletedEvent(UniStoreMessage src) + { + super(src); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Overrides from class MessageUpdateEvent + *-------------------------------------------------------------------------------- + */ + + public String toString() + { + return "MessageDeletedEvent: deleted message ID#" + getMessage().getMessageID(); + + } // end toString + +} // end class MessageDeletedEvent diff --git a/src/dynamo-framework/com/silverwrist/dynamo/event/MessagePartAddedEvent.java b/src/dynamo-framework/com/silverwrist/dynamo/event/MessagePartAddedEvent.java new file mode 100644 index 0000000..45dbceb --- /dev/null +++ b/src/dynamo-framework/com/silverwrist/dynamo/event/MessagePartAddedEvent.java @@ -0,0 +1,47 @@ +/* + * 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.event; + +import com.silverwrist.dynamo.iface.UniStorePart; + +public class MessagePartAddedEvent extends MessagePartUpdateEvent +{ + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public MessagePartAddedEvent(UniStorePart src) + { + super(src); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Overrides from class MessagePartUpdateEvent + *-------------------------------------------------------------------------------- + */ + + public String toString() + { + return "MessagePartAddedEvent: message ID#" + getMessagePart().getMessageID() + ", new " + + (isBinaryPart() ? "binary" : "text") + " part " + getMessagePart().getPartIdentity(); + + } // end toString + +} // end class MessagePartAddedEvent diff --git a/src/dynamo-framework/com/silverwrist/dynamo/event/MessagePartDeletedEvent.java b/src/dynamo-framework/com/silverwrist/dynamo/event/MessagePartDeletedEvent.java new file mode 100644 index 0000000..8e7b596 --- /dev/null +++ b/src/dynamo-framework/com/silverwrist/dynamo/event/MessagePartDeletedEvent.java @@ -0,0 +1,47 @@ +/* + * 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.event; + +import com.silverwrist.dynamo.iface.UniStorePart; + +public class MessagePartDeletedEvent extends MessagePartUpdateEvent +{ + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public MessagePartDeletedEvent(UniStorePart src) + { + super(src); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Overrides from class MessagePartUpdateEvent + *-------------------------------------------------------------------------------- + */ + + public String toString() + { + return "MessagePartDeletedEvent: message ID#" + getMessagePart().getMessageID() + ", deleted " + + (isBinaryPart() ? "binary" : "text") + " part " + getMessagePart().getPartIdentity(); + + } // end toString + +} // end class MessagePartDeletedEvent diff --git a/src/dynamo-framework/com/silverwrist/dynamo/htmlcheck/RewriteURLs.java b/src/dynamo-framework/com/silverwrist/dynamo/htmlcheck/RewriteURLs.java index e1d9142..24d78ad 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/htmlcheck/RewriteURLs.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/htmlcheck/RewriteURLs.java @@ -109,16 +109,16 @@ class RewriteURLs implements HTMLCheckerConfigurator, HTMLRewriter try { // pre-compile all our regex patterns // recognize: http://[:]... - l.add(new Match("^http://[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]+)?(/.*)?")); + l.add(new Match("^http://[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]+)?(/.*)?$")); // recognize: ftp://[:]... - l.add(new Match("^ftp://[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]+)?(/.*)?")); + l.add(new Match("^ftp://[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]+)?(/.*)?$")); // recognize: gopher://[:]... - l.add(new Match("^gopher://[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]+)?(/.*)?")); + l.add(new Match("^gopher://[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]+)?(/.*)?$")); - // recognize: mailto: - l.add(new Match("^mailto:[A-Za-z0-9!#$%*+-/=?^_`{|}~.]+@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*")); + // recognize: mailto:[?queryinfo] + l.add(new Match("^mailto:[A-Za-z0-9!#$%*+-/=?^_`{|}~.]+@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\?.+)?$")); // recognize: news: l.add(new Match("^news:[A-Za-z0-9-_]+(\\.[A-Za-z0-9-_]+)*$")); @@ -137,13 +137,13 @@ class RewriteURLs implements HTMLCheckerConfigurator, HTMLRewriter l.add(new Match("^tn3270://[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(:[0-9]+)?$")); // recognize "www.whatever" and tack http:// onto the front - l.add(new Match("^www\\.[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(/.*)?","http://")); + l.add(new Match("^www\\.[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(/.*)?$","http://")); // recognize "ftp.whatever" and tack ftp:// onto the front - l.add(new Match("^ftp\\.[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(/.*)?","ftp://")); + l.add(new Match("^ftp\\.[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(/.*)?$","ftp://")); // recognize "gopher.whatever" and tack gopher:// onto the front - l.add(new Match("^gopher\\.[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(/.*)?","gopher://")); + l.add(new Match("^gopher\\.[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(/.*)?$","gopher://")); } // end try catch (PatternSyntaxException pe) diff --git a/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStore.java b/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStore.java index 70a65b6..a2b26d2 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStore.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStore.java @@ -23,4 +23,6 @@ public interface UniStore { public UniStoreMessage getMessage(long msgid) throws DatabaseException; + public UniStoreMessage createMessage(DynamoUser caller) throws DatabaseException; + } // end interface UniStore diff --git a/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStoreMessage.java b/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStoreMessage.java index 549d73c..38adc89 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStoreMessage.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStoreMessage.java @@ -17,6 +17,8 @@ */ package com.silverwrist.dynamo.iface; +import java.io.InputStream; +import java.io.IOException; import java.security.acl.AclNotFoundException; import java.util.Date; import java.util.List; @@ -61,4 +63,19 @@ public interface UniStoreMessage extends SecureObjectStore public List getBinaryParts() throws DatabaseException; + public UniStoreTextPart createTextPart(DynamoUser caller, String namespace, String name, String mimetype, + HTMLChecker data) throws DatabaseException, DynamoSecurityException; + + public UniStoreTextPart createTextPart(DynamoUser caller, String namespace, String name, String mimetype, + String data) throws DatabaseException, DynamoSecurityException; + + public UniStoreBinaryPart createBinaryPart(DynamoUser caller, String namespace, String name, DataItem data) + throws IOException, DatabaseException, DynamoSecurityException; + + public UniStoreBinaryPart createBinaryPart(DynamoUser caller, String namespace, String name, String mimetype, + String filename, int length, InputStream data) + throws DatabaseException, DynamoSecurityException; + + public void delete(DynamoUser caller) throws DatabaseException, DynamoSecurityException; + } // end interface UniStoreMessage diff --git a/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStorePart.java b/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStorePart.java index cc7ccff..5413c3d 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStorePart.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/iface/UniStorePart.java @@ -19,6 +19,7 @@ package com.silverwrist.dynamo.iface; import java.util.Date; import com.silverwrist.dynamo.except.DatabaseException; +import com.silverwrist.dynamo.except.DynamoSecurityException; import com.silverwrist.dynamo.util.QualifiedNameKey; public interface UniStorePart extends SecureObjectStore @@ -41,4 +42,6 @@ public interface UniStorePart extends SecureObjectStore public void touchRead() throws DatabaseException; + public void delete(DynamoUser caller) throws DatabaseException, DynamoSecurityException; + } // end interface UniStorePart diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartImpl.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartImpl.java index ccbed52..914e0b9 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartImpl.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartImpl.java @@ -22,6 +22,7 @@ import java.sql.Blob; import java.sql.SQLException; import java.util.*; import org.apache.commons.collections.*; +import com.silverwrist.dynamo.Namespaces; import com.silverwrist.dynamo.db.NamespaceCache; import com.silverwrist.dynamo.event.*; import com.silverwrist.dynamo.except.*; @@ -141,7 +142,7 @@ class BinaryPartImpl implements UniStoreBinaryPart private ReferenceMap m_properties; /*-------------------------------------------------------------------------------- - * Constructor + * Constructors *-------------------------------------------------------------------------------- */ @@ -166,6 +167,36 @@ class BinaryPartImpl implements UniStoreBinaryPart } // end constructor + BinaryPartImpl(PostDynamicUpdate post, MessageImpl parent, int part, QualifiedNameKey identity) + { + m_ops = null; + m_nscache = null; + m_post = post; + m_parent = parent; + m_part = part; + m_identity = identity; + m_mimetype = null; + m_size = -1; + m_filename = null; + m_nread = 0; + m_lastread = null; + m_properties = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Overrides from class Object + *-------------------------------------------------------------------------------- + */ + + public String toString() + { + if (m_ops==null) + return "(deleted binary part)"; + return "message " + m_parent.getMessageID() + ", binary part " + m_identity.toString(); + + } // end toString + /*-------------------------------------------------------------------------------- * Implementations from interface ObjectProvider *-------------------------------------------------------------------------------- @@ -180,6 +211,8 @@ class BinaryPartImpl implements UniStoreBinaryPart */ 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); @@ -231,6 +264,8 @@ class BinaryPartImpl implements UniStoreBinaryPart public Object setObject(DynamoUser caller, String namespace, String name, Object value) throws DatabaseException, DynamoSecurityException { + if (m_ops==null) + throw new DatabaseException(BinaryPartImpl.class,"UniStoreMessages","part.deleted"); m_parent.testPermission(caller,namespace,"set.property","no.setProperty"); Object rc = null; // convert the namespace name to an ID here @@ -264,6 +299,8 @@ class BinaryPartImpl implements UniStoreBinaryPart public Object removeObject(DynamoUser caller, String namespace, String name) throws DatabaseException, DynamoSecurityException { + if (m_ops==null) + throw new DatabaseException(BinaryPartImpl.class,"UniStoreMessages","part.deleted"); m_parent.testPermission(caller,namespace,"remove.property","no.removeProperty"); Object rc = null; // convert the namespace name to an ID here @@ -291,6 +328,9 @@ class BinaryPartImpl implements UniStoreBinaryPart */ 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_parent.getMessageID(),m_part); @@ -312,6 +352,9 @@ class BinaryPartImpl implements UniStoreBinaryPart */ 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_parent.getMessageID(),m_part,nsid); @@ -342,7 +385,7 @@ class BinaryPartImpl implements UniStoreBinaryPart public long getMessageID() { - return m_parent.getMessageID(); + return (m_parent==null) ? -1L : m_parent.getMessageID(); } // end getMessageID @@ -390,6 +433,8 @@ class BinaryPartImpl implements UniStoreBinaryPart public void touchRead() throws DatabaseException { + if (m_ops==null) + throw new DatabaseException(BinaryPartImpl.class,"UniStoreMessages","part.deleted"); synchronized (this) { // touch the database, then the local values java.util.Date tmp = m_ops.touchRead(m_parent.getMessageID(),m_part); @@ -402,6 +447,22 @@ class BinaryPartImpl implements UniStoreBinaryPart } // end touchRead + public synchronized void delete(DynamoUser caller) throws DatabaseException, DynamoSecurityException + { + if (m_ops==null) + throw new DatabaseException(BinaryPartImpl.class,"UniStoreMessages","part.deleted"); + m_parent.testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"delete.part","no.deletePart"); + + // Cut this object loose from the parent. + m_parent.deletedBinaryPart(m_part,m_identity); + + // Zap it from the database. + m_ops.delete(m_parent.getMessageID(),m_part); + + baleeted(); // BALEETED! + + } // end delete + /*-------------------------------------------------------------------------------- * Implementations from interface DataItem *-------------------------------------------------------------------------------- @@ -415,6 +476,14 @@ class BinaryPartImpl implements UniStoreBinaryPart public InputStream getDataStream() throws IOException { + if (m_ops==null) + { // we've been deleted! + IOException ioe = new IOException("Part has been deleted"); + ioe.initCause(new DatabaseException(BinaryPartImpl.class,"UniStoreMessages","part.deleted")); + throw ioe; + + } // end if + try { // call through to the database to get the data stream return m_ops.getData(m_parent.getMessageID(),m_part); @@ -432,6 +501,14 @@ class BinaryPartImpl implements UniStoreBinaryPart public Blob getBlob() throws SQLException { + if (m_ops==null) + { // we've been deleted! + SQLException se = new SQLException("Part has been deleted"); + se.initCause(new DatabaseException(BinaryPartImpl.class,"UniStoreMessages","part.deleted")); + throw se; + + } // end if + try { // load the data from the database and create the Blob around it byte[] data = new byte[m_size]; @@ -461,4 +538,43 @@ class BinaryPartImpl implements UniStoreBinaryPart *-------------------------------------------------------------------------------- */ + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + void resetPartNumber(int new_num) + { + m_part = new_num; + + } // end resetPartNumber + + /** + * Called after the part has been deleted, either alone or through the entire message being deleted. This + * method nulls out the internal data of the object and posts a "part-deleted" notification.

+ * See this page for the source of the method name. + */ + synchronized void baleeted() + { + // Cut loose most of our data before we post an update event. + m_ops = null; + m_nscache = null; + m_mimetype = null; + m_size = -1; + m_filename = null; + m_nread = 0; + m_lastread = null; + m_properties.clear(); + + // Post the "deleted" notification event. + m_post.postUpdate(new MessagePartDeletedEvent(this)); + + // Cut loose the rest of our data. + m_post = null; + m_parent = null; + m_part = -1; + m_identity = null; + + } // end baleeted + } // end class BinaryPartImpl diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps.java index 50e63a5..b7b4e8b 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps.java @@ -59,4 +59,6 @@ abstract class BinaryPartOps extends OpsBase abstract int getData(long msgid, int part, byte[] here) throws IOException, DatabaseException; + abstract void delete(long msgid, int part) throws DatabaseException; + } // end class BinaryPartOps diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps_mysql.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps_mysql.java index c55e1c2..9e800af 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps_mysql.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/BinaryPartOps_mysql.java @@ -503,4 +503,57 @@ class BinaryPartOps_mysql extends BinaryPartOps } // end getData + void delete(long msgid, int part) throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + Statement stmt2 = null; + try + { // get a connection + conn = getConnection(); + + // lock the tables + stmt2 = conn.createStatement(); + stmt2.executeUpdate("LOCK TABLES us_binary WRITE, us_binary_props WRITE;"); + + // delete all entries from the main and property tables + stmt = conn.prepareStatement("DELETE FROM us_binary WHERE msgid = ? AND part = ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("DELETE FROM us_binary_prop WHERE msgid = ? AND part = ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + stmt.close(); + + // renumber all entries above that one to close the gap + stmt = conn.prepareStatement("UPDATE us_binary SET part = part - 1 WHERE msgid = ? AND part > ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("UPDATE us_binary_prop SET part = part - 1 WHERE msgid = ? AND part > ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + MySQLUtils.unlockTables(conn); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(stmt2); + SQLUtils.shutdown(conn); + + } // end finally + + } // end delete + } // end class BinaryPartOps_mysql diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps.java index b30f40a..ae1b09c 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps.java @@ -77,6 +77,8 @@ abstract class ManagerOps extends OpsBase abstract Map getMessageData(long msgid) throws DatabaseException; + abstract long createMessage(int creator, java.util.Date postdate) throws DatabaseException; + /*-------------------------------------------------------------------------------- * External operations *-------------------------------------------------------------------------------- diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps_mysql.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps_mysql.java index 925fd33..ccf0674 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps_mysql.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/ManagerOps_mysql.java @@ -116,4 +116,43 @@ public class ManagerOps_mysql extends ManagerOps } // end getMessageData + long createMessage(int creator, java.util.Date postdate) throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + Statement stmt2 = null; + try + { // get a connection + conn = getConnection(); + + // lock the base table + stmt2 = conn.createStatement(); + stmt2.executeUpdate("LOCK TABLES us_head WRITE;"); + + // create and execute the INSERT statement + stmt = conn.prepareStatement("INSERT INTO us_head (creator, posted) VALUES (?, ?);"); + stmt.setInt(1,creator); + m_utils.setDateTime(stmt,2,postdate); + stmt.executeUpdate(); + + // return the new message ID + return MySQLUtils.getLastInsertLong(conn); + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + MySQLUtils.unlockTables(conn); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(stmt2); + SQLUtils.shutdown(conn); + + } // end finally + + } // end createMessage + } // end class ManagerOps_mysql diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageImpl.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageImpl.java index e17cef6..bd4f818 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageImpl.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageImpl.java @@ -17,8 +17,10 @@ */ 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; @@ -39,6 +41,10 @@ class MessageImpl implements UniStoreMessage private static Logger logger = Logger.getLogger(MessageImpl.class); + private static final Integer NO_READS = new Integer(0); + + private static Pattern NEWLINES; + /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- @@ -92,13 +98,109 @@ class MessageImpl implements UniStoreMessage } // 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 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. @@ -106,6 +208,9 @@ class MessageImpl implements UniStoreMessage */ 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); @@ -157,6 +262,8 @@ class MessageImpl implements UniStoreMessage 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 @@ -190,6 +297,8 @@ class MessageImpl implements UniStoreMessage 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 @@ -217,6 +326,9 @@ class MessageImpl implements UniStoreMessage */ 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); @@ -238,6 +350,9 @@ class MessageImpl implements UniStoreMessage */ 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); @@ -320,7 +435,9 @@ class MessageImpl implements UniStoreMessage public DynamoUser getCreator() throws DatabaseException { - return m_users.getUser(m_creator); + if (m_ops==null) + throw new DatabaseException(MessageImpl.class,"UniStoreMessages","message.deleted"); + return (m_users==null) ? null : m_users.getUser(m_creator); } // end getCreator @@ -332,7 +449,12 @@ class MessageImpl implements UniStoreMessage public DynamoAcl getAcl() throws DatabaseException, AclNotFoundException { - return m_srm.getAcl(m_aclid); + 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 @@ -355,6 +477,8 @@ class MessageImpl implements UniStoreMessage 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; @@ -363,6 +487,8 @@ class MessageImpl implements UniStoreMessage 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; @@ -371,6 +497,8 @@ class MessageImpl implements UniStoreMessage 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) @@ -408,6 +536,8 @@ class MessageImpl implements UniStoreMessage 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) @@ -440,6 +570,8 @@ class MessageImpl implements UniStoreMessage 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) @@ -477,6 +609,8 @@ class MessageImpl implements UniStoreMessage 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) @@ -509,6 +643,8 @@ class MessageImpl implements UniStoreMessage 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++) @@ -519,6 +655,8 @@ class MessageImpl implements UniStoreMessage 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++) @@ -527,6 +665,140 @@ class MessageImpl implements UniStoreMessage } // 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 *-------------------------------------------------------------------------------- @@ -535,6 +807,8 @@ class MessageImpl implements UniStoreMessage 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) @@ -568,4 +842,127 @@ class MessageImpl implements UniStoreMessage } // 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 diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps.java index ebce3aa..0dc6a40 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps.java @@ -17,6 +17,7 @@ */ package com.silverwrist.dynamo.unistore; +import java.io.*; import java.util.*; import com.silverwrist.dynamo.db.OpsBase; import com.silverwrist.dynamo.except.*; @@ -112,6 +113,18 @@ abstract class MessageOps extends OpsBase abstract Map loadBinaryPart(long msgid, PropertyKey identity) throws DatabaseException; + abstract int createTextPart(long msgid, int nsid, String name, String mimetype, int charcount, int linecount, + String data) throws DatabaseException; + + abstract int createBinaryPart(long msgid, int nsid, String name, String mimetype, String filename, int length, + InputStream data) throws DatabaseException; + + abstract void delete(long msgid) throws DatabaseException; + + abstract Map listTextParts(long msgid) throws DatabaseException; + + abstract Map listBinaryParts(long msgid) throws DatabaseException; + /*-------------------------------------------------------------------------------- * External operations *-------------------------------------------------------------------------------- diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps_mysql.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps_mysql.java index 1911c20..a9723b2 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps_mysql.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/MessageOps_mysql.java @@ -17,6 +17,7 @@ */ package com.silverwrist.dynamo.unistore; +import java.io.*; import java.sql.*; import java.util.*; import com.silverwrist.util.*; @@ -790,4 +791,286 @@ class MessageOps_mysql extends MessageOps } // end loadBinaryPart + int createTextPart(long msgid, int nsid, String name, String mimetype, int charcount, int linecount, String data) + throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + Statement stmt2 = null; + ResultSet rs = null; + try + { // get a connection + conn = getConnection(); + + // lock the table + stmt2 = conn.createStatement(); + stmt2.executeUpdate("LOCK TABLES us_text WRITE;"); + + // start by seeing if the identity already exists + stmt = conn.prepareStatement("SELECT part FROM us_text WHERE msgid = ? AND ident_nsid = ? AND ident_name = ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,nsid); + stmt.setString(3,name); + rs = stmt.executeQuery(); + if (rs.next()) + { // identity already exists, throw exception + DatabaseException de = new DatabaseException(MessageOps_mysql.class,"UniStoreMessages","part.exists"); + de.setParameter(0,String.valueOf(msgid)); + throw de; + + } // end if + + SQLUtils.shutdown(rs); + rs = null; + SQLUtils.shutdown(stmt); + + // get the part number for this part + stmt = conn.prepareStatement("SELECT IFNULL(MAX(part),0) FROM us_text WHERE msgid = ?;"); + stmt.setLong(1,msgid); + rs = stmt.executeQuery(); + int partnum = SQLUtils.getReturnCountInt(rs,1) + 1; + + SQLUtils.shutdown(rs); + rs = null; + SQLUtils.shutdown(stmt); + + // perform the insert! + stmt = conn.prepareStatement("INSERT INTO us_text (msgid, part, ident_nsid, ident_name, mimetype, charcount, " + + "linecount, data) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"); + stmt.setLong(1,msgid); + stmt.setInt(2,partnum); + stmt.setInt(3,nsid); + stmt.setString(4,name); + stmt.setString(5,mimetype); + stmt.setInt(6,charcount); + stmt.setInt(7,linecount); + stmt.setString(8,data); + stmt.executeUpdate(); + + return partnum; + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + MySQLUtils.unlockTables(conn); + SQLUtils.shutdown(rs); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(stmt2); + SQLUtils.shutdown(conn); + + } // end finally + + } // end createTextPart + + int createBinaryPart(long msgid, int nsid, String name, String mimetype, String filename, int length, + InputStream data) throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + Statement stmt2 = null; + ResultSet rs = null; + try + { // get a connection + conn = getConnection(); + + // lock the table + stmt2 = conn.createStatement(); + stmt2.executeUpdate("LOCK TABLES us_binary WRITE;"); + + // start by seeing if the identity already exists + stmt = conn.prepareStatement("SELECT part FROM us_binary WHERE msgid = ? AND ident_nsid = ? " + + "AND ident_name = ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,nsid); + stmt.setString(3,name); + rs = stmt.executeQuery(); + if (rs.next()) + { // identity already exists, throw exception + DatabaseException de = new DatabaseException(MessageOps_mysql.class,"UniStoreMessages","part.exists"); + de.setParameter(0,String.valueOf(msgid)); + throw de; + + } // end if + + SQLUtils.shutdown(rs); + rs = null; + SQLUtils.shutdown(stmt); + + // get the part number for this part + stmt = conn.prepareStatement("SELECT IFNULL(MAX(part),0) FROM us_binary WHERE msgid = ?;"); + stmt.setLong(1,msgid); + rs = stmt.executeQuery(); + int partnum = SQLUtils.getReturnCountInt(rs,1) + 1; + + SQLUtils.shutdown(rs); + rs = null; + SQLUtils.shutdown(stmt); + + // perform the insert! + stmt = conn.prepareStatement("INSERT INTO us_binary (msgid, part, ident_nsid, ident_name, mimetype, datalen, " + + "filename, data) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"); + stmt.setLong(1,msgid); + stmt.setInt(2,partnum); + stmt.setInt(3,nsid); + stmt.setString(4,name); + stmt.setString(5,mimetype); + stmt.setInt(6,length); + stmt.setString(7,filename); + stmt.setBinaryStream(8,data,length); + stmt.executeUpdate(); + + return partnum; + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + MySQLUtils.unlockTables(conn); + SQLUtils.shutdown(rs); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(stmt2); + SQLUtils.shutdown(conn); + + } // end finally + + } // end createBinaryPart + + void delete(long msgid) throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + Statement stmt2 = null; + try + { // get a connection + conn = getConnection(); + + // lock the tables + stmt2 = conn.createStatement(); + stmt2.executeUpdate("LOCK TABLES us_head WRITE, us_prop WRITE, us_text WRITE, us_text_prop WRITE, " + + "us_binary WRITE, us_binary_prop WRITE;"); + + // delete all data from the tables + stmt = conn.prepareStatement("DELETE FROM us_head WHERE msgid = ?;"); + stmt.setLong(1,msgid); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("DELETE FROM us_prop WHERE msgid = ?;"); + stmt.setLong(1,msgid); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("DELETE FROM us_text WHERE msgid = ?;"); + stmt.setLong(1,msgid); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("DELETE FROM us_text_prop WHERE msgid = ?;"); + stmt.setLong(1,msgid); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("DELETE FROM us_binary WHERE msgid = ?;"); + stmt.setLong(1,msgid); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("DELETE FROM us_binary_prop WHERE msgid = ?;"); + stmt.setLong(1,msgid); + stmt.executeUpdate(); + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + MySQLUtils.unlockTables(conn); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(stmt2); + SQLUtils.shutdown(conn); + + } // end finally + + } // end delete + + Map listTextParts(long msgid) throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + ResultSet rs = null; + try + { // get a connection + conn = getConnection(); + + // run the statement that lists the parts + stmt = conn.prepareStatement("SELECT part, ident_nsid, ident_name FROM us_text WHERE msgid = ? ORDER BY part;"); + stmt.setLong(1,msgid); + rs = stmt.executeQuery(); + + // create and return the listing + TreeMap rc = new TreeMap(); + while (rs.next()) + rc.put(new Integer(rs.getInt(1)),new PropertyKey(rs.getInt(2),rs.getString(3))); + return rc; + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + SQLUtils.shutdown(rs); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(conn); + + } // end finally + + } // end listTextParts + + Map listBinaryParts(long msgid) throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + ResultSet rs = null; + try + { // get a connection + conn = getConnection(); + + // run the statement that lists the parts + stmt = conn.prepareStatement("SELECT part, ident_nsid, ident_name FROM us_binary WHERE msgid = ? " + + "ORDER BY part;"); + stmt.setLong(1,msgid); + rs = stmt.executeQuery(); + + // create and return the listing + TreeMap rc = new TreeMap(); + while (rs.next()) + rc.put(new Integer(rs.getInt(1)),new PropertyKey(rs.getInt(2),rs.getString(3))); + return rc; + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + SQLUtils.shutdown(rs); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(conn); + + } // end finally + + } // end listTextParts + } // end class MessageOps_mysql diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartImpl.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartImpl.java index 49cce84..20a5e78 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartImpl.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartImpl.java @@ -21,6 +21,7 @@ import java.io.*; import java.lang.ref.*; import java.util.*; import org.apache.commons.collections.*; +import com.silverwrist.dynamo.Namespaces; import com.silverwrist.dynamo.db.NamespaceCache; import com.silverwrist.dynamo.event.*; import com.silverwrist.dynamo.except.*; @@ -49,7 +50,7 @@ class TextPartImpl implements UniStoreTextPart private SoftReference m_text = null; /*-------------------------------------------------------------------------------- - * Constructor + * Constructors *-------------------------------------------------------------------------------- */ @@ -76,6 +77,36 @@ class TextPartImpl implements UniStoreTextPart } // end constructor + TextPartImpl(PostDynamicUpdate post, MessageImpl parent, int part, QualifiedNameKey identity) + { + m_ops = null; + m_nscache = null; + m_post = post; + m_parent = parent; + m_part = part; + m_identity = identity; + m_mimetype = null; + m_size = -1; + m_linecount = -1; + m_nread = 0; + m_lastread = null; + m_properties = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Overrides from class Object + *-------------------------------------------------------------------------------- + */ + + public String toString() + { + if (m_ops==null) + return "(deleted text part)"; + return "message " + m_parent.getMessageID() + ", text part " + m_identity.toString(); + + } // end toString + /*-------------------------------------------------------------------------------- * Implementations from interface ObjectProvider *-------------------------------------------------------------------------------- @@ -90,6 +121,8 @@ class TextPartImpl implements UniStoreTextPart */ 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); @@ -141,6 +174,8 @@ class TextPartImpl implements UniStoreTextPart public Object setObject(DynamoUser caller, String namespace, String name, Object value) throws DatabaseException, DynamoSecurityException { + if (m_ops==null) + throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted"); m_parent.testPermission(caller,namespace,"set.property","no.setProperty"); Object rc = null; // convert the namespace name to an ID here @@ -174,6 +209,8 @@ class TextPartImpl implements UniStoreTextPart public Object removeObject(DynamoUser caller, String namespace, String name) throws DatabaseException, DynamoSecurityException { + if (m_ops==null) + throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted"); m_parent.testPermission(caller,namespace,"remove.property","no.removeProperty"); Object rc = null; // convert the namespace name to an ID here @@ -201,6 +238,9 @@ class TextPartImpl implements UniStoreTextPart */ 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_parent.getMessageID(),m_part); @@ -222,6 +262,9 @@ class TextPartImpl implements UniStoreTextPart */ 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_parent.getMessageID(),m_part,nsid); @@ -252,7 +295,7 @@ class TextPartImpl implements UniStoreTextPart public long getMessageID() { - return m_parent.getMessageID(); + return (m_parent==null) ? -1L : m_parent.getMessageID(); } // end getMessageID @@ -300,6 +343,8 @@ class TextPartImpl implements UniStoreTextPart public void touchRead() throws DatabaseException { + if (m_ops==null) + throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted"); synchronized (this) { // touch the database, then the local values java.util.Date tmp = m_ops.touchRead(m_parent.getMessageID(),m_part); @@ -312,6 +357,22 @@ class TextPartImpl implements UniStoreTextPart } // end touchRead + public synchronized void delete(DynamoUser caller) throws DatabaseException, DynamoSecurityException + { + if (m_ops==null) + throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted"); + m_parent.testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"delete.part","no.deletePart"); + + // Cut this object loose from the parent. + m_parent.deletedTextPart(m_part,m_identity); + + // Zap it from the database. + m_ops.delete(m_parent.getMessageID(),m_part); + + baleeted(); // BALEETED! + + } // end delete + /*-------------------------------------------------------------------------------- * Implementations from interface UniStoreTextPart *-------------------------------------------------------------------------------- @@ -325,6 +386,8 @@ class TextPartImpl implements UniStoreTextPart public synchronized String getText() throws DatabaseException { + if (m_ops==null) + throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted"); String rc = null; if (m_text!=null) rc = (String)(m_text.get()); @@ -341,8 +404,58 @@ class TextPartImpl implements UniStoreTextPart public Reader getTextAsReader() throws DatabaseException { + if (m_ops==null) + throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted"); return new StringReader(this.getText()); } // end getTextAsReader + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + void precacheText(String txt) + { + m_text = new SoftReference(txt); + + } // end txt + + void resetPartNumber(int new_num) + { + m_part = new_num; + + } // end resetPartNumber + + /** + * Called after the part has been deleted, either alone or through the entire message being deleted. This + * method nulls out the internal data of the object and posts a "part-deleted" notification.

+ * See this page for the source of the method name. + */ + synchronized void baleeted() + { + // Cut loose most of our data before we post an update event. + m_ops = null; + m_nscache = null; + m_mimetype = null; + m_size = -1; + m_linecount = -1; + m_nread = 0; + m_lastread = null; + m_properties.clear(); + if (m_text!=null) + m_text.clear(); + m_text = null; + + // Post the "deleted" notification event. + m_post.postUpdate(new MessagePartDeletedEvent(this)); + + // Now cut loose the rest of our data. + m_post = null; + m_parent = null; + m_part = -1; + m_identity = null; + + } // end baleeted + } // end class TextPartImpl diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps.java index 4983f03..ff810ff 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps.java @@ -55,4 +55,6 @@ abstract class TextPartOps extends OpsBase abstract String getText(long msgid, int part) throws DatabaseException; + abstract void delete(long msgid, int part) throws DatabaseException; + } // end class TextPartOps diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps_mysql.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps_mysql.java index 46aacd2..cefc6ab 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps_mysql.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/TextPartOps_mysql.java @@ -455,4 +455,57 @@ class TextPartOps_mysql extends TextPartOps } // end getText + void delete(long msgid, int part) throws DatabaseException + { + Connection conn = null; + PreparedStatement stmt = null; + Statement stmt2 = null; + try + { // get a connection + conn = getConnection(); + + // lock the tables + stmt2 = conn.createStatement(); + stmt2.executeUpdate("LOCK TABLES us_text WRITE, us_text_props WRITE;"); + + // delete all entries from the main and property tables + stmt = conn.prepareStatement("DELETE FROM us_text WHERE msgid = ? AND part = ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("DELETE FROM us_text_prop WHERE msgid = ? AND part = ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + stmt.close(); + + // renumber all entries above that one to close the gap + stmt = conn.prepareStatement("UPDATE us_text SET part = part - 1 WHERE msgid = ? AND part > ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + stmt.close(); + stmt = conn.prepareStatement("UPDATE us_text_prop SET part = part - 1 WHERE msgid = ? AND part > ?;"); + stmt.setLong(1,msgid); + stmt.setInt(2,part); + stmt.executeUpdate(); + + } // end try + catch (SQLException e) + { // translate to a general DatabaseException + throw generalException(e); + + } // end catch + finally + { // shut everything down + MySQLUtils.unlockTables(conn); + SQLUtils.shutdown(stmt); + SQLUtils.shutdown(stmt2); + SQLUtils.shutdown(conn); + + } // end finally + + } // end delete + } // end class TextPartOps_mysql diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreManager.java b/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreManager.java index da766e3..2a7da7d 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreManager.java +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreManager.java @@ -24,6 +24,7 @@ import com.silverwrist.util.*; import com.silverwrist.util.xml.*; 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; @@ -38,6 +39,9 @@ public class UniStoreManager implements NamedObject, ComponentInitialize, Compon 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; @@ -190,9 +194,46 @@ public class UniStoreManager implements NamedObject, ComponentInitialize, Compon 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 diff --git a/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreMessages.properties b/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreMessages.properties index e540dfe..3945167 100644 --- a/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreMessages.properties +++ b/src/dynamo-framework/com/silverwrist/dynamo/unistore/UniStoreMessages.properties @@ -26,3 +26,9 @@ bad.loadText.part=Unable to find text part #{0} of message #{1} in the Universal bad.loadText.id=Unable to find text part with ID {0}::{1} in message #{2} in the Universal Message Store. bad.loadBinary.part=Unable to find binary part #{0} of message #{1} in the Universal Message Store. bad.loadBinary.id=Unable to find binary part with ID {0}::{1} in message #{2} in the Universal Message Store. +no.createPart=You are not authorized to create a new part in message #{0} in the Universal Message Store. +part.exists=A part with this identity already exists in message #{0}. +no.deletePart=You are not authorized to delete a part from message #{0} in the Universal Message Store. +part.deleted=This part has been deleted. +no.deleteMessage=You are not authorized to delete message #{0} in the Universal Message Store. +message.deleted=This message has been deleted.