Return-Path: Delivered-To: apmail-openjpa-dev-archive@www.apache.org Received: (qmail 24798 invoked from network); 18 Jun 2010 22:56:46 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 18 Jun 2010 22:56:46 -0000 Received: (qmail 81011 invoked by uid 500); 18 Jun 2010 22:56:46 -0000 Delivered-To: apmail-openjpa-dev-archive@openjpa.apache.org Received: (qmail 80838 invoked by uid 500); 18 Jun 2010 22:56:45 -0000 Mailing-List: contact dev-help@openjpa.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@openjpa.apache.org Delivered-To: mailing list dev@openjpa.apache.org Received: (qmail 80829 invoked by uid 99); 18 Jun 2010 22:56:44 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 18 Jun 2010 22:56:44 +0000 X-ASF-Spam-Status: No, hits=-1531.2 required=10.0 tests=ALL_TRUSTED,AWL X-Spam-Check-By: apache.org Received: from [140.211.11.22] (HELO thor.apache.org) (140.211.11.22) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 18 Jun 2010 22:56:43 +0000 Received: from thor (localhost [127.0.0.1]) by thor.apache.org (8.13.8+Sun/8.13.8) with ESMTP id o5IMuMf5002661 for ; Fri, 18 Jun 2010 22:56:23 GMT Message-ID: <30625368.87421276901782712.JavaMail.jira@thor> Date: Fri, 18 Jun 2010 18:56:22 -0400 (EDT) From: "Heath Thomann (JIRA)" To: dev@openjpa.apache.org Subject: [jira] Updated: (OPENJPA-1702) UnsupportedOperationException caused in BrokerImpl during transaction commit processing. In-Reply-To: <27865656.71351276823905093.JavaMail.jira@thor> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-JIRA-FingerPrint: 30527f35849b9dde25b450d4833f0394 [ https://issues.apache.org/jira/browse/OPENJPA-1702?page=3Dcom.atlass= ian.jira.plugin.system.issuetabpanels:all-tabpanel ] Heath Thomann updated OPENJPA-1702: ----------------------------------- Attachment: OPENJPA-1702-TEST.patch.txt I'm providing a test (in a 'patch' form), named OPENJPA-1702-TEST.patch.txt= , which will recreate the UnsupportedOperationException (UOE). =20 Looking at the comments within the 'test' method, you will see that I provi= ded a suggested change to fix the UOE. However, as the remained of the tes= t shows, after fixing the UOE I've found that the updates made in 'beforeCo= mmit' processing are not persisted to the DB. For now, I'll provide the te= st for those interested and will continue to dig into a complete fix. =20 Also note that after fixing the UOE, if you change the test case to dirty t= he entity before the commit, the changes made in 'beforeCommit' processing = ARE persisted. This may offer a clue. > UnsupportedOperationException caused in BrokerImpl during transaction com= mit processing. > -------------------------------------------------------------------------= --------------- > > Key: OPENJPA-1702 > URL: https://issues.apache.org/jira/browse/OPENJPA-1702 > Project: OpenJPA > Issue Type: Bug > Components: kernel > Affects Versions: 1.2.0, 2.0.0 > Reporter: Heath Thomann > Assignee: Heath Thomann > Priority: Minor > Attachments: OPENJPA-1702-TEST.patch.txt > > > For a given scenario, which will be described in detail below, an Unsuppo= rtedOperationException occurs as follows: > [main] openjpa.Runtime - An exception occurred while ending the transacti= on. This exception will be re-thrown. > org.apache.= openjpa.util.StoreException: null > =09at org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.ja= va:1853) > =09at org.apache.openjpa.kernel.LocalManagedRuntime.commit(LocalManagedRu= ntime.java:81) > =09at org.apache.openjpa.kernel.BrokerImpl.commit(BrokerImpl.java:1369) > =09at org.apache.openjpa.kernel.DelegatingBroker.commit(DelegatingBroker.= java:877) > =09at org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManag= erImpl.java:513) > =09at hat.tests.TestUnsupportedOp.commitTx(TestUnsupportedOp.java:44) > =09at hat.tests.TestUnsupportedOp.test(TestUnsupportedOp.java:90) > =09at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) > =09at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImp= l.java:39) > =09at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcc= essorImpl.java:25) > =09at java.lang.reflect.Method.invoke(Method.java:592) > =09at junit.framework.TestCase.runTest(TestCase.java:164) > =09at junit.framework.TestCase.runBare(TestCase.java:130) > =09at junit.framework.TestResult$1.protect(TestResult.java:110) > =09at junit.framework.TestResult.runProtected(TestResult.java:128) > =09at junit.framework.TestResult.run(TestResult.java:113) > =09at junit.framework.TestCase.run(TestCase.java:120) > =09at junit.framework.TestSuite.runTest(TestSuite.java:228) > =09at junit.framework.TestSuite.run(TestSuite.java:223) > =09at org.junit.internal.runners.OldTestClassRunner.run(OldTestClassRunne= r.java:35) > =09at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUni= t4TestReference.java:46) > =09at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecuti= on.java:38) > =09at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(Rem= oteTestRunner.java:467) > =09at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(Rem= oteTestRunner.java:683) > =09at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTe= stRunner.java:390) > =09at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteT= estRunner.java:197) > Caused by: java.lang.UnsupportedOperationException > =09at java.util.AbstractCollection.add(AbstractCollection.java:216) > =09at java.util.AbstractCollection.addAll(AbstractCollection.java:318) > =09at org.apache.openjpa.kernel.BrokerImpl.flushTransAdditions(BrokerImpl= .java:2103) > =09at org.apache.openjpa.kernel.BrokerImpl.flushAdditions(BrokerImpl.java= :2086) > =09at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2000) > =09at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:1927= ) > =09at org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.ja= va:1845) > =09... 25 more > Using the stack trace, and some particulars about the code path, I've bee= n able to recreate the UnsupportedOperationException. =C2=A0Let me first su= mmarize what my test does, and then let me go into great details on how the= issue occurs.=C2=A0 My test does the following: > 1) My "main" code simply begins a tran, performs a query, and commits the= tran. > 2) I've created a 'tran listener' (i.e. an impl of org.apache.openjpa.eve= nt.TransactionListener) and in that 'listener', method 'beforeCommit', I di= rty the entity queried/found in #1. > 3) After my 'beforeCommit' method returns, the UnsupportedOperationExcept= ion is thrown. > OK, that was the brief summary, for anyone else who cares to hear the gor= y details, lets dig in.....first, the exception stack shows the exception i= s hit here: > Caused by: java.lang.UnsupportedOperationException > =C2=A0 =C2=A0at java.util.AbstractCollection.add(AbstractCollection.java:= 68) > =C2=A0 =C2=A0at java.util.AbstractCollection.addAll(AbstractCollection.ja= va:87) > =C2=A0 =C2=A0at > org.apache.openjpa.kernel.BrokerImpl.flushTransAdditions(BrokerImpl.java:= 2099)=20 > =C2=A0 =C2=A0at > org.apache.openjpa.kernel.BrokerImpl.flushAdditions(BrokerImpl.java:2086) > =C2=A0 =C2=A0at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.jav= a:2000)=20 > So, lets look at the code around 'flush(BrokerImpl.java:2000)'.=C2=A0 To = follow is line 2000 (the last line) and a number of lines proceeding it: > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0 if ((_transEventManager.hasFlushListeners() > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 || _transEventManager.hasEndList= eners()) > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 && (flush || reason =3D=3D FLUSH= _COMMIT)) { > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 // fire events > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 mobjs =3D new ManagedObjectColle= ction(transactional); > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (reason =3D=3D FLUSH_COMMIT > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 && _tran= sEventManager.hasEndListeners()) { > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 fireTran= sactionEvent(new TransactionEvent(this,=20 > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0 TransactionEvent.BEFORE_COMMIT, mobjs, > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0=C2=A0=C2=A0 _persistedClss, _updatedClss, _deletedClss)); > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0= =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 flushAdd= itions(transactional, reason);=C2=A0=C2=A0=C2=A0 <----- 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).=C2=A0 OK, w= ith 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 'flus= hTransAdditions(BrokerImpl.java:2099)' looks like this: > =C2=A0=C2=A0=C2=A0 private boolean flushTransAdditions(Collection transac= tional, int reason) { > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (_transAdditions =3D=3D nul= l || _transAdditions.isEmpty()) > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return= false; > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 // keep local transactional li= st copy up to date > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 transactional.addAll(_transAdd= itions);=C2=A0=C2=A0 <----- 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 '_transAdditi= ons'. > For #1, lets visit the javadoc for Collection.addAll and see why/when it = throws the UnsupportedOperationException.....its states: > =C2=A0=C2=A0=C2=A0 * @throws UnsupportedOperationException if this collec= tion does not > =C2=A0=C2=A0=C2=A0=C2=A0 *=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0 support the addAll method. > So, we know that the 'Collection' is of a type which must not support add= All.=C2=A0 This offers a clue and we should look to see at which points 'tr= ansactional' could be defined as a 'Collection' which doesn't support 'addA= ll'.=C2=A0 'transactional' is set in BrokerImpl at line 1946 which is here: > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 Collection transactional =3D g= etTransactionalStates(); > If we look at 'getTransactionalStates()', we can see that the method coul= d return a Collections.EMPTY_SET ('EmptySet'): > =C2=A0=C2=A0=C2=A0 protected Collection getTransactionalStates() { > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 if (!hasTransactionalObjects()= ) > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return= Collections.EMPTY_SET; > =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 return _transCache.copy(); > =C2=A0=C2=A0=C2=A0 } > An 'EmptySet.addAll' eventually calls 'AbstractCollection.add' which blat= antly throws an UnsupportedOperationException (plus, and Collections.EMPTY_= SET is immutable, so we should be adding to it anyway).=C2=A0 So, we know w= e must have a case where 'transactional' is an EmtpySet.=C2=A0 One way this= may occur is to only query objects as I've done in step #1 of my test (i.e= . I never dirty anything in step #1). > Next, #2 offers another clue in that we need to look at the case where '_= transAdditions' is not null and not empty.=C2=A0 If we look in BorkerImpl a= t the places where '_transAdditions' is set, we can see things are added to= it in the 'setDirty' method.=C2=A0 But, as we previously found, we are onl= y querying objects, not making them dirty.=C2=A0 So, how can we have 'trans= actional' be an EmptySet, yet '_transAdditions' not null or empty?=C2=A0 On= e way is to go back to the 'listener' we discussed earlier and when the 'li= stener' is called, have it dirty an entity.=C2=A0 In so doing, the 'setDirt= y' method will be called which will add elements to '_transAdditions' such = that conditions are met to cause 'transactional.addAll' to be called in 'fl= ushTransAdditions'.=C2=A0 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.=C2= =A0 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/AbstractColl= ection which returns the exception. --=20 This message is automatically generated by JIRA. - You can reply to this email to add a comment to the issue online.