polling and hibernate conflict



Hey,

I've spent today trying to track down all the issues that persistence
introduced.  I think there's a pretty big conflict with the polling
system.  One major problem I ran into first is that the parent
relationship was not being persisted, so instantiated objects would have
null parents, and thus no changes on them would propagate.  I solved
this by removing the idea of parents, and having objects notify Yarrr of
changes, and then Yarrr pushes the changes to itself and all the active
topics.

However, that wasn't enough; the ClientPollEntry object keeps a
reference to each object around, but this is no longer updated
automatically, since there is no longer one Java object per item.

I'm attaching a patch with my current work.  Alex, your help would be
appreciated if we can go over what needs to happen with the polling
system tomorrow.

I am really wishing now I'd kept working on the branch...sorry about
this.




Index: hibernate.properties
===================================================================
RCS file: /cvs/gnome/yarrr/hibernate.properties,v
retrieving revision 1.2
diff -u -r1.2 hibernate.properties
--- hibernate.properties	22 Apr 2005 18:50:50 -0000	1.2
+++ hibernate.properties	26 Apr 2005 03:45:15 -0000
@@ -2,4 +2,4 @@
 hibernate.c3p0.max_size=20
 hibernate.c3p0.timeout=1800
 hibernate.c3p0.max_statements=50
-hibernate.show_sql=true
\ No newline at end of file
+hibernate.show_sql=false
\ No newline at end of file
Index: .externalToolBuilders/Deploy To Local Tomcat Server.launch
===================================================================
RCS file: /cvs/gnome/yarrr/.externalToolBuilders/Deploy To Local Tomcat Server.launch,v
retrieving revision 1.7
diff -u -r1.7 Deploy To Local Tomcat Server.launch
--- .externalToolBuilders/Deploy To Local Tomcat Server.launch	14 Apr 2005 17:21:03 -0000	1.7
+++ .externalToolBuilders/Deploy To Local Tomcat Server.launch	26 Apr 2005 03:45:15 -0000
@@ -12,15 +12,15 @@
 <stringAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_TARGETS" value="package-servlet,undeploy,deploy,"/>
 <stringAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTY_FILES" value="${workspace_loc:/yarrr/web/tomcat-deploy.properties},"/>
 <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="yarrr"/>
+<mapAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTIES">
+<mapEntry key="eclipse.running" value="true"/>
+</mapAttribute>
 <listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.5.0&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.ant.ui.classpathentry.antHome&quot;&gt;&#10;&lt;memento default=&quot;true&quot;/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/yarrr/lib/catalina-ant.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
 <listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;runtimeClasspathEntry id=&quot;org.eclipse.ant.ui.classpathentry.extraClasspathEntries&quot;&gt;&#10;&lt;memento/&gt;&#10;&lt;/runtimeClasspathEntry&gt;&#10;"/>
 </listAttribute>
-<mapAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTIES">
-<mapEntry key="eclipse.running" value="true"/>
-</mapAttribute>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="yarrr"/>
 <stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc}/${project_path}/build.xml"/>
 </launchConfiguration>
Index: src/org/gnome/yarrr/ActiveTopic.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/ActiveTopic.java,v
retrieving revision 1.43
diff -u -r1.43 ActiveTopic.java
--- src/org/gnome/yarrr/ActiveTopic.java	25 Apr 2005 13:58:10 -0000	1.43
+++ src/org/gnome/yarrr/ActiveTopic.java	26 Apr 2005 03:45:15 -0000
@@ -23,26 +23,26 @@
 	private ArrayList /* Discussion */discussions;
 
 	public ActiveTopic(Yarrr yarrr, long topicId) {
-		super(yarrr, yarrr, false);
+		super(yarrr);
 		this.topicId = topicId;
-		
-		ReferencableObjectRegistry.register(this);
 
 		discussions = new ArrayList();
 
 		this.defaultDiscussion = new Discussion(this, yarrr.getTheCapn(), "Default Discussion");
 		this.defaultDiscussion.setDefault(true);
+        ReferencableObjectRegistry.register(this);
 	}
     
     public Topic getTopic() {
         return Topic.load(new Long(this.topicId));
     }
 
-	public synchronized ClosedComment closeComment(LiveComment livecomment, Person closer) {
+	public synchronized ClosedComment closeComment(Discussion discussion, LiveComment livecomment, Person closer) {
 		Set allAuthors = livecomment.getAllAuthors();
-		Chat.Message[] discussion = livecomment.getRelatedMessages();
-		ClosedComment comment = new ClosedComment(this, closer, livecomment.getText(), allAuthors, discussion);
+		Chat.Message[] msgs = discussion.getChat().getMessagesSince(livecomment.getStartTime());
+		ClosedComment comment = new ClosedComment(this.getTopic(), closer, livecomment.getText(), allAuthors, msgs);
 		this.getTopic().addClosedComment(comment);
+        discussion.removeComment(livecomment, closer);
 		this.signalChanged();
 		return comment;
 	}
@@ -67,8 +67,8 @@
     }
     
 	public synchronized Statement addStatement(Person author, String content) {
-        Statement statement = new Statement(this, author, content);
-        
+        Statement statement = new Statement(this.getTopic(), author, content);
+        statement.setChangeListener(this.getYarrr());
         this.getTopic().addStatement(statement);
         this.subscribeStatement(statement, author);
 		this.signalChanged();
Index: src/org/gnome/yarrr/Chat.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/Chat.java,v
retrieving revision 1.17
diff -u -r1.17 Chat.java
--- src/org/gnome/yarrr/Chat.java	24 Apr 2005 22:44:05 -0000	1.17
+++ src/org/gnome/yarrr/Chat.java	26 Apr 2005 03:45:15 -0000
@@ -27,7 +27,6 @@
 	static Log logger = LogFactory.getLog(Chat.class);
 	
 	private List /* Message */ messages;
-	private ActiveTopic topic;
 	
 	static public class MessageFetchResult implements XmlRpcMarshaller {
 		private static final long serialVersionUID = 1L;
@@ -82,9 +81,9 @@
 		}
 	}
 	
-	public Chat(ToplevelReferencableObject parent) {
-		super(parent, true);
+	public Chat() {
 		this.messages = new ArrayList();
+        ReferencableObjectRegistry.register(this);
 	}
 	
 	synchronized public int addMessage(Person author, String contents) {
Index: src/org/gnome/yarrr/ClientPoll.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/ClientPoll.java,v
retrieving revision 1.4
diff -u -r1.4 ClientPoll.java
--- src/org/gnome/yarrr/ClientPoll.java	25 Apr 2005 18:06:57 -0000	1.4
+++ src/org/gnome/yarrr/ClientPoll.java	26 Apr 2005 03:45:15 -0000
@@ -53,6 +53,23 @@
 		pollObject = pollServer.newPollObjectFor(address);
 		touch();
 	}
+    
+    public String toString() {
+        StringBuffer ret = new StringBuffer("[");
+        ret.append(super.toString());
+        ret.append("] (");
+        Iterator it = this.entries.iterator();
+        while (it.hasNext()) {
+            ClientPollEntry e = (ClientPollEntry) it.next();
+            ret.append(e.id);
+            ret.append(", ");
+        }
+        ret.append(") clientid: ");
+        ret.append(clientId);
+        ret.append(" person: ");
+        ret.append(person.getReferenceId());
+        return ret.toString();
+    }
 	
 	public String getClientId() {
 		return clientId;
@@ -127,7 +144,7 @@
 	}
 
 	public boolean containsObject(ReferencableObject obj) {
-		return lookupEntry(obj) != null;
+		return lookupEntry(ReferencableObjectRegistry.getReference(obj)) != null;
 	}
 
 	public String getPollUrl() {
Index: src/org/gnome/yarrr/ClosedComment.hbm.xml
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/ClosedComment.hbm.xml,v
retrieving revision 1.6
diff -u -r1.6 ClosedComment.hbm.xml
--- src/org/gnome/yarrr/ClosedComment.hbm.xml	25 Apr 2005 15:40:17 -0000	1.6
+++ src/org/gnome/yarrr/ClosedComment.hbm.xml	26 Apr 2005 03:45:15 -0000
@@ -15,12 +15,12 @@
 		<property name="closeDate" access="field"/>
 		<property name="messages" access="field" type="serializable" length="32000"/>
 		<set name="proponents" access="field" cascade="all">
-			<key column="closedCommentProponentsId"/>
-			<one-to-many class="Person"/>
+			<key column="proponentid"/>
+			<many-to-many column="proponentpersonid" class="Person"/>
 		</set>
 		<set name="authors" access="field" cascade="all">
-			<key column="closedCommentAuthorsId"/>
-			<one-to-many class="Person"/>
+			<key column="authorid"/>
+			<many-to-many column="authorpersonid" class="Person"/>
 		</set>
 	</class>
 </hibernate-mapping>
Index: src/org/gnome/yarrr/ClosedComment.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/ClosedComment.java,v
retrieving revision 1.19
diff -u -r1.19 ClosedComment.java
--- src/org/gnome/yarrr/ClosedComment.java	25 Apr 2005 15:40:17 -0000	1.19
+++ src/org/gnome/yarrr/ClosedComment.java	26 Apr 2005 03:45:15 -0000
@@ -29,9 +29,8 @@
     
     ClosedComment() {}
     
-    public ClosedComment(ActiveTopic topic, Person closer, String content, Set /* Person */ authors, Chat.Message[] messages) {
-		super(topic, false);
-        this.topic = topic.getTopic();
+    public ClosedComment(Topic topic, Person closer, String content, Set /* Person */ authors, Chat.Message[] messages) {
+        this.topic = topic;
 		this.content = new YarrrMarkup(content);
 		this.closer = closer;
 		this.closeDate = new Date();
@@ -78,6 +77,10 @@
 
     public Person getCloser() {
         return this.closer;
+    }
+
+    public Set getAuthors() {
+        return this.authors;
     }
 }
 
Index: src/org/gnome/yarrr/Discussion.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/Discussion.java,v
retrieving revision 1.20
diff -u -r1.20 Discussion.java
--- src/org/gnome/yarrr/Discussion.java	25 Apr 2005 13:00:18 -0000	1.20
+++ src/org/gnome/yarrr/Discussion.java	26 Apr 2005 03:45:15 -0000
@@ -21,7 +21,6 @@
 
 	static Log logger = LogFactory.getLog(Discussion.class);
 	
-	private ActiveTopic topic;
 	private Person.Ref creator;
 	private boolean isDefault;
 	private String title;
@@ -31,32 +30,28 @@
 	private Chat chat;
 	
 	public Discussion(ActiveTopic topic, Person creator, String title) {
-		super(topic, true);
 		this.creator = creator.getRef();
 		this.title = title;
 		this.isDefault = false;
 		this.creationDate = new Date();
 		this.comments = new Vector();
         this.whiteboards = new Vector();
-		this.topic = topic;
-		this.chat = new Chat(topic);
+		this.chat = new Chat();
+        ReferencableObjectRegistry.register(this);
 	}
 	
     /* Comment-related methods: */
 	public synchronized LiveComment openComment(Person author) {
-		LiveComment comment = new LiveComment(this, author); 
+		LiveComment comment = new LiveComment(this, author);
 		this.comments.add(comment);
 		signalChanged();
 		return comment;
 	}
 	
-	public synchronized ClosedComment closeComment(LiveComment comment, Person closer) {
+	public synchronized void removeComment(LiveComment comment, Person closer) {
 		assert(this.comments.contains(comment));
-		
-		ClosedComment ret = topic.closeComment(comment, closer);
 		this.comments.remove(comment);
 		signalChanged();
-        return ret;
 	}
 	
 	protected synchronized void deleteComment(LiveComment comment) {
@@ -71,7 +66,7 @@
     
     /* Whiteboard-related methods: */
     public synchronized Whiteboard createWhiteboard(Person creator) {
-        Whiteboard w = new Whiteboard(this, creator, 640, 480);
+        Whiteboard w = new Whiteboard(creator, 640, 480);
         this.whiteboards.add(w);
         this.signalChanged();        
         return w;
@@ -79,7 +74,7 @@
     
     public synchronized Whiteboard uploadFile(Person creator, byte[] data, String contentType) throws IOException {
         BufferedImage image = Whiteboard.generateImageFromFile(data, contentType);
-        Whiteboard w = new Whiteboard(this, creator, image);
+        Whiteboard w = new Whiteboard(creator, image);
         this.whiteboards.add(w);
         this.signalChanged();        
         return w;        
@@ -116,9 +111,11 @@
 		this.title = title;
 		this.signalChanged();
 	}
+    
 	public synchronized boolean isDefault() {
 		return isDefault;
 	}
+    
 	public synchronized void setDefault(boolean isDefault) {
 		this.isDefault = isDefault;
 	}
Index: src/org/gnome/yarrr/HibernateUtil.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/HibernateUtil.java,v
retrieving revision 1.3
diff -u -r1.3 HibernateUtil.java
--- src/org/gnome/yarrr/HibernateUtil.java	24 Apr 2005 22:44:05 -0000	1.3
+++ src/org/gnome/yarrr/HibernateUtil.java	26 Apr 2005 03:45:15 -0000
@@ -18,7 +18,7 @@
 	private static ThreadLocal threadTransaction = null;
     
     static Log logger = LogFactory.getLog(HibernateUtil.class);
-	
+    
 	public static boolean initialized() {
 		return sessionFactory != null;
 	}
@@ -77,7 +77,6 @@
 		if (s == null) {
 			s = sessionFactory.openSession();
 			threadSession.set(s);
-            logger.info("opened new Hibernate session " + s);
 		}
 		return s;
 	}
@@ -102,11 +101,6 @@
 		if (s != null && s.isOpen()) {	
 			// If there is an uncommited/rollbacked transaction open, try to roll it back
 			Transaction tx = (Transaction) threadTransaction.get();
-            if (tx != null) {
-                logger.info("Hibernate transaction \"" + tx + "\": committed: " + tx.wasCommitted() + " rolledback: " + tx.wasRolledBack());
-            } else {
-                logger.info("No hibernate transaction");
-            }
 			threadTransaction.set(null);
 			try {
 				if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
@@ -132,7 +126,6 @@
 	public static void commitTransaction() throws HibernateException {
 		Transaction tx = (Transaction) threadTransaction.get();
 		try {
-            logger.info("Hibernate transaction \"" + tx + "\": committed: " + tx.wasCommitted() + " rolledback: " + tx.wasRolledBack());
 			if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
 				tx.commit();
 			}
Index: src/org/gnome/yarrr/LiveComment.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/LiveComment.java,v
retrieving revision 1.25
diff -u -r1.25 LiveComment.java
--- src/org/gnome/yarrr/LiveComment.java	25 Apr 2005 13:12:33 -0000	1.25
+++ src/org/gnome/yarrr/LiveComment.java	26 Apr 2005 03:45:15 -0000
@@ -58,8 +58,7 @@
 	static class TooOldVersionException extends Exception {
 		private static final long serialVersionUID = 1L;
 	}
-	
-	private Discussion discussion;
+
 	private String closer;
 	private Date startTime;
 	private Person.Ref opener;
@@ -70,14 +69,13 @@
 	LinkedList oldVersions;
 	
 	public LiveComment(Discussion discussion, Person opener) {
-		super(discussion.getParent(), true);
 		this.oldVersions = new LinkedList();
 		this.startTime = new Date();
 		this.contents = "";
 		this.authors = new HashSet();
 		this.struckAuthors = new HashSet();
-		this.discussion = discussion;
 		this.opener = opener.getRef();
+        ReferencableObjectRegistry.register(this);
 	}	
 	
 	synchronized void saveOldVersion() {
@@ -167,15 +165,7 @@
     public boolean isStruckAuthor(Person person) {
         return struckAuthors.contains(person.getRef());
     }
-    
-	synchronized public Chat.Message[] getRelatedMessages() {
-		return discussion.getChat().getMessagesSince(startTime);
-	}
-	
-	public Discussion getDiscussion() {
-		return discussion;
-	}
-	
+
 	/**
 	 * Update the text of the comment.
 	 * 
@@ -248,5 +238,9 @@
     
     public synchronized void setContents(String contents) {
         this.contents = contents;
+    }
+
+    public Date getStartTime() {
+        return this.startTime;
     }
 }
Index: src/org/gnome/yarrr/Person.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/Person.java,v
retrieving revision 1.11
diff -u -r1.11 Person.java
--- src/org/gnome/yarrr/Person.java	25 Apr 2005 13:00:18 -0000	1.11
+++ src/org/gnome/yarrr/Person.java	26 Apr 2005 03:45:15 -0000
@@ -28,7 +28,6 @@
     Person() {}
 
 	public Person(String name) {
-		super(null, false);
 		this.name = name;
         HibernateUtil.getSession().save(this);
 	}
Index: src/org/gnome/yarrr/ReferencableObject.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/ReferencableObject.java,v
retrieving revision 1.13
diff -u -r1.13 ReferencableObject.java
--- src/org/gnome/yarrr/ReferencableObject.java	25 Apr 2005 10:19:25 -0000	1.13
+++ src/org/gnome/yarrr/ReferencableObject.java	26 Apr 2005 03:45:15 -0000
@@ -8,10 +8,8 @@
 import java.util.Set;
 import java.util.Vector;
 
-import org.gnome.yarrr.ReferencableObjectRegistry.UnreferencedObjectException;
-import org.gnome.yarrr.xmlrpc.XmlRpcDemarshaller;
-import org.gnome.yarrr.xmlrpc.XmlRpcHandler;
-import org.gnome.yarrr.xmlrpc.XmlRpcMarshaller;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.gnome.yarrr.xmlrpc.XmlRpcSerializable;
 
 /**
@@ -24,22 +22,22 @@
  * 
  * Has an integer version; you may want to look at #ActiveTopicObject which overrides incVersion
  */
-public abstract class ReferencableObject implements XmlRpcDemarshaller, XmlRpcMarshaller, XmlRpcSerializable {
-	protected ToplevelReferencableObject parent;
+public abstract class ReferencableObject implements XmlRpcSerializable {
 
 	protected long referenceId;
 	protected int version;
-
-	protected ReferencableObject(ToplevelReferencableObject parent, boolean register) {
-		this.parent = parent;
-        this.referenceId = -1;
-		if (register)
-			ReferencableObjectRegistry.register(this);
-	}
+    private ReferencableObjectChangeListener changeListener;
+    
+    public interface ReferencableObjectChangeListener {
+        public void objectChanged(ReferencableObject obj);
+    }
+    
+    static Log logger = LogFactory.getLog(ReferencableObject.class);
+    
 	protected ReferencableObject() {
-		this(null, true);
+        this.referenceId = -1;
 	}
-	
+
 	public long getReferenceId() {
 		return this.referenceId;
 	}
@@ -64,35 +62,11 @@
 	 */
 	public int signalChanged() {
 		int v = this.version++;
-		if (parent != null)
-			parent.emitSignalChanged(this);
+        logger.debug("Incrementing version for \"" + this + "\" to " + v);
+		if (this.changeListener != null)
+            this.changeListener.objectChanged(this);
 		return v;
 	}
-	
-	static public Object xmlRpcDemarshal(Object xmlRpcObject, XmlRpcHandler handler) throws DemarshallingException {
-		try {
-			YarrrXmlRpcMethods methods = (YarrrXmlRpcMethods) handler;
-			String objref = (String) xmlRpcObject;
-			Object ret = ReferencableObjectRegistry.lookup(objref);
-			if (ret == null)
-				throw new DemarshallingException("Unreferenced object "
-						+ objref);
-			return ret;
-		} catch (UnreferencedObjectException e) {
-			throw new XmlRpcDemarshaller.DemarshallingException(e);
-		}
-	}
-
-	static public Class xmlRpcMarshalledType = String.class;
-
-	public Object xmlRpcMarshal(XmlRpcHandler handler) throws MarshallingException {
-		YarrrXmlRpcMethods methods = (YarrrXmlRpcMethods) handler;
-		try {
-			return ReferencableObjectRegistry.getReference(this);
-		} catch (UnreferencedObjectException e) {
-			throw new XmlRpcMarshaller.MarshallingException(e);
-		}
-	}
 
 	public synchronized void xmlRpcSerialize(Map table) {
 		table.put("id", ReferencableObjectRegistry.getReference(this));
@@ -109,8 +83,8 @@
 		}
 		return ret;
 	}
-	
-	public ToplevelReferencableObject getParent() {
-		return parent;
-	}
+
+    public void setChangeListener(ReferencableObjectChangeListener listener) {
+        this.changeListener = listener;
+    }
 }
Index: src/org/gnome/yarrr/ReferencableObjectRegistry.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/ReferencableObjectRegistry.java,v
retrieving revision 1.9
diff -u -r1.9 ReferencableObjectRegistry.java
--- src/org/gnome/yarrr/ReferencableObjectRegistry.java	24 Apr 2005 17:05:18 -0000	1.9
+++ src/org/gnome/yarrr/ReferencableObjectRegistry.java	26 Apr 2005 03:45:15 -0000
@@ -27,7 +27,10 @@
         long id;
     }
     
-	static Log logger = LogFactory.getLog(ReferencableObjectRegistry.class);
+	private static Log logger = LogFactory.getLog(ReferencableObjectRegistry.class);
+    
+    private static ReferencableObject.ReferencableObjectChangeListener listener;
+    
 	/**
 	 * @author walters
 	 */
@@ -51,6 +54,10 @@
 
 	private static AtomicLong idseq = new AtomicLong();
     
+    public static void setListener(ReferencableObject.ReferencableObjectChangeListener listener) {
+        ReferencableObjectRegistry.listener = listener;
+    }
+    
     public static void register(Class klass) {
         if (findLoadMethod(klass) == null) {
             throw new RuntimeException("Class \"" + klass.toString() + "\" does not declare a load method");   
@@ -63,6 +70,7 @@
         if (persistentClasses.contains(klass)) {
             return;
         }
+        obj.setChangeListener(listener);
         
 		long newid = idseq.addAndGet(1);
 		obj.setReferenceId(newid);
@@ -97,15 +105,20 @@
         }
 	}
 
-	public static ReferencableObject lookup(String reference) throws UnreferencedObjectException {
-		logger.debug("Looking up object " + reference);
-		Reference ref = parseReference(reference);
-		if (persistentClasses.contains(ref.klass)) {
-		    return lookupObjectViaClass(ref.klass, ref.id);
+    public static ReferencableObject lookup(String reference) throws UnreferencedObjectException {
+        logger.debug("Looking up object " + reference);
+        Reference ref = parseReference(reference);
+        ReferencableObject ret;
+        if (persistentClasses.contains(ref.klass)) {
+            ret = lookupObjectViaClass(ref.klass, ref.id);
+            if (listener != null)
+                ret.setChangeListener(listener);
         } else {
-            return (ReferencableObject) objectRegistry.get(new Long(ref.id));
+            ret = (ReferencableObject) objectRegistry.get(new Long(ref.id));
         }
-	}
+        
+        return ret;
+    }
     
     private static Method findLoadMethod(Class klass) {
          try {
Index: src/org/gnome/yarrr/Statement.hbm.xml
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/Statement.hbm.xml,v
retrieving revision 1.5
diff -u -r1.5 Statement.hbm.xml
--- src/org/gnome/yarrr/Statement.hbm.xml	25 Apr 2005 13:58:10 -0000	1.5
+++ src/org/gnome/yarrr/Statement.hbm.xml	26 Apr 2005 03:45:15 -0000
@@ -6,15 +6,15 @@
 	package="org.gnome.yarrr">
 	
 	<class name="Statement">
-		<id name="referenceId" column="statementReferenceId">
+		<id name="referenceId">
 			<generator class="native"/>
 		</id>
 		<many-to-one name="topic" access="field" column="topicId" not-null="true"/>
 		<property name="content" access="field" not-null="true"/>
 		<many-to-one name="author" access="field" class="Person" not-null="true"/>
 		<set name="subscribers" access="field" cascade="all">
-			<key column="statementReferenceId"/>
-			<one-to-many class="Person"/>
+			<key column="statementid"/>
+			<many-to-many column="subscriberid" class="Person"/>
 		</set>
 	</class>
 </hibernate-mapping>
Index: src/org/gnome/yarrr/Statement.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/Statement.java,v
retrieving revision 1.10
diff -u -r1.10 Statement.java
--- src/org/gnome/yarrr/Statement.java	25 Apr 2005 15:40:17 -0000	1.10
+++ src/org/gnome/yarrr/Statement.java	26 Apr 2005 03:45:15 -0000
@@ -7,12 +7,10 @@
 import java.util.Map;
 import java.util.Set;
 
-import org.gnome.yarrr.xmlrpc.XmlRpcMarshaller;
-
 /**
  * @author marco
  */
-public class Statement extends ReferencableObject implements XmlRpcMarshaller {
+public class Statement extends ReferencableObject {
     private Topic topic;
 	private Person author;
 	private String content;
@@ -20,9 +18,8 @@
     
     Statement(){}
 	
-	public Statement(ActiveTopic topic, Person author, String content) {
-		super(topic, false);
-        this.topic = topic.getTopic();
+	public Statement(Topic topic, Person author, String content) {
+        this.topic = topic;
 		this.author = author;
 		this.content = content;
 		this.subscribers = new HashSet();
@@ -65,4 +62,8 @@
 		table.put("content", content);
 		table.put("subscribers", personSetToVector(subscribers));
 	}
+
+    public Person getAuthor() {
+        return this.author;
+    }
 }
Index: src/org/gnome/yarrr/ToplevelReferencableObject.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/ToplevelReferencableObject.java,v
retrieving revision 1.6
diff -u -r1.6 ToplevelReferencableObject.java
--- src/org/gnome/yarrr/ToplevelReferencableObject.java	21 Apr 2005 09:39:51 -0000	1.6
+++ src/org/gnome/yarrr/ToplevelReferencableObject.java	26 Apr 2005 03:45:15 -0000
@@ -28,11 +28,10 @@
 	 */
 	protected Yarrr yarrr;
 
-	private Map clientPolls;
+	private Map /* String,ClientPoll */ clientPolls;
 	private static Log logger = LogFactory.getLog(ToplevelReferencableObject.class);
 	
-	public ToplevelReferencableObject(Yarrr yarrr, ToplevelReferencableObject parent, boolean register) {
-		super(parent, register);
+	public ToplevelReferencableObject(Yarrr yarrr) {
 		// Can't pass "this" as argument to super in Yarrr constructor:
 		if (yarrr == null)
 			this.yarrr = (Yarrr) this;
@@ -72,21 +71,21 @@
 		return poll.getChanges();
 	}
 
-	public int signalChanged() {
-		int v = this.version++;
-		this.emitSignalChanged(this);
-		return v;
-	}
-	
 	public synchronized void emitSignalChanged(ReferencableObject obj) {
+        
+        if (logger.isDebugEnabled()) {
+            String ref = ReferencableObjectRegistry.getReference(obj);
+            logger.debug("Object \"" + this + "\" emitting changed: \"" + ref + "\"");
+        }
+        
 		for (Iterator i = clientPolls.values().iterator(); i.hasNext(); ) {
 			ClientPoll poll = (ClientPoll) i.next();
-			if (poll.containsObject(obj))
+            logger.debug("Examining poll " + poll);
+			if (poll.containsObject(obj)) {
+			    logger.debug("Marking poll " + poll + " as done");
 			    poll.markDone();
+            }
 		}
-
-		if (parent != null) 
-			parent.emitSignalChanged(obj);
 	}
 	
 	public synchronized void clientInactive(ClientPoll poll) {
Index: src/org/gnome/yarrr/Whiteboard.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/Whiteboard.java,v
retrieving revision 1.24
diff -u -r1.24 Whiteboard.java
--- src/org/gnome/yarrr/Whiteboard.java	26 Apr 2005 02:00:46 -0000	1.24
+++ src/org/gnome/yarrr/Whiteboard.java	26 Apr 2005 03:45:15 -0000
@@ -79,8 +79,7 @@
     private final int pngCompressionLevel = 7;
     private final boolean drawVersionDebugString = false;
     
-    Whiteboard(Discussion discussion, Person creator, int width, int height) {
-        super(discussion.getParent(), true);
+    Whiteboard(Person creator, int width, int height) {
         if (creator != null)
             this.creator = creator.getRef();
         this.width = width;
@@ -96,8 +95,7 @@
         ReferencableObjectRegistry.register(this);
     }
     
-    public Whiteboard(Discussion discussion, Person creator, BufferedImage image) {
-        super(discussion.getParent(), true);
+    public Whiteboard(Person creator, BufferedImage image) {
         if (creator != null)
             this.creator = creator.getRef();
         this.backgroundImage = image;
Index: src/org/gnome/yarrr/Yarrr.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/Yarrr.java,v
retrieving revision 1.23
diff -u -r1.23 Yarrr.java
--- src/org/gnome/yarrr/Yarrr.java	25 Apr 2005 15:50:23 -0000	1.23
+++ src/org/gnome/yarrr/Yarrr.java	26 Apr 2005 03:45:16 -0000
@@ -23,6 +23,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.log4j.PropertyConfigurator;
 import org.apache.xmlrpc.XmlRpcHandler;
+import org.gnome.yarrr.ReferencableObject.ReferencableObjectChangeListener;
 import org.gnome.yarrr.xmlrpc.StandaloneXmlRpcServer;
 import org.gnome.yarrr.xmlrpc.XmlRpcSerializable;
 
@@ -30,7 +31,7 @@
  * @author alex
  */
 public class Yarrr extends ToplevelReferencableObject implements
-		XmlRpcSerializable {
+		XmlRpcSerializable, ReferencableObjectChangeListener {
 	/**
 	 * @author walters
 	 */
@@ -64,6 +65,9 @@
     private ClientReaper reaper;
 
     private String storePath;
+
+    private ReferencableObjectChangeListener listener;
+    
     
 	/**
 	 * This function is just used initially to test stuff a bit
@@ -102,19 +106,22 @@
     }
     
 	public Yarrr(String storePath) {
-		super(null, null, false);
-        
+        super(null);
         this.storePath = storePath;
         URL props = Yarrr.class.getResource("log4j.properties");
         if (props == null)
             throw new RuntimeException("no properties found!");
         PropertyConfigurator.configure(props);
-        
+	}
+    
+    private void init() {
         String opts;
         if (!new File(storePath).exists())
             opts = ";create=true";
         else
             opts = "";
+        
+        ReferencableObjectRegistry.setListener(this);
 
         String jdbcURL = getJDBCUrl(opts);
         logger.info("using JDBC url: " + jdbcURL);
@@ -124,10 +131,10 @@
         ReferencableObjectRegistry.register(Statement.class);
         ReferencableObjectRegistry.register(ClosedComment.class);
         
-		activeTopics = new HashMap();
-		handler = new YarrrXmlRpcMethods(this);
-		registry = new ReferencableObjectRegistry();
-		clientIdMap = Collections.synchronizedMap(new HashMap());
+        activeTopics = new HashMap();
+        handler = new YarrrXmlRpcMethods(this);
+        registry = new ReferencableObjectRegistry();
+        clientIdMap = Collections.synchronizedMap(new HashMap());
 
         HibernateUtil.beginTransaction();
         try {
@@ -141,12 +148,16 @@
         } finally {
             HibernateUtil.closeSession();
         }
-		
-		setupTestObjects();
+        
+        setupTestObjects();
         ReferencableObjectRegistry.register(this);
-	}
+    }
     
-    public void startup() {
+    public void startup(boolean startWebserver) {
+        this.init();
+        if (!startWebserver)
+            return;
+        
         reaper = new ClientReaper();
         reaper.start();
         
@@ -259,7 +270,7 @@
 
 	public static void main(String[] args) {
 		Yarrr yarrr = new Yarrr();
-        yarrr.startup();
+        yarrr.startup(true);
 
 		StandaloneXmlRpcServer rpcServer = StandaloneXmlRpcServer
 				.getXmlRpcServer(19842);
@@ -339,4 +350,32 @@
 			this.interrupt();
 		}
 	}
+    
+    public void setChangedListener(ReferencableObjectChangeListener listener) {
+        this.listener = listener;
+    }
+    
+    private synchronized void emitObjectChanged(ReferencableObject obj) {
+        if (logger.isInfoEnabled())
+            logger.info("Object changed:" + ReferencableObjectRegistry.getReference(obj));
+        this.emitSignalChanged(obj);
+        Iterator it = this.activeTopics.values().iterator();
+        while (it.hasNext()) {
+            ToplevelReferencableObject top = (ToplevelReferencableObject) it.next();
+            top.emitSignalChanged(obj);
+        }
+        if (this.listener != null)
+            this.listener.objectChanged(obj);   
+    }
+    
+    public void objectSaved(Object gobj) {
+        if (!(gobj instanceof ReferencableObject))
+            return;
+        emitObjectChanged((ReferencableObject) gobj);
+
+    }
+
+    public void objectChanged(ReferencableObject obj) {
+        emitObjectChanged(obj);
+    }
 }
Index: src/org/gnome/yarrr/YarrrServlet.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/YarrrServlet.java,v
retrieving revision 1.25
diff -u -r1.25 YarrrServlet.java
--- src/org/gnome/yarrr/YarrrServlet.java	25 Apr 2005 22:51:05 -0000	1.25
+++ src/org/gnome/yarrr/YarrrServlet.java	26 Apr 2005 03:45:16 -0000
@@ -48,7 +48,7 @@
 		this.xmlrpc = new XmlRpcServer();
 
 		yarrr = new Yarrr();
-        yarrr.startup();
+        yarrr.startup(true);
 		this.xmlrpc.addHandler("$default", yarrr.getXmlRpcHandler());
 	}
 	
Index: src/org/gnome/yarrr/YarrrXmlRpcMethods.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/YarrrXmlRpcMethods.java,v
retrieving revision 1.40
diff -u -r1.40 YarrrXmlRpcMethods.java
--- src/org/gnome/yarrr/YarrrXmlRpcMethods.java	24 Apr 2005 19:16:58 -0000	1.40
+++ src/org/gnome/yarrr/YarrrXmlRpcMethods.java	26 Apr 2005 03:45:16 -0000
@@ -60,9 +60,9 @@
 		return true;
 	}
 	
-	public int subscribeStatement (ActiveTopic topic, Statement statement, Person subscriber) {
+	public boolean subscribeStatement (ActiveTopic topic, Statement statement, Person subscriber) {
 		topic.subscribeStatement (statement, subscriber);
-		return 0;
+		return true;
 	}
 	
 	public Statement addStatement(ActiveTopic topic, Person author, String content) {
@@ -70,13 +70,11 @@
 	}
 	
 	public LiveComment openLiveComment(Discussion discussion, Person opener) {
-		LiveComment comment = discussion.openComment(opener);
-		return comment;
+		return discussion.openComment(opener);
 	}
 
 	public boolean deAuthorLiveComment(Discussion discussion, LiveComment comment, Person author) {
-		boolean ret = discussion.deAuthorLiveComment(comment, author);
-		return ret;
+		return discussion.deAuthorLiveComment(comment, author);
 	}
 	
 	public Vector getClosedCommentMessages(ClosedComment comment) {
@@ -105,13 +103,11 @@
 	}
 	
 	public VersionAndDiff updateLiveComment(LiveComment comment, Person author, StringDiff diff, int version) throws Exception {
-		VersionAndDiff res = comment.updateContents(author, diff, version);
-		return res;
+		return comment.updateContents(author, diff, version);
 	}
 
-	public boolean closeLiveComment(LiveComment comment, Person closer) throws Exception {
-		comment.getDiscussion().closeComment(comment, closer);
-		return true;
+	public ClosedComment closeLiveComment(ActiveTopic topic, Discussion discussion, LiveComment comment, Person closer) throws Exception {
+        return topic.closeComment(discussion, comment, closer);
 	}
 
 	public int addChatMessage(Chat chat, Person author, String contents) {
@@ -127,17 +123,15 @@
 	}
 	
 	public String getPollUrl(ToplevelReferencableObject obj, boolean present, Person poller, String clientid, Vector items) {
-		String ret = obj.getPollUrl(present, items, poller, clientid);
-		return ret;
+		return obj.getPollUrl(present, items, poller, clientid);
 	}
 
 	public Vector getPollChanges(ToplevelReferencableObject obj, Person poller, String clientid) {
 		return obj.getPollChanges(poller, clientid);
 	}
 	
-    public boolean createWhiteboard(Discussion discussion, Person creator) {
-        discussion.createWhiteboard(creator);
-        return true;
+    public Whiteboard createWhiteboard(Discussion discussion, Person creator) {
+        return discussion.createWhiteboard(creator);
     }
     
     public int addWhiteboardStroke(Whiteboard whiteboard, Stroke stroke) throws Exception {
Index: src/org/gnome/yarrr/log4j.properties
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/log4j.properties,v
retrieving revision 1.5
diff -u -r1.5 log4j.properties
--- src/org/gnome/yarrr/log4j.properties	25 Apr 2005 12:49:29 -0000	1.5
+++ src/org/gnome/yarrr/log4j.properties	26 Apr 2005 03:45:16 -0000
@@ -4,13 +4,21 @@
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 log4j.appender.stdout.layout.ConversionPattern=%x %d{ABSOLUTE} %5p %c{1}:%L - %m%n
 
-log4j.logger.org.gnome.yarrr.ReferencableObjectRegistry=debug
+log4j.logger.org.gnome.yarrr.ReferencableObjectRegistry=info
+log4j.logger.org.gnome.yarrr.ReferencableObject=debug
+
+# Polling
+log4j.logger.org.gnome.yarrr.ToplevelReferencableObject=debug
+#log4j.logger.org.gnome.yarrr.PollWebserver=debug
+log4j.logger.org.gnome.yarrr.ClientPoll=debug
+
 # Make this debug to see xmlrpc calls
-log4j.logger.org.gnome.yarrr.xmlrpc.XmlRpcHandler=debug
+log4j.logger.org.gnome.yarrr.xmlrpc.XmlRpcHandler=info
 log4j.logger.org.gnome.yarrr=info, stdout
 
 log4j.logger.net.sf.ehcache=warn, stdout
-log4j.logger.org.hibernate=warn, stdout
+log4j.logger.org.hibernate=info, stdout
+#log4j.logger.org.hibernate.transaction.JDBCTransaction=debug, stdout
 log4j.logger.org.hibernate.SQL=info, stdout
 log4j.logger.org.hibernate.tool.hbm2ddl=debug, stdout
 
Index: src/org/gnome/yarrr/tests/ChatTests.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/tests/ChatTests.java,v
retrieving revision 1.4
diff -u -r1.4 ChatTests.java
--- src/org/gnome/yarrr/tests/ChatTests.java	24 Apr 2005 17:05:24 -0000	1.4
+++ src/org/gnome/yarrr/tests/ChatTests.java	26 Apr 2005 03:45:16 -0000
@@ -16,7 +16,7 @@
 
 	protected Chat getChatInstance1() {
 		ActiveTopic topic = this.getActiveTopic1();
-		Chat c = new Chat(topic);
+		Chat c = new Chat();
 		return c;
 	}
 
Index: src/org/gnome/yarrr/tests/YarrrTests.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/tests/YarrrTests.java,v
retrieving revision 1.4
diff -u -r1.4 YarrrTests.java
--- src/org/gnome/yarrr/tests/YarrrTests.java	25 Apr 2005 15:40:18 -0000	1.4
+++ src/org/gnome/yarrr/tests/YarrrTests.java	26 Apr 2005 03:45:16 -0000
@@ -4,12 +4,14 @@
 package org.gnome.yarrr.tests;
 
 import java.util.Iterator;
+import java.util.Set;
 
 import org.gnome.yarrr.ActiveTopic;
 import org.gnome.yarrr.ClosedComment;
 import org.gnome.yarrr.Discussion;
-import org.gnome.yarrr.HibernateUtil;
 import org.gnome.yarrr.LiveComment;
+import org.gnome.yarrr.Person;
+import org.gnome.yarrr.ReferencableObjectRegistry;
 import org.gnome.yarrr.Statement;
 import org.gnome.yarrr.Topic;
 
@@ -33,7 +35,9 @@
     }
     
     public void testTopicActivation() throws Exception {
-       assertNotNull(this.activateTopic());
+       ActiveTopic t = this.activateTopic();
+       assertEquals(0, t.getClosedComments().size());
+       assertEquals(1, t.getDiscussions().size());
     }
     
     protected Discussion getDefaultDiscussion() {
@@ -46,81 +50,226 @@
         throw new Error("should not be reached");
     }
     
+    public void testGetDefaultDiscussion() {
+        Discussion d = getDefaultDiscussion();
+        assertEquals(0, d.getComments().size());
+    }
+    
     protected LiveComment getLiveComment() {
         Discussion d = getDefaultDiscussion();
         return d.openComment(this.getYarrr().getTheCapn());
     }
  
     public void testOpenLiveComment() throws Exception {
-       getLiveComment();
-    }
-    
-    public void testCloseComment() throws Exception {
         Discussion d = getDefaultDiscussion();
         LiveComment c = getLiveComment();
-        d.closeComment(c, getYarrr().getTheCapn());
+        assertEquals(1, d.getComments().size());
+        assertEquals(c, d.getComments().iterator().next());
+        expectChanged(d);
     }
-    
+
     private ClosedComment getClosedComment() throws Exception {
+        ActiveTopic t = activateTopic();
         Discussion d = getDefaultDiscussion();
         LiveComment c = getLiveComment();
         c.setContents(COMMENT_TEXT);
-        return d.closeComment(c, getYarrr().getTheCapn());
+        return t.closeComment(d, c, getYarrr().getTheCapn());
     }
-    
-    public void testCloseCommentWithContent() throws Exception {
-        ClosedComment closed = getClosedComment();
+
+    private void postCloseAsserts(ActiveTopic t, ClosedComment closed) {
+        assertEquals(1, t.getClosedComments().size());
+        assertEquals(closed, t.getClosedComments().iterator().next());
         assertEquals(getYarrr().getTheCapn(), closed.getCloser());
         assertEquals(0, closed.getProponents().size());
     }
     
+    public void testCloseCommentWithContent() throws Exception {
+        ActiveTopic t = activateTopic();
+        Discussion d = getDefaultDiscussion();
+        assertEquals(0, t.getClosedComments().size());
+        ClosedComment closed = getClosedComment();
+        expectChanged(t);
+        expectChanged(d);
+
+        postCloseAsserts(t, closed);
+    }
+
     public void testLoadClosedComment() throws Exception {
+        ActiveTopic t = activateTopic();
         ClosedComment closed = getClosedComment();
-        long id = closed.getReferenceId();
+        String ref = ReferencableObjectRegistry.getReference(closed);
         closed = null;
         
-        HibernateUtil.newTestTransaction();
+        newTestTransaction();
         
-        closed = ClosedComment.load(new Long(id));
+        closed = (ClosedComment) ReferencableObjectRegistry.lookup(ref);
         assertNotNull(closed);
-        assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<yarrr-markup><text>" + COMMENT_TEXT + "</text></yarrr-markup>\n", closed.getContent().toString());
+        assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<yarrr-markup><text>" + COMMENT_TEXT + "</text></yarrr-markup>\n", closed.getContent().toString());   
+        postCloseAsserts(t, closed);
+    }
+    
+    private ClosedComment getMultiAuthorClosedComment() throws Exception {
+        ActiveTopic t = activateTopic();
+        Discussion d = getDefaultDiscussion();
+        LiveComment lc = getLiveComment();
+        lc.setContents("moo");
+        lc.addAuthor(getPerson1());
+        lc.addAuthor(getPerson2());
+        return t.closeComment(d, lc, getYarrr().getAnonymous());
+    }
+    
+    public void testCloseCommentMultiAuthor() throws Exception {
+        ClosedComment closed = getMultiAuthorClosedComment();
+        String closedRef = ReferencableObjectRegistry.getReference(closed);
+        
+        newTestTransaction();
+        closed = (ClosedComment) ReferencableObjectRegistry.lookup(closedRef);
+        Set authors = closed.getAuthors();
+        assertEquals(3, authors.size());
+        assertTrue(authors.contains(getYarrr().getTheCapn()));
+        assertTrue(authors.contains(getPerson1()));
+        assertTrue(authors.contains(getPerson2()));
     }
     
     public void testProponentClosedComment() throws Exception {
         ClosedComment closed = getClosedComment();
+        String ref = ReferencableObjectRegistry.getReference(closed);
+
+        newTestTransaction();
+        
+        closed = (ClosedComment) ReferencableObjectRegistry.lookup(ref);
         closed.addProponent(getYarrr().getTheCapn());
-        long id = closed.getReferenceId();
+        expectChanged(closed);
         
-        HibernateUtil.newTestTransaction();
+        newTestTransaction();
         
-        closed = ClosedComment.load(new Long(id));
+        closed = (ClosedComment) ReferencableObjectRegistry.lookup(ref);
         Iterator it = closed.getProponents().iterator();
         assertTrue(it.hasNext());
         assertEquals(getYarrr().getTheCapn(), it.next());
         assertFalse(it.hasNext());
     }
     
+    public void testProponentMultipleClosedComments() throws Exception {
+        ClosedComment closed = getMultiAuthorClosedComment();
+        Set proponents = closed.getProponents();
+        assertEquals(0, proponents.size());
+        closed.addProponent(getPerson2());
+        proponents = closed.getProponents();
+        assertEquals(1, proponents.size());
+        
+        String closedRef = ReferencableObjectRegistry.getReference(closed);
+        
+        ActiveTopic t = activateTopic();
+        Discussion d = getDefaultDiscussion();
+        LiveComment c = getLiveComment();
+        c.setContents("yay whee fun");
+        c.addAuthor(getYarrr().getTheCapn());
+        c.addAuthor(getPerson1());
+        ClosedComment closed2 = t.closeComment(d, c, getYarrr().getAnonymous());
+        proponents = closed2.getProponents();
+        assertEquals(0, proponents.size());
+        closed2.addProponent(getYarrr().getTheCapn());
+        closed2.addProponent(getPerson1());
+        proponents = closed2.getProponents();
+        assertEquals(2, proponents.size());
+        String closed2ref = ReferencableObjectRegistry.getReference(closed2);
+        
+        newTestTransaction();
+        
+        closed = (ClosedComment) ReferencableObjectRegistry.lookup(closedRef);
+        proponents = closed.getProponents();
+        assertEquals(1, proponents.size());
+        assertEquals(getPerson2(), proponents.iterator().next());
+        
+        closed2 = (ClosedComment) ReferencableObjectRegistry.lookup(closed2ref);
+        proponents = closed2.getProponents();
+        assertEquals(2, proponents.size());
+        assertTrue(proponents.contains(getYarrr().getTheCapn()));
+        assertTrue(proponents.contains(getPerson1()));
+    }
+    
     private Statement getStatement() throws Exception {
         ActiveTopic t = activateTopic();
         return t.addStatement(getYarrr().getAnonymous(), "babies are tasty");
     }
     
     public void testCreateStatement() throws Exception {
+        ActiveTopic t = activateTopic();
+        clearChanged();
         Statement s = getStatement();
+        expectChanged(t);
         assertEquals(1, s.getSubscribers().size());
-        assertTrue(s.hasSubscriber(getYarrr().getAnonymous()));
+        assertEquals(getYarrr().getAnonymous(), s.getAuthor());
     }
-    
+
     public void testSubscribeStatement() throws Exception {
+        ActiveTopic t = activateTopic();
         Statement s = getStatement();
+        String ref = ReferencableObjectRegistry.getReference(s);
+        
+        newTestTransaction();
+        
+        s = (Statement) ReferencableObjectRegistry.lookup(ref);
         assertFalse(s.hasSubscriber(getYarrr().getTheCapn()));
         s.addSubscriber(getYarrr().getTheCapn());
         assertTrue(s.hasSubscriber(getYarrr().getTheCapn()));
+        expectChanged(s);
+              
+        newTestTransaction();
         
-        long id = s.getReferenceId();
-        HibernateUtil.newTestTransaction();
-        
-        s = Statement.load(new Long(id));
+        s = (Statement) ReferencableObjectRegistry.lookup(ref);
         assertTrue(s.hasSubscriber(getYarrr().getTheCapn()));
+        
+        Person p = Person.loadOrCreate("moo");
+        String pRef = ReferencableObjectRegistry.getReference(p);
+        s.addSubscriber(p);
+        
+        newTestTransaction();
+        
+        s = (Statement) ReferencableObjectRegistry.lookup(ref);
+        p = (Person) ReferencableObjectRegistry.lookup(pRef);
+        assertTrue(s.hasSubscriber(p));
+    }
+    
+    private void verifyStatement(Statement s) {
+        assertTrue(s.hasSubscriber(getYarrr().getAnonymous()));
+        assertFalse(s.hasSubscriber(getYarrr().getTheCapn()));
+    }
+    
+    public void testSubscribeStatement2() throws Exception {
+        testSubscribeStatement();
+        ActiveTopic t = activateTopic();
+        Statement s1 = getStatement();
+        String s1ref = ReferencableObjectRegistry.getReference(s1);
+        verifyStatement(s1);
+        
+        newTestTransaction();
+        
+        s1 = (Statement) ReferencableObjectRegistry.lookup(s1ref);
+        verifyStatement(s1);
+        
+        Statement s2 = t.addStatement(getPerson1(), "foo bar baz");
+        String s2ref = ReferencableObjectRegistry.getReference(s2);
+        assertEquals(getPerson1(), s2.getAuthor());
+        assertTrue(s2.hasSubscriber(getPerson1()));
+        
+        newTestTransaction();
+        
+        s1 = (Statement) ReferencableObjectRegistry.lookup(s1ref);
+        verifyStatement(s1);
+        s2 = (Statement) ReferencableObjectRegistry.lookup(s2ref);
+        assertEquals(getPerson1(), s2.getAuthor());
+        assertTrue(s2.hasSubscriber(getPerson1()));
+        s2.addSubscriber(getYarrr().getTheCapn());
+        assertTrue(s2.hasSubscriber(getYarrr().getTheCapn()));
+        
+        newTestTransaction();
+        
+        s1 = (Statement) ReferencableObjectRegistry.lookup(s1ref);
+        verifyStatement(s1);
+        s2 = (Statement) ReferencableObjectRegistry.lookup(s2ref);
+        assertTrue(s2.hasSubscriber(getPerson1()));
+        assertTrue(s2.hasSubscriber(getYarrr().getTheCapn()));
     }
 }
Index: src/org/gnome/yarrr/tests/YarrrUsingTest.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/tests/YarrrUsingTest.java,v
retrieving revision 1.3
diff -u -r1.3 YarrrUsingTest.java
--- src/org/gnome/yarrr/tests/YarrrUsingTest.java	24 Apr 2005 19:16:58 -0000	1.3
+++ src/org/gnome/yarrr/tests/YarrrUsingTest.java	26 Apr 2005 03:45:16 -0000
@@ -4,8 +4,14 @@
 package org.gnome.yarrr.tests;
 
 import java.io.File;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
 
 import org.gnome.yarrr.HibernateUtil;
+import org.gnome.yarrr.Person;
+import org.gnome.yarrr.ReferencableObject;
+import org.gnome.yarrr.ReferencableObjectRegistry;
 import org.gnome.yarrr.Yarrr;
 
 import junit.framework.TestCase;
@@ -13,10 +19,12 @@
 /**
  * @author walters
  */
-public class YarrrUsingTest extends TestCase {
+public class YarrrUsingTest extends TestCase implements Yarrr.ReferencableObjectChangeListener {
 
 	private Yarrr theYarrr;
     private File tmpPath;
+    private Set changedObjs;
+    private Set expectedChangedObjs;
 
 	protected Yarrr getYarrr() {
 		assert theYarrr != null;
@@ -27,15 +35,65 @@
 		super.setUp();
         tmpPath = TestUtils.getTemporaryDir();
 		theYarrr = new Yarrr(tmpPath.toString());
+        theYarrr.startup(false);
+        theYarrr.setChangedListener(this);
+        this.changedObjs = new HashSet();
+        this.expectedChangedObjs = new HashSet();
         HibernateUtil.beginTransaction();
 	}
+    
+    protected void clearChanged() {
+        this.changedObjs.clear();
+    }
+    
+    protected void expectChanged(ReferencableObject obj) {
+        this.expectedChangedObjs.add(ReferencableObjectRegistry.getReference(obj));
+    }
+    
+    protected Person getPerson1() {
+       return Person.loadOrCreate("person1");
+    }
+    
+    protected Person getPerson2() {
+       return Person.loadOrCreate("person2");
+    }
+    
+    private void printIterator(String header, Iterator it) {
+        System.out.print(header);
+        while (it.hasNext()) {
+            System.out.print(it.next());
+            System.out.print(", ");
+        }
+        System.out.println();
+    }
+    
+    public void closeTransactionVerify() throws Exception {
+        HibernateUtil.commitTransaction();
+        HibernateUtil.closeSession();
+        Iterator it = expectedChangedObjs.iterator();
+        while (it.hasNext()) {
+            String expected = (String) it.next();
+            assertTrue("Object \"" + expected + "\" changed", changedObjs.contains(expected));
+        }
+        expectedChangedObjs.removeAll(changedObjs);
+        assertEquals(0, expectedChangedObjs.size());
+        this.changedObjs = new HashSet();
+    }
+    
+    public void newTestTransaction() throws Exception {
+        closeTransactionVerify();
+        HibernateUtil.beginTransaction();
+    }
 
 	public void tearDown() throws Exception {
         super.tearDown();
-        HibernateUtil.commitTransaction();
-        HibernateUtil.closeSession();
+        closeTransactionVerify();
         theYarrr.shutdownDB();
         TestUtils.delete(tmpPath);
 		theYarrr = null;
 	}
+
+    public void objectChanged(ReferencableObject obj) {
+        this.changedObjs.add(ReferencableObjectRegistry.getReference(obj));
+    }
 }
Index: src/org/gnome/yarrr/xmlrpc/XmlRpcHandler.java
===================================================================
RCS file: /cvs/gnome/yarrr/src/org/gnome/yarrr/xmlrpc/XmlRpcHandler.java,v
retrieving revision 1.15
diff -u -r1.15 XmlRpcHandler.java
--- src/org/gnome/yarrr/xmlrpc/XmlRpcHandler.java	22 Apr 2005 18:50:51 -0000	1.15
+++ src/org/gnome/yarrr/xmlrpc/XmlRpcHandler.java	26 Apr 2005 03:45:16 -0000
@@ -19,6 +19,10 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.xmlrpc.XmlRpc;
 import org.gnome.yarrr.HibernateUtil;
+import org.gnome.yarrr.ReferencableObject;
+import org.gnome.yarrr.ReferencableObjectRegistry;
+import org.gnome.yarrr.ToplevelReferencableObject;
+import org.gnome.yarrr.xmlrpc.XmlRpcDemarshaller.DemarshallingException;
 import org.gnome.yarrr.xmlrpc.XmlRpcMarshaller.MarshallingException;
 
 /**
@@ -75,7 +79,9 @@
 			if (marshalledClass != null &&
 					marshalledClass.isAssignableFrom(param.getClass()))
 				return true;
-		} 
+		} else if (ReferencableObject.class.isAssignableFrom(argType) && param.getClass() == String.class) {
+            return true;
+		}
 		if (argType == Integer.TYPE && param instanceof Integer)
 			return true;
 		if (argType == Double.TYPE && param instanceof Double)
@@ -105,34 +111,50 @@
 		Method [] methods = getClass().getMethods(); 
 		for (int i = 0; i < methods.length; i++) {
 			Method method = methods[i];
+            
 			if (method.getName().equals(methodName) &&
 					Modifier.isPublic(method.getModifiers()) &&
 					!Modifier.isStatic(method.getModifiers()) &&
-					method.getDeclaringClass() != Object.class &&
-					methodParamsMatch(method, params))
-				return method;
+					method.getDeclaringClass() != Object.class) {
+			        if (methodParamsMatch(method, params))
+			            return method;
+            }
 		}
 		return null;
-	}
-	
+    }
+    
+    private Object demarshalParam (Class argType, Object param) throws Exception {
+        if (ReferencableObject.class.isAssignableFrom(argType)) {
+            String objref = (String) param;
+            Object ret = ReferencableObjectRegistry.lookup(objref);
+            if (ret == null)
+                throw new DemarshallingException("Unreferenced object " + objref);
+            return ret;
+        } else if (XmlRpcDemarshaller.class.isAssignableFrom(argType)) {
+            return demarshalFromClass(argType, param);
+        } else {
+            return param;
+        }
+    }
 
 	private Object [] demarshalParams(Method method, Vector params) throws Exception {
 		Object [] out = new Object[params.size()];
 		Class[] argTypes = method.getParameterTypes();
+        ToplevelReferencableObject parent = null;
+   
 		for (int i = 0; i < out.length; i++) {
 			Object param = params.get(i);
 			Class argType = argTypes[i];
 			
-			if (XmlRpcDemarshaller.class.isAssignableFrom(argType)) {
-				out[i] = demarshalFromClass(argType, param);
-			} else {
-				out[i] = param;
-			}
+			out[i] = demarshalParam(argType, param);
 		}
 		return out;
 	}
 	
 	public Object recursivelyMarshal(Object obj) throws MarshallingException {
+        if (obj instanceof ReferencableObject) {
+            return ReferencableObjectRegistry.getReference((ReferencableObject) obj);
+        }
 		if (obj instanceof XmlRpcMarshaller) {
 			obj = ((XmlRpcMarshaller) obj).xmlRpcMarshal(this);
 		}
@@ -175,8 +197,8 @@
 	    returnVal = recursivelyMarshal(returnVal);
 		return returnVal;
 	}
-	
-	/**
+
+    /**
 	 * Here we can special-case how xmlrpc parameters are logged
 	 */
 	private String parameterAsString(Object o) throws NoSuchAlgorithmException {
@@ -196,16 +218,18 @@
 		try {
             HibernateUtil.beginTransaction();
 			if (logger.isDebugEnabled()) {
-				logger.debug("invoking xmlRpc method " + methodName + "() with " + params.size()  +" arguments:");
+				logger.debug("Invoking XML-RPC method " + methodName + "() with " + params.size()  +" arguments:");
 				Iterator i;	int n;
 				for (n = 1, i = params.iterator(); i.hasNext(); n++) {
 					Object o = i.next();
 					logger.debug(n +": " + o.getClass().getName() + " = " + parameterAsString(o));
 				}
-			}
+			} else if (logger.isInfoEnabled()) {
+			    logger.info("Invoking XML-RPC method: " + methodName);
+            }
 			
 			Object res = executeDemarshalled(methodName, params);
-			logger.debug("method " + methodName + " returned: " + res);
+			logger.debug("Method " + methodName + " returned: " + res);
             HibernateUtil.commitTransaction();
 			return res;
 		} catch (Exception e) {
Index: web/comment.js
===================================================================
RCS file: /cvs/gnome/yarrr/web/comment.js,v
retrieving revision 1.29
diff -u -r1.29 comment.js
--- web/comment.js	24 Apr 2005 17:05:24 -0000	1.29
+++ web/comment.js	26 Apr 2005 03:45:16 -0000
@@ -352,9 +352,10 @@
   }
 }
 
+Comment.prototype.onclose = function() {}
+
 Comment.prototype.close = function() {
-  yarrr.closeLiveComment(this.id, getYarrrPerson(), function (res, err) {} );
-  // Remove immediately so you can't close this comment twice
+  this.onclose();
   this.remove();
 }
 
Index: web/topic.js
===================================================================
RCS file: /cvs/gnome/yarrr/web/topic.js,v
retrieving revision 1.85
diff -u -r1.85 topic.js
--- web/topic.js	26 Apr 2005 01:23:33 -0000	1.85
+++ web/topic.js	26 Apr 2005 03:45:16 -0000
@@ -492,6 +492,9 @@
   return this.topic.getPresentPersonNames();
 }
 
+Discussion.prototype.onCommentClose = function(commentid) {
+}
+
 Discussion.prototype.sync = function(contents) {
   var discussion = this;
   if (this.chat == null && ('chat' in contents)) {
@@ -502,17 +505,20 @@
   var comments = contents.comments
   var commentsdict = {};
   for (var i = 0; i < comments.length; i++) {
-    commentsdict[comments[i]] = true;
-    if (! (comments[i] in this.livecomments)) {
-      this.livecomments[comments[i]] = new Comment(comments[i], this.comments_div);
-      if (this.openedCommentId == comments[i]) {
+    var commentid = comments[i];
+    commentsdict[commentid] = true;
+    if (! (commentid in this.livecomments)) {
+      this.livecomments[commentid] = new Comment(commentid, this.comments_div);
+      var comment = this.livecomments[commentid]
+      if (this.openedCommentId == commentid) {
         var textarea = this.commentEntry.stealTextAndDeactivate()
-        this.livecomments[comments[i]].setTextArea(textarea)
+        this.livecomments[commentid].setTextArea(textarea)
         this.openedCommentId = null;
       }
-      var commentid = comments[i];
-      this.livecomments[comments[i]].oncancel = function () { discussion.handleLiveCommentCancel(commentid); }
-      registerObject(this.livecomments[comments[i]])
+
+      comment.oncancel = function () { discussion.handleLiveCommentCancel(commentid); }
+      comment.onclose = function () { discussion.onCommentClose(commentid); };
+      registerObject(comment)
       if (this.onUnread) {
         this.onUnread(1)
       }
@@ -668,10 +674,9 @@
   topicelt.appendChild(this.contentelt) 
 
   this.commentdiv = document.createElement("div")
+  this.commentdiv.appendChild(document.createTextNode("Loading..."))
   this.contentelt.appendChild(this.commentdiv); 
 
-  this.initLog();
-
   var precached = stealPrecachedReferencableObject(this.id)
   if (precached != null)
     this.sync(precached)
@@ -681,24 +686,6 @@
 
 ClosedComment.prototype = new ReferencableObject();
 
-ClosedComment.prototype.initLog = function (contents) {
-
-  /* Create the log */
-  var span = document.createElement("span");
-  span.setAttribute("class", "closedcommentdiscussion");
-  span.appendChild(document.createTextNode("Discussion (closed)"));
-
-  var anchor = document.createElement("a");
-  anchor.appendChild (document.createTextNode("Logs"));
-  // Apparently, we can't have an href that calls js correctly.  so we point the href to '#'
-  // cnn.com does this too.  *shrug*
-  anchor.setAttribute("href", "#");
-  var comment = this; anchor.onclick = function () { comment.showLogs() };
-
-  this.contentelt.appendChild(span);
-  this.contentelt.appendChild(anchor);
-}
-
 ClosedComment.prototype.sync = function (contents) {
  try {
   var canProponent = false;
@@ -742,6 +729,17 @@
       canProponent = false;
     }
   }
+  
+  /* Create the log */
+  var anchor = document.createElement("a");
+  anchor.appendChild (document.createTextNode("Discussion (closed)"));
+  // Apparently, we can't have an href that calls js correctly.  so we point the href to '#'
+  // cnn.com does this too.  *shrug*
+  anchor.setAttribute("href", "#");
+  var comment = this; anchor.onclick = function () { comment.showLogs() };
+
+  this.contentelt.appendChild(anchor);
+  
   if (canProponent) {
     var button = document.createElement("input")
     button.setAttribute("type", "button")
@@ -853,6 +851,7 @@
 }
 
 Topic.prototype.sync = function (content) {
+  var topic = this;
   this.setCreator(content.creator, content.creationDate);
   this.setPresentPersons(content.presentpersons);
   
@@ -870,10 +869,12 @@
     var id = content.discussions[i];
     if (!(id in this.discussions)) {
       this.discussions[id] = new Discussion(id, this);
-      registerObject(this.discussions[id])
-      if (this.openDiscussionActive && !this.discussions[id].expanded) {
+      var discussion = this.discussions[id]
+      registerObject(discussion)
+      discussion.onCommentClose = function (commentid) { yarrr.closeLiveComment(topic.getId(), discussion.getId(), commentid, getYarrrPerson(), function (result, err) {}); } 
+      if (this.openDiscussionActive && !discussion.expanded) {
         this.openDiscussionActive = false; // Racy, but oh well
-        this.discussions[id].expand()
+        discussion.expand()
       }
     }
   }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]