JDO
  1. JDO
  2. JDO-445

Add convenience method that returns a PersistenceManager proxy

    Details

      Description

      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.

      1. JDO-445.patch
        6 kB
        Craig L Russell
      2. JDO-445.patch
        7 kB
        Craig L Russell

        Activity

        Hide
        Craig L Russell added a comment -

        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.

        Show
        Craig L Russell added a comment - 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.
        Hide
        Craig L Russell added a comment -

        Please review this patch, which consolidates patches for JDO-445, JDO-449, and JDO-462

        Show
        Craig L Russell added a comment - Please review this patch, which consolidates patches for JDO-445 , JDO-449 , and JDO-462
        Hide
        Craig L Russell added a comment -

        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.

        Show
        Craig L Russell added a comment - 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.
        Hide
        Craig L Russell added a comment -

        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.

        Show
        Craig L Russell added a comment - 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.
        Hide
        Craig L Russell added a comment -

        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.

        Show
        Craig L Russell added a comment - 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.
        Hide
        Michael Watzek added a comment -

        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

        Show
        Michael Watzek added a comment - 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
        Hide
        Guido Anzuoni added a comment -

        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

        Show
        Guido Anzuoni added a comment - 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
        Hide
        Craig L Russell added a comment -

        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?

        Show
        Craig L Russell added a comment - 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?
        Hide
        Guido Anzuoni added a comment -

        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 ?

        Show
        Guido Anzuoni added a comment - 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 ?
        Hide
        Guido Anzuoni added a comment -

        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 ?

        Show
        Guido Anzuoni added a comment - 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 ?
        Hide
        Craig L Russell added a comment -

        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 Craig.Russell@sun.com
        P.S. A good JDO? O, Gasp!

        Show
        Craig L Russell added a comment - 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 Craig.Russell@sun.com P.S. A good JDO? O, Gasp!
        Hide
        Jörg von Frantzius added a comment -

        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.

        Show
        Jörg von Frantzius added a comment - 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.
        Hide
        Jörg von Frantzius added a comment -

        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?

        Show
        Jörg von Frantzius added a comment - 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?
        Hide
        Craig L Russell added a comment -

        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.

        Show
        Craig L Russell added a comment - 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.
        Hide
        Craig L Russell added a comment -

        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.

        Show
        Craig L Russell added a comment - 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.

          People

          • Assignee:
            Craig L Russell
            Reporter:
            Michelle Caisse
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development