HI!  Using the stack trace, and some particulars about the code path in your
stack, I've been able to recreate the UnsupportedOperationException.  I need
you to walk though my test (and the code if you care since you seem to have
already poked around at BrokerImpl).  Let me first summarize what my test
does, and then let me go into great detail for the rest of the folks
watching this list in order to determine if I'm on the right path.  My test
does the following (please let me know if your code does something similar):

1) My "main" code simply begins a tran, queries entities, and commits the
tran.....you stated your code does the same (i.e. I never update/dirty
anything or create new objects).
2) I've created a 'tran listener' (i.e. an impl of
org.apache.openjpa.event.TransactionListener) and in that 'listener', method
'beforeCommit' I dirty the entity queried in #1.
3) After my my 'beforeCommit' method returns, the
UnsupportedOperationException is thrown.

Does your code do anything at all remotely similar?


OK, that was the brief summary, for anyone else who cares to hear the gory
details, lets dig in.....first, the exception stack shows the exception is
hit here:

Caused by: java.lang.UnsupportedOperationException
   at java.util.AbstractCollection.add(AbstractCollection.java:68)
   at java.util.AbstractCollection.addAll(AbstractCollection.java:87)
   at
org.apache.openjpa.kernel.BrokerImpl.flushTransAdditions(BrokerImpl.java:2099)

   at
org.apache.openjpa.kernel.BrokerImpl.flushAdditions(BrokerImpl.java:2086)
   at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2000)


So, lets look at the code around 'flush(BrokerImpl.java:2000)'.  To follow
is line 2000 (the last line) and a number of lines proceeding it:


               if ((_transEventManager.hasFlushListeners()
                    || _transEventManager.hasEndListeners())
                    && (flush || reason == FLUSH_COMMIT)) {
                    // fire events
                    mobjs = new ManagedObjectCollection(transactional);
                    if (reason == FLUSH_COMMIT
                        && _transEventManager.hasEndListeners()) {
                        fireTransactionEvent(new TransactionEvent(this,
                            TransactionEvent.BEFORE_COMMIT, mobjs,
                            _persistedClss, _updatedClss, _deletedClss));

                        flushAdditions(transactional, reason);    <-----
line 2000


So, in order to get to this 'flushAdditions', you must have a 'listener'
(i.e. an impl of org.apache.openjpa.event.TransactionListener).  OK, with
that said, keep this 'listener' idea in mind as we will come back to it.

Continue to dig into the stack and going up two levels, we see that
'flushTransAdditions(BrokerImpl.java:2099)' looks like this:

    private boolean flushTransAdditions(Collection transactional, int
reason) {
        if (_transAdditions == null || _transAdditions.isEmpty())
            return false;

        // keep local transactional list copy up to date
        transactional.addAll(_transAdditions);   <----- line 2099

There are two important things to note here:
1) 'transactional' is a 'Collection'.
2) the addAll will only be called depending on the state of
'_transAdditions'.

For #1, lets visit the javadoc for Collection.addAll and see why/when it
throws the UnsupportedOperationException.....its states:

    * @throws UnsupportedOperationException if this collection does not
     *         support the <tt>addAll</tt> method.

So, we know that the 'Collection' is of a type which must not support
addAll.  This offers a clue and we should look to see at which points
'transactional' could be defined as a 'Collection' which doesn't support
'addAll'.  As you stated in your post, 'transactional' is set in BrokerImpl
at line 1946 which is here:

        Collection transactional = getTransactionalStates();

If we look at 'getTransactionalStates()', we can see that the method could
return a Collections.EMPTY_SET ('EmptySet'):

    protected Collection getTransactionalStates() {
        if (!hasTransactionalObjects())
            return Collections.EMPTY_SET;
        return _transCache.copy();
    }

An 'EmptySet.addAll' eventually calls 'AbstractCollection.add' which
blatantly throws an UnsupportedOperationException.  So, we know we must have
a case where 'transactional' is an EmtpySet.  One way this may occur, is as
you've stated, that is, that you only query objects.

Next, #2 offers another clue in that we need to look at the case where
'_transAdditions' is not null and not empty.  If we look in BorkerImpl at
the places where '_transAdditions' is set, we can see things are added to it
in the 'setDirty' method.  But, as we previously found, we are only querying
object, not making them dirty.  So, how can we have 'transactional' be an
EmptySet, yet '_transAdditions' not null or empty?  One way is to go back to
the 'listener' we discussed earlier and when the 'listener' is called, have
it dirty and entity.  In so doing, the 'setDirty' method will be called
which will add elements to '_transAdditions' such that conditions are met to
cause 'transactional.addAll' to be called in 'flushTransAdditions'.  The
ordering is basically like this:

1) 'transactional' is set to an EmptySet and the beginning of flush.
2) The 'listener' is called later on in flush which dirties an entity.  This
causes '_transAdditions' to not be null or empty.
3) After the 'listener' is called, flushTransAdditions is called where at
which time 'addAll', and then 'add', is called on an
EmptySet/AbstractCollection which returns the exception.

Thanks,

Heath

On Mon, May 3, 2010 at 4:10 PM, Jeff Awe <[email protected]> wrote:

> We're running with OpenJPA 1.2.2, and seeing failures calling commit on an
> EntityManager transaction.  It seems like a timing issue since we've only
> seen this a few times.  It looks like
> BrokerImpl.flush(BrokerImpl.java:1946)
> calls getTransactionalStates() to setup the Collection that the
> UnsupportedOperationException eventually happens on.
> getTransactionalStates() will set this to Collections.EMPTY_SET if
> hasTransactionalObjects() is false.... which would cause the error we're
> seeing.
>
> Could we be doing something wrong to cause this, or is a check missing
> somewhere in BrokerImpl when processing this transaction?  I'm not certain,
> but I think when this fails, we're not even calling persist() on the
> EntityManager.  We just open the transaction, maybe do some querying, and
> then commit it.  I tried to recreate this in a standalone test, but
> couldn't
> get it to fail.  In this test, _transAdditions is null in
> BrokerImpl.flushTransAdditions(BrokerImpl.java:2096), so it returns false
> right away.
>
> 2010/05/03 15:53:07.446 WARNING Action failed: Commit failed, transaction
> was rolled back: updateResourceCollection(ResourceCollection)
> ::class.method=EntityManagerConnectionManager.tryCommit()
> ::thread=AEMDBServiceThreadQueue <5 of 10>
> ::loggername=com.ibm.sysmgmt.persistence.jpa.extensions.emcm
>
>     org.apache.openjpa.persistence.RollbackException: null
>    at
>
> org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:523)
>    at
>
> com.ibm.sysmgmt.persistence.jpa.extensions.USMiEntityManager$USMiEntityTransaction.commit(USMiEntityManager.java:609)
>    at
>
> com.ibm.sysmgmt.persistence.jpa.extensions.EntityManagerConnectionManager.tryCommit(EntityManagerConnectionManager.java:720)
>    at
>
> com.ibm.sysmgmt.resource.caching.jpa.JPAResourceCachingService.tryCommit(JPAResourceCachingService.java:6536)
>    at
>
> com.ibm.sysmgmt.resource.caching.jpa.JPAResourceCachingService._updateResourceCollection(JPAResourceCachingService.java:3840)
>    at
>
> com.ibm.sysmgmt.resource.caching.jpa.JPAResourceCachingService._updateResourceCollection(JPAResourceCachingService.java:3480)
>    at
>
> com.ibm.sysmgmt.resource.caching.BaseResourceCachingService.updateResourceCollection(BaseResourceCachingService.java:4498)
>    at
>
> com.ibm.aem.common.dbservice.ResourceAccess.updateUSMIServiceDataBatch(Unknown
> Source)
>    at
> com.ibm.aem.common.dbservice.ResourceAccess.findUSMIResourceType(Unknown
> Source)
>    at com.ibm.aem.common.dbservice.ResourceAccess.findResourceType(Unknown
> Source)
>    at com.ibm.aem.common.dbservice.ResourceAccess.queryResourceData(Unknown
> Source)
>    at com.ibm.aem.common.dbservice.ResourceAccess.queryResourceData(Unknown
> Source)
>    at com.ibm.aem.common.dbservice.AemDBService.getResourceData(Unknown
> Source)
>    at com.ibm.aem.common.dbservice.AemDBService.getResourceData(Unknown
> Source)
>    at
>
> com.ibm.aem.common.dbservice.ManageableElementThread.manageableElementCreated(Unknown
> Source)
>    at com.ibm.aem.common.dbservice.ManageableElementThread.process(Unknown
> Source)
>    at com.tivoli.twg.libs.QueueBatch$QueueServer.run(QueueBatch.java:358)
>    at java.lang.Thread.run(Thread.java:736)
> Caused by:  org.apache.openjpa.persistence.PersistenceException: null
>    at
> org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.java:1853)
>    at
>
> org.apache.openjpa.kernel.LocalManagedRuntime.commit(LocalManagedRuntime.java:81)
>    at org.apache.openjpa.kernel.BrokerImpl.commit(BrokerImpl.java:1369)
>    at
>
> org.apache.openjpa.kernel.DelegatingBroker.commit(DelegatingBroker.java:877)
>    at
>
> org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:512)
>    at
>
> com.ibm.sysmgmt.persistence.jpa.extensions.USMiEntityManager$USMiEntityTransaction.commit(USMiEntityManager.java:604)
>    ... 16 more
> Caused by: java.lang.UnsupportedOperationException
>    at java.util.AbstractCollection.add(AbstractCollection.java:68)
>    at java.util.AbstractCollection.addAll(AbstractCollection.java:87)
>    at
>
> org.apache.openjpa.kernel.BrokerImpl.flushTransAdditions(BrokerImpl.java:2099)
>    at
> org.apache.openjpa.kernel.BrokerImpl.flushAdditions(BrokerImpl.java:2086)
>    at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2000)
>    at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:1927)
>    at
> org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.java:1845)
>    ... 21 more
>

Reply via email to