ModeShape/modeshape — pull request #710 by rhauch (2013-02-25 20:25:43+00:00)
Title: MODE-1819 Changed how WorkspaceCache is used inside user transactions

Added a new specialization of WorkspaceCache called TransactionWorkspaceCache that is used in place of WorkspaceCache any time a ReadOnlySessionCache or WritableSessionCache is used during the scope of a running user transaction.

Normally, each RepositoryCache instance has for each workspace a single WorkspaceCache instance shared by all sessions. However, in the case of a session is running within a user transaction, it is possible for the session (or other sessions that are also participating in the same user transaction) to persist node changes in the document store but to not commit the transaction. This means that such persisted-but-not-committed node representations should be visible to the sessions running within the transaction but should not be visible to sessions outside of the transaction. Because the shared WorkspaceCache lazily loads the requested nodes using the transactional scope of the requestor, this means that the WorkspaceCache may load these persisted-but-not-committed node representations and make them available to other sessions (outside the transaction boundary) and thereby leaking transactionally-scoped data. Similarly, a WorkspaceCache that already has the persisted-and-committed node representations from other sessions will expose those already-cached forms to sessions running within the transaction, thereby causing some of the sessions in the same user transaction to not see the transactionally-scoped (e.g., persisted-but-not-committed) changes.

Therefore, such sessions running within user transactions need a transactionally-scoped WorkspaceCache instance rather than sharing the commont WorkspaceCache instance. However, the ModeShape infrastructure is not set up to handle lots of WorkspaceCache instances for the same workspace, so we have to be careful about any transaction-specific caches.

This new design changes the AbstractSessionCache to maintain the original reference to the real (shared) WorkspaceCache, but to also have another WorkspaceCache reference that may change (depending upon whether there is a current user transaction) to a transient TransactionalWorkspaceCache instance specific to the transaction.

Originally, I tried to have the TransactionalWorkspaceCache do no caching whatsoever, but this caused problems within the WritableSessionCache's persistChanges logic, which needs access to the persisted representation of the node prior to when each changed node was actually changed. So, the TWC simply uses an in-memory ConcurrentHashMap. (The alternative was to use another Infinispan cache such as the one used for the shared workspace caches, but this would dramatically increase the complexity of the system and would require an additional cache container configured with different settings than the normal workspace caches and to use a different naming convention.)

Several new test cases were added to verify that multiple sessions within the same user transaction do indeed all see the same transactionally-scoped data, while other sessions that are outside of the user transaction do indeed not see any of the transactionally-scoped changes until commit occurs.