Index: src/main/java/org/apache/jackrabbit/core/SessionImpl.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/SessionImpl.java (revision 752420) +++ src/main/java/org/apache/jackrabbit/core/SessionImpl.java (working copy) @@ -226,6 +226,12 @@ * Retention and Hold Manager */ private RetentionManager retentionManager; + + /** + * The stack trace knows who opened this session. It is logged + * if the session is finalized, but Session.logout() was never called. + */ + private Exception openStackTrace = new Exception("Stack Trace"); /** * Internal helper class for common validation checks (lock status, checkout @@ -1542,5 +1548,17 @@ ps.println(); itemStateMgr.dump(ps); } + + /** + * Finalize the session. If the application doesn't close Session.logout(), + * the session is closed automatically; however a warning is written to the log file, + * together with the stack trace of where the session was opened. + */ + public void finalize() { + if (alive) { + log.warn("Unclosed session detected. The session was opened here: ", openStackTrace); + logout(); + } + } } Index: src/main/java/org/apache/jackrabbit/core/TransientRepository.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/TransientRepository.java (revision 752420) +++ src/main/java/org/apache/jackrabbit/core/TransientRepository.java (working copy) @@ -25,14 +25,14 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Properties; -import java.util.Set; import javax.jcr.Credentials; -import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; +import org.apache.commons.collections.map.ReferenceMap; import org.apache.commons.io.IOUtils; import org.apache.jackrabbit.api.JackrabbitRepository; import org.apache.jackrabbit.core.config.ConfigurationException; @@ -57,11 +57,6 @@ LoggerFactory.getLogger(TransientRepository.class); /** - * Buffer size for copying the default repository configuration file. - */ - private static final int BUFFER_SIZE = 4096; - - /** * Resource path of the default repository configuration file. */ private static final String DEFAULT_REPOSITORY_XML = "repository.xml"; @@ -123,7 +118,7 @@ * repository instance is automatically shut down until a new session * is opened. */ - private final Set sessions; + private final Map sessions = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK); /** * The static repository descriptors. The default {@link RepositoryImpl} @@ -142,7 +137,6 @@ public TransientRepository(RepositoryFactory factory) throws IOException { this.factory = factory; this.repository = null; - this.sessions = new HashSet(); this.descriptors = new Properties(); // FIXME: The current RepositoryImpl class does not allow static @@ -331,9 +325,9 @@ try { logger.debug("Opening a new session"); - Session session = repository.login(credentials, workspaceName); - sessions.add(session); - ((SessionImpl) session).addListener(this); + SessionImpl session = (SessionImpl) repository.login(credentials, workspaceName); + sessions.put(session, session); + session.addListener(this); logger.info("Session opened"); return session; @@ -393,7 +387,7 @@ * @see Session#logout() */ public synchronized void shutdown() { - Iterator iterator = new HashSet(sessions).iterator(); + Iterator iterator = new HashSet(sessions.keySet()).iterator(); while (iterator.hasNext()) { Session session = (Session) iterator.next(); session.logout(); @@ -410,7 +404,7 @@ * @see SessionListener#loggedOut(SessionImpl) */ public synchronized void loggedOut(SessionImpl session) { - assert sessions.contains(session); + assert sessions.containsKey(session); sessions.remove(session); logger.info("Session closed"); if (sessions.isEmpty()) { Index: src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java (revision 752420) +++ src/main/java/org/apache/jackrabbit/core/state/StateChangeDispatcher.java (working copy) @@ -20,6 +20,8 @@ import org.apache.jackrabbit.core.NodeId; import org.apache.jackrabbit.spi.Name; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.util.Collection; import java.util.Iterator; @@ -30,12 +32,14 @@ public class StateChangeDispatcher { /** - * Simple item state listeners + * Simple item state listeners. + * A copy on write array list is used so that no synchronization is required. */ private final Collection listeners = new CopyOnWriteArrayList(); /** * Node state listeners + * A copy on write array list is used so that no synchronization is required. */ private final transient Collection nsListeners = new CopyOnWriteArrayList(); @@ -44,14 +48,29 @@ * @param listener the new listener to be informed on modifications */ public void addListener(ItemStateListener listener) { - assert (!listeners.contains(listener)); - listeners.add(listener); + assert getReference(listeners, listener) == null; + listeners.add(new WeakReference(listener)); if (listener instanceof NodeStateListener) { - assert (!nsListeners.contains(listener)); - nsListeners.add(listener); + assert getReference(nsListeners, listener) == null; + nsListeners.add(new WeakReference(listener)); } } + + private Reference getReference(Collection coll, ItemStateListener listener) { + Iterator iter = coll.iterator(); + while (iter.hasNext()) { + Reference ref = (Reference) iter.next(); + Object o = ref.get(); + if (o == listener) { + return ref; + } else if (o == null) { + // clean up unreferenced objects + coll.remove(ref); + } + } + return null; + } /** * Remove an ItemStateListener @@ -59,9 +78,9 @@ */ public void removeListener(ItemStateListener listener) { if (listener instanceof NodeStateListener) { - nsListeners.remove(listener); + nsListeners.remove(getReference(nsListeners, listener)); } - listeners.remove(listener); + listeners.remove(getReference(listeners, listener)); } /** @@ -71,7 +90,11 @@ public void notifyStateCreated(ItemState created) { Iterator iter = listeners.iterator(); while (iter.hasNext()) { - ((ItemStateListener) iter.next()).stateCreated(created); + Reference ref = (Reference) iter.next(); + ItemStateListener l = (ItemStateListener) ref.get(); + if (l != null) { + l.stateCreated(created); + } } } @@ -82,7 +105,11 @@ public void notifyStateModified(ItemState modified) { Iterator iter = listeners.iterator(); while (iter.hasNext()) { - ((ItemStateListener) iter.next()).stateModified(modified); + Reference ref = (Reference) iter.next(); + ItemStateListener l = (ItemStateListener) ref.get(); + if (l != null) { + l.stateModified(modified); + } } } @@ -93,7 +120,11 @@ public void notifyStateDestroyed(ItemState destroyed) { Iterator iter = listeners.iterator(); while (iter.hasNext()) { - ((ItemStateListener) iter.next()).stateDestroyed(destroyed); + Reference ref = (Reference) iter.next(); + ItemStateListener l = (ItemStateListener) ref.get(); + if (l != null) { + l.stateDestroyed(destroyed); + } } } @@ -104,7 +135,11 @@ public void notifyStateDiscarded(ItemState discarded) { Iterator iter = listeners.iterator(); while (iter.hasNext()) { - ((ItemStateListener) iter.next()).stateDiscarded(discarded); + Reference ref = (Reference) iter.next(); + ItemStateListener l = (ItemStateListener) ref.get(); + if (l != null) { + l.stateDiscarded(discarded); + } } } @@ -118,7 +153,11 @@ public void notifyNodeAdded(NodeState state, Name name, int index, NodeId id) { Iterator iter = nsListeners.iterator(); while (iter.hasNext()) { - ((NodeStateListener) iter.next()).nodeAdded(state, name, index, id); + Reference ref = (Reference) iter.next(); + NodeStateListener n = (NodeStateListener) ref.get(); + if (n != null) { + n.nodeAdded(state, name, index, id); + } } } @@ -129,7 +168,11 @@ public void notifyNodesReplaced(NodeState state) { Iterator iter = nsListeners.iterator(); while (iter.hasNext()) { - ((NodeStateListener) iter.next()).nodesReplaced(state); + Reference ref = (Reference) iter.next(); + NodeStateListener n = (NodeStateListener) ref.get(); + if (n != null) { + n.nodesReplaced(state); + } } } @@ -140,7 +183,11 @@ public void notifyNodeModified(NodeState state) { Iterator iter = nsListeners.iterator(); while (iter.hasNext()) { - ((NodeStateListener) iter.next()).nodeModified(state); + Reference ref = (Reference) iter.next(); + NodeStateListener n = (NodeStateListener) ref.get(); + if (n != null) { + n.nodeModified(state); + } } } @@ -154,7 +201,11 @@ public void notifyNodeRemoved(NodeState state, Name name, int index, NodeId id) { Iterator iter = nsListeners.iterator(); while (iter.hasNext()) { - ((NodeStateListener) iter.next()).nodeRemoved(state, name, index, id); + Reference ref = (Reference) iter.next(); + NodeStateListener n = (NodeStateListener) ref.get(); + if (n != null) { + n.nodeRemoved(state, name, index, id); + } } }