Issue Details (XML | Word | Printable)

Key: JDO-445
Type: New Feature New Feature
Status: Resolved Resolved
Resolution: Fixed
Priority: Major Major
Assignee: Craig Russell
Reporter: Michelle Caisse
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
JDO

Add convenience method that returns a PersistenceManager proxy

Created: 08/Dec/06 08:34 PM   Updated: 20/Feb/07 11:35 PM
Return to search
Component/s: api2, api2-legacy
Affects Version/s: None
Fix Version/s: JDO 2 maintenance release 1

Time Tracking:
Not Specified

File Attachments:
  Size
Text File Licensed for inclusion in ASF works JDO-445.patch 2007-02-16 11:51 PM Craig Russell 7 kB
Text File Licensed for inclusion in ASF works JDO-445.patch 2007-02-16 12:32 AM Craig Russell 6 kB

Resolution Date: 20/Feb/07 11:35 PM


 Description  « Hide
In Chapter 8, add a convenience method that returns a PersistenceManager proxy that can be used in web and ejb containers to dynamically bind to the transaction-associated PersistenceManager.

 All   Comments   Work Log   Change History   Subversion Commits      Sort Order: Ascending order - Click to sort in descending order
Michelle Caisse made changes - 08/Dec/06 11:20 PM
Field Original Value New Value
Assignee Craig Russell [ clr ]
Craig Russell made changes - 09/Dec/06 11:49 PM
Fix Version/s JDO 2 maintenance release 1 [ 12310923 ]
Craig Russell added a comment - 19/Dec/06 08:45 PM
Proposal:

Add a new JDOHelper method getPersistenceManagerProxy that returns a thread-safe PersistenceManager that dynamically delegates to the PersistenceManager currently bound to the thread's global transaction as reported by TransactionSynchronizationRegistry. If there is no such PersistenceManager, the method will get one from the PersistenceManagerFactory. This method can be used by application developers who know that their application only needs to run in environments that support TransactionSynchronizationRegistry registered via JNDI in the standard location.

Add a new PersistenceManagerFactory method getPersistenceManagerProxy that returns a thread-safe PersistenceManager that dynamically delegates to the PersistenceManager currently bound to the thread's global transaction. If there is no such PersistenceManager, the method will get one from the PersistenceManagerFactory. This method can be implemented by the persistence provider to support Java EE 5 by simply delegating to JDOHelper.getPersistenceManagerProxy. If support for non-Java EE 5 servers is desired, the provider must implement the functionality as value-add.

Craig Russell added a comment - 19/Dec/06 10:45 PM
It looks like there might not be a need for a JDOHelper method. The required functionality can be encapsulated entirely in a new class, PersistenceManagerProxy, that can be used directly by a user or by the persistence provider.

I suggest that the specification changes needed are to add the getPersistenceManagerProxy method to the PersistenceManagerFactory interface, and to document the PersistenceManagerProxy class.

Craig Russell made changes - 19/Dec/06 10:45 PM
Attachment PMProxy-patch.txt [ 12347522 ]
Jörg von Frantzius added a comment - 20/Dec/06 10:34 AM
Probably I didn't understand something here, but I wonder why it should be proxies and not simply the PersistenceManager objects themselves that are returned by these new methods?

Jörg von Frantzius added a comment - 20/Dec/06 11:59 AM
And what about non-transactional reads? What we do in our systems is to keep track of one PM per thread in a Map, and upon returning a PM, we iterate the keyset() of threads in this map, "reaping" dead threads by closing their PMs if they weren't closed already, and removing found dead threads from the Map afterwards.

As far as I can see, this could provide an implementation of the proposed methods, as with an ongoing transaction the current thread is equivalent to the ongoing transaction. The only problem could be transactions that (sequentially) span multiple threads, but I don't believe that any XA implementation supports that scenario anyway.

Craig Russell added a comment - 21/Dec/06 01:37 AM
Hi Jörg,

Here's the use case:

class MyServlet implements javax.servlet {
static PersistenceManager pm;

void init(ServletContext...) {
   pm = pmf.getPersistenceManagerProxy();
...
}

void doGet(Request...) {
   pm.getObjectById(...);
...
}

The doPost, doGet, etc methods don't have to go look up something
from JNDI or even get the "current" pm from the pmf. They just use
the pm instance that the init method instantiated.

Craig



Craig Russell
Architect, Sun Java Enterprise System http://java.sun.com/products/jdo
408 276-5638 mailto:Craig.Russell@sun.com
P.S. A good JDO? O, Gasp!


Craig Russell made changes - 21/Dec/06 01:37 AM
Attachment smime.p7s [ 12347634 ]
Guido Anzuoni added a comment - 21/Dec/06 09:41 AM
Is it absolutely (technically) impossible to implement this functionality with JTA 1.0.x ?
I understand that there are some ordering issues, but if we assume that in a global txn
context features like non-transactional reads might not have sense, it is sufficient that the
real PM attached to a global transaction registers itself as a Synchronization and
afterCompletion notify the PMProxy of the event wich physically closes the real PM, removing
it from the map that bind jta transaction to real PM.
If I can express an opinion, I don't like the name getPersistenceManagerProxy for the method.
It is too generic, why not getJtaPersistenceManager() or getTransactionalPersistenceManager()
that clearly states the role of the object returned.

Guido

P.S.
About TransactionManager lookup, may I suggest the solution used in the patch attached to
http://www.jpox.org/servlet/jira/browse/CORE-2734 ?

Guido Anzuoni added a comment - 21/Dec/06 09:52 AM
Another problem I see, looking at the attached patch too, is that the real PM lifecycle is not clear. When the real PM is closed and its cache cleared ?

Craig Russell made changes - 21/Dec/06 11:02 PM
Attachment smime.p7s [ 12347634 ]
Craig Russell added a comment - 21/Dec/06 11:14 PM
Here's a working draft of the specification text in Chapter 11.

PersistenceManager getPersistenceManagerProxy();
Returns a PersistenceManager instance that is thread-safe in web and application servers, that dynamically delegates to the PersistenceManager currently associated with the thread's transaction. The instance returned can be used in a servlet init method to initialize a static variable in a web server application. Similarly, it can be used in a session bean to initialize a static variable in an application server application.

If any method is invoked on the returned instance, the instance dynamically binds to a delegate PersistenceManager obtained from the PersistenceManagerFactory from which the proxy was obtained.

If there is an active global transaction bound to the calling thread, and there is a PersistenceManager currently bound to the transaction, the method is invoked on the delegate.

If there is an active global transaction bound to the calling thread, and there is not a PersistenceManager currently bound to the transaction, a PersistenceManager is obtained from the PersistenceManagerFactory (which automatically associates the PersistenceManager with the transaction) and the method is invoked on the delegate.

Committing or rolling back a global transaction has the side effect of disassociating the PersistenceManager from the transaction during afterCompletion.

Open questions:

What should the behavior be if there is no global transaction active?

Outside the server environment, what should the Proxy do?

Guido Anzuoni added a comment - 22/Dec/06 11:06 AM
The simple approach would be no transaction no PM.
If we would try a more sophisticated approach, try to consider the scenario where
you begin with a ordinary PM and then you begin a global transaction.
Suppose you get a PM without a global transaction and begin to modify some objetcs and flush.
At his point (considering for simplicity a RDBMS backend) the PM should have started
a local transaction on the DataSource Connection acquired.
Now you begin a global transaction.
What to do now ?
In this case you could not "join" the global transaction but you should complete the local one.
But, what happen to the DataSource Connection itself used to operate until now ?
Its corresponding XAResource is enlisted ?
Is still possible to commit() the local transaction on the Connection ?

For the second point, I would say that is sufficient to have a TransactionManager, that can be
setup even in a J2SE environment





Michael Watzek added a comment - 22/Dec/06 02:59 PM
Hi Craig,

the patch looks good! Please find my comments below:

- PersistenceManagerProxies are cached using PMF instances as keys. Thus, you get the same proxy for the same PMF. I think this pattern disallows applications to implementing a 1:1 mapping for servlets and PMFs because applications would retrieve the same proxy instance with each (possibly concurrent) request.

- PersistenceManagerProxy.close() does nothing. This means that after calling close, it's still possible to successfully call methods like makePersistent() etc. My understanding of javax.jdo.PersistenceManager is that all methods throw exceptions after close() except for isClosed(). Is it a goal to have the same behavior for the proxy class and javax.jdo.PersistenceManager?

- Why is getPersistenceManagerProxy() a static method on class PersistenceManagerProxy rather than on class JDOHelper? This means that application code must import class PersistenceManagerProxy. If the method is defined on JDOHelper then class PersistenceManagerProxy can have default access modifier and the proxy class can be transparent to application code.

- Class PersistenceManagerProxy never calls remove on static field 'persistenceManagerProxies'.

- The JNDI lookup of TransactionSynchronizationRegistry is done in a static initializer which may throw ExceptionInInitializerError. Would it make sense to do the lookup in getPersistenceManagerProxy()?

Regards,
Michael

Craig Russell added a comment - 23/Dec/06 12:58 AM
These comments were made during the JDO TCK conference call.

What about close()? Since the life cycle of the delegate is managed by the server, and the life cycle of the proxy is unbounded, close() should probably throw an exception.

What about commit() and rollback() for Transaction instances obtained from the proxy? Since the transaction is by definition managed by the server, these methods should throw an exception.

Is the proxy for use only with JTA? Other possible use cases include "extended persistence context" within the server as well as Java SE. In these cases, where there is no TransactionSynchronizationRegistry assistance, a ThreadLocal would be needed, and the implementation is not as straightforward. For example, to support multiple PersistenceManagerFactories, a Map of pmf to pm would be needed in the ThreadLocal. And the API would need to be told which factory to use.

Craig Russell added a comment - 23/Dec/06 01:09 AM
Reply to Michael Watzek:

[brazil]- PersistenceManagerProxies are cached using PMF instances as keys. Thus, you get the same proxy for the same PMF. I think this pattern disallows applications to implementing a 1:1 mapping for servlets and PMFs because applications would retrieve the same proxy instance with each (possibly concurrent) request.

[clr] Yes, the intent is that you get the same proxy for the same PMF. Since the proxy is thread-safe, this will always work.

[brazil]- PersistenceManagerProxy.close() does nothing. This means that after calling close, it's still possible to successfully call methods like makePersistent() etc. My understanding of javax.jdo.PersistenceManager is that all methods throw exceptions after close() except for isClosed(). Is it a goal to have the same behavior for the proxy class and javax.jdo.PersistenceManager?

[clr] Not exactly a goal, especially since close is a life cycle method and the life cycle of the proxy and the delegate are different. So close will need to be specified separately for the proxy. Since it doesn't make sense to close the proxy, and the life cycle of the delegate is under the control of the server, close should throw an exception. Also you will never get a closed delegate from the proxy.

[brazil]- Why is getPersistenceManagerProxy() a static method on class PersistenceManagerProxy rather than on class JDOHelper? This means that application code must import class PersistenceManagerProxy. If the method is defined on JDOHelper then class PersistenceManagerProxy can have default access modifier and the proxy class can be transparent to application code.

[clr] The intent was that this would not be used by the user, who would use getPersistenceManagerProxy from the factory.

[brazil]- Class PersistenceManagerProxy never calls remove on static field 'persistenceManagerProxies'.

[clr] You are right, the proxies will exist until forever. The number of proxies in the Map is small (one per PersistenceManagerFactory). We should use a weak Map that would allow the Entry to be removed if the key is garbage collected. Thanks for the suggestion.

[brazil]- The JNDI lookup of TransactionSynchronizationRegistry is done in a static initializer which may throw ExceptionInInitializerError. Would it make sense to do the lookup in getPersistenceManagerProxy()?

[clr] Thanks for the suggestion. I'll take a look. I'll need to double check to make sure the resources needed by the code are available at the time the methods are called.

Craig Russell added a comment - 23/Dec/06 07:56 PM
I think there might be a use-case for the getPersistenceManagerProxy for a non-JTA PersistenceManagerFactory. This proxy would use the delegation pattern but would use a ThreadLocal to store the delegate PersistenceManager. This allows multiple transactions to be begun and committed using the same delegate.

The behavior would be slightly different in the life cycle of the delegate (the proxy would still be static and thread-safe, and bound to an instance of PersitsenceManagerFactory. If there were no thread local delegate, the PersistenceManager would be obtained from the factory and stored in the thread local. The delegate would be removed from the thread local when the close() method was called on the proxy. The next invocation of a method on the proxy would obtain a new delegate.

The behavior of getPersistenceManagerProxy could be different based on whether the factory were configured as a TransactionType JTA versus RESOURCE_LOCAL. We could use the same method, and it would return either a PersistenceManagerJTAProxy or a PersistenceManagerThreadLocalProxy depending on the setting of the TransactionType.

Alternatively, we could define two methods, one of which would always throw an exception: getPersistenceManagerJTAProxy or getPersistenceManagerThreadLocalProxy.

I'm not afraid of long method names, by the way. ;-)

Craig Russell made changes - 16/Feb/07 12:18 AM
Attachment PMProxy-patch.txt [ 12347522 ]
Craig Russell made changes - 16/Feb/07 12:32 AM
Attachment JDO-445.patch [ 12351307 ]
Craig Russell added a comment - 16/Feb/07 11:51 PM
Please review this patch, which consolidates patches for JDO-445, JDO-449, and JDO-462

Craig Russell made changes - 16/Feb/07 11:51 PM
Attachment JDO-445.patch [ 12351408 ]
Repository Revision Date User Message
ASF #509809 Tue Feb 20 23:34:41 UTC 2007 clr JDO-445 JDO-449 JDO-462 Updated PersistenceManager and PersistenceManagerFactory with new methods
Files Changed
MODIFY /db/jdo/trunk/tck20/src/conf/jdo-2_0-signatures.txt
MODIFY /db/jdo/trunk/api20/src/java/javax/jdo/PersistenceManagerFactory.java
MODIFY /db/jdo/trunk/api20/src/java/javax/jdo/PersistenceManager.java

Craig Russell added a comment - 20/Feb/07 11:35 PM
svn commit -m "JDO-445 JDO-449 JDO-462 Updated PersistenceManager and PersistenceManagerFactory with new methods" api20/src/java/javax/jdo/PersistenceManagerFactory.java api20/src/java/javax/jdo/PersistenceManager.java tck20/src/conf/jdo-2_0-signatures.txt
Sending api20/src/java/javax/jdo/PersistenceManager.java
Sending api20/src/java/javax/jdo/PersistenceManagerFactory.java
Sending tck20/src/conf/jdo-2_0-signatures.txt
Transmitting file data ...
Committed revision 509809.

Craig Russell made changes - 20/Feb/07 11:35 PM
Resolution Fixed [ 1 ]
Status Open [ 1 ] Resolved [ 5 ]