Index: cpa/src/main/java/org/exolab/castor/persist/LockEngine.java =================================================================== --- cpa/src/main/java/org/exolab/castor/persist/LockEngine.java (revision 8526) +++ cpa/src/main/java/org/exolab/castor/persist/LockEngine.java (working copy) @@ -405,6 +405,11 @@ OID oid = paramoid; ClassMolder molder = paramMolder; + ClassMolder basemolder = paramMolder; + while (basemolder.getExtends() != null) { + basemolder = basemolder.getExtends(); + } + InstanceFactory entityFactory = tx.getInstanceFactory(); ClassLoader entityLoader = tx.getDatabase().getClassLoader(); @@ -445,73 +450,80 @@ } else { action = ObjectLock.ACTION_READ; } - - lock = typeinfo.acquire(oid, tx, action, timeout); - // Remember if entity is extended or not - proposedObject.setExpanded(oid != lock.getOID()); + // we need to synchronize until we got the final lock. this will have quite some + // performance impact but currently there is no other chance. on the long term we + // have to rewrite LockEngine to always acquire locks on the base class of an + // extends hierarchy. + synchronized (basemolder) { + lock = typeinfo.acquire(oid, tx, action, timeout); + + // Remember if entity is extended or not + proposedObject.setExpanded(oid != lock.getOID()); - // (lock.getObject() == null) indicates a cache miss - if (lock.getObject() == null) { - // We always need to load at cache miss - typeinfo._molder.load(tx, lock, proposedObject, accessMode, results); + // (lock.getObject() == null) indicates a cache miss + if (lock.getObject() == null) { + // We always need to load at cache miss + typeinfo._molder.load(tx, lock, proposedObject, accessMode, results); + + // Get ClassMolder + molder = getClassMolderWithDependent(proposedObject.getActualEntityClass()); + } else { + // Get ClassMolder + molder = getClassMolderWithDependent(lock.getOID().getName()); + } - // Get ClassMolder - molder = getClassMolderWithDependent(proposedObject.getActualEntityClass()); - } else { - // Get ClassMolder - molder = getClassMolderWithDependent(lock.getOID().getName()); - } - - if (proposedObject.isExpanded()) { - // Confirm lock before setting it to null - lock.confirm(tx, true); + if (proposedObject.isExpanded()) { + // Confirm lock before setting it to null + lock.confirm(tx, true); - // Current transaction holds lock for old OID - typeinfo.release(lock.getOID(), tx); + // Current transaction holds lock for old OID + typeinfo.release(lock.getOID(), tx); - lock = null; - - // Remove old OID from ObjectTracker - tx.untrackObject(proposedObject.getEntity()); - - // Create new OID - oid = new OID(molder, oid.getIdentity()); + lock = null; + + // Remove old OID from ObjectTracker + tx.untrackObject(proposedObject.getEntity()); + + // Create new OID + oid = new OID(molder, oid.getIdentity()); - // Create instance of 'expanded object' - if (entityFactory != null) { - objectInTx = entityFactory.newInstance(molder.getName(), entityLoader); - } else { - objectInTx = molder.newInstance(entityLoader); - } + // Create instance of 'expanded object' + if (entityFactory != null) { + objectInTx = entityFactory.newInstance(molder.getName(), entityLoader); + } else { + objectInTx = molder.newInstance(entityLoader); + } - molder.setIdentity(tx, objectInTx, identity); - - proposedObject.setActualClassMolder(null); - proposedObject.setEntity(objectInTx); - proposedObject.setExpanded(false); + molder.setIdentity(tx, objectInTx, identity); + + proposedObject.setActualClassMolder(null); + proposedObject.setEntity(objectInTx); + proposedObject.setExpanded(false); - // Add new OID to ObjectTracker - tx.trackObject(molder, oid, proposedObject.getEntity()); - - // Reload 'expanded object' using correct ClassMolder - typeinfo = _typeInfo.get(oid.getName()); - if (typeinfo == null) { - throw new ClassNotPersistenceCapableException(Messages.format( - "persist.classNotPersistenceCapable", oid.getName())); - } + // Add new OID to ObjectTracker + tx.trackObject(molder, oid, proposedObject.getEntity()); + + // Reload 'expanded object' using correct ClassMolder + typeinfo = _typeInfo.get(oid.getName()); + if (typeinfo == null) { + throw new ClassNotPersistenceCapableException(Messages.format( + "persist.classNotPersistenceCapable", oid.getName())); + } - accessMode = typeinfo._molder.getAccessMode(suggestedAccessMode); + accessMode = typeinfo._molder.getAccessMode(suggestedAccessMode); - if ((accessMode == AccessMode.Exclusive) || (accessMode == AccessMode.DbLocked)) { - action = ObjectLock.ACTION_WRITE; - } else { - action = ObjectLock.ACTION_READ; - } + if ((accessMode == AccessMode.Exclusive) + || (accessMode == AccessMode.DbLocked)) { + action = ObjectLock.ACTION_WRITE; + } else { + action = ObjectLock.ACTION_READ; + } - lock = typeinfo.acquire(oid, tx, action, timeout); + lock = typeinfo.acquire(oid, tx, action, timeout); + } } - + // Set fields at proposed object proposedObject.setFields(lock.getObject(tx)); Index: cpactf/src/test/java/org/castor/cpa/test/test2861/TestReferenceChange.java =================================================================== --- cpactf/src/test/java/org/castor/cpa/test/test2861/TestReferenceChange.java (revision 8508) +++ cpactf/src/test/java/org/castor/cpa/test/test2861/TestReferenceChange.java (working copy) @@ -15,6 +15,8 @@ public final class TestReferenceChange extends CPAThreadedTestCase { private static final String DBNAME = "test2861"; private static final String MAPPING = "/org/castor/cpa/test/test2861/mapping.xml"; + private static final String MAPPING_RO = "/org/castor/cpa/test/test2861/mappingReadOnly.xml"; + private static final String DBNAME_RO = "test2861ReadOnly"; public static Test suite() throws Exception { TestSuite suite = new TestSuite(TestReferenceChange.class.getName()); @@ -23,6 +25,7 @@ suite.addTest(new TestReferenceChange("testCreateInvoice")); suite.addTest(new TestReferenceChange("testQuerySameInvoiceSubsequent")); suite.addTest(new TestReferenceChange("testQuerySameInvoiceMT")); + suite.addTest(new TestReferenceChange("testQuerySameInvoiceReadOnlyMT")); return suite; } @@ -52,12 +55,36 @@ marvin.setSocialSecurityNumber("2"); marvin.setCreator(acme); + NaturalPerson baby = new NaturalPerson(); + baby.setOid("AAAAbaby"); + baby.setName("BABY HERMAN"); + baby.setSocialSecurityNumber("3"); + baby.setCreator(acme); + + NaturalPerson judge = new NaturalPerson(); + judge.setOid("AAAjudge"); + judge.setName("JUDGE DOOM"); + judge.setSocialSecurityNumber("4"); + judge.setCreator(acme); + Motorcycle motorcycle = new Motorcycle(); motorcycle.setOid("AAAAMT01"); motorcycle.setChassisNumber("CHASSIS00001"); motorcycle.setHolder(marvin); motorcycle.setReseller(acme); + Motorcycle motorcycle2 = new Motorcycle(); + motorcycle2.setOid("AAAAMT02"); + motorcycle2.setChassisNumber("CHASSIS00002"); + motorcycle2.setHolder(acme); + motorcycle2.setReseller(acme); + + Motorcycle motorcycle3 = new Motorcycle(); + motorcycle3.setOid("AAAAMT03"); + motorcycle3.setChassisNumber("CHASSIS00003"); + motorcycle3.setHolder(acme); + motorcycle3.setReseller(acme); + Parameter invoiceSeq = new Parameter(); invoiceSeq.setOid("AAAINVSQ"); invoiceSeq.setIdSys("INVSEQ"); @@ -70,7 +97,11 @@ db.create(acme); db.create(marvin); + db.create(baby); + db.create(judge); db.create(motorcycle); + db.create(motorcycle2); + db.create(motorcycle3); db.create(invoiceSeq); db.commit(); @@ -78,6 +109,12 @@ } public void testCreateInvoice() throws Exception { + testCreateInvoice1(); + testCreateInvoice2(); + testCreateInvoice3(); + } + + public void testCreateInvoice1() throws Exception { LegalPerson acme = null; NaturalPerson marvin = null; Motorcycle motorcycle = null; @@ -143,6 +180,138 @@ db.close(); } + public void testCreateInvoice2() throws Exception { + LegalPerson acme = null; + NaturalPerson baby = null; + Motorcycle motorcycle = null; + + Database db = getJDOManager(DBNAME, MAPPING).getDatabase(); + db.setAutoStore(false); + db.begin(); + acme = db.load(LegalPerson.class, "AAAAacme"); + baby = db.load(NaturalPerson.class, "AAAAbaby"); + motorcycle = db.load(Motorcycle.class, "AAAAMT02"); + db.commit(); + + Vector vInvoiceItem = new Vector(); + Invoice invoice = new Invoice(); + invoice.setOid("AAAINV02"); + invoice.setEmitter(acme); + invoice.setBillTo(baby); + invoice.setInvoiceItem(vInvoiceItem); + + InvoiceItem invoiceItem = new InvoiceItem(); + invoiceItem.setOid("AINVIT02"); + invoiceItem.setInvoice(invoice); + invoiceItem.setProduct(motorcycle); + invoiceItem.setQuantity(new Integer(1)); + invoiceItem.setPrice(new Double(13000)); + invoiceItem.setTotal(new Double(13000)); + vInvoiceItem.add(invoiceItem); + + motorcycle.setHolder(baby); + + db.setAutoStore(false); + db.begin(); + + OQLQuery oql = db.getOQLQuery( + "SELECT obj FROM " + Parameter.class.getName() + " obj " + + "WHERE person.oid = $1 AND idSys = $2"); + oql.bind(acme.getOid()); + oql.bind("INVSEQ"); + QueryResults results = oql.execute(); + + Parameter invoiceSeq = null; + if (results.hasMore()) { + invoiceSeq = (Parameter) results.nextElement(); + } + int newInvoiceSeq = invoiceSeq.getIntValue().intValue() + 1; + invoiceSeq.setIntValue(new Integer(newInvoiceSeq)); + invoice.setNumber(invoiceSeq.getIntValue()); + + oql.close(); + results.close(); + + db.update(motorcycle); + db.create(invoice); + + db.commit(); + + db.begin(); + motorcycle = db.load(Motorcycle.class, "AAAAMT02"); + db.commit(); + + assertEquals(motorcycle.getHolder().getName(), baby.getName()); + + db.close(); + } + + public void testCreateInvoice3() throws Exception { + LegalPerson acme = null; + NaturalPerson judge = null; + Motorcycle motorcycle = null; + + Database db = getJDOManager(DBNAME, MAPPING).getDatabase(); + db.setAutoStore(false); + db.begin(); + acme = db.load(LegalPerson.class, "AAAAacme"); + judge = db.load(NaturalPerson.class, "AAAjudge"); + motorcycle = db.load(Motorcycle.class, "AAAAMT03"); + db.commit(); + + Vector vInvoiceItem = new Vector(); + Invoice invoice = new Invoice(); + invoice.setOid("AAAINV03"); + invoice.setEmitter(acme); + invoice.setBillTo(judge); + invoice.setInvoiceItem(vInvoiceItem); + + InvoiceItem invoiceItem = new InvoiceItem(); + invoiceItem.setOid("AINVIT03"); + invoiceItem.setInvoice(invoice); + invoiceItem.setProduct(motorcycle); + invoiceItem.setQuantity(new Integer(1)); + invoiceItem.setPrice(new Double(23000)); + invoiceItem.setTotal(new Double(23000)); + vInvoiceItem.add(invoiceItem); + + motorcycle.setHolder(judge); + + db.setAutoStore(false); + db.begin(); + + OQLQuery oql = db.getOQLQuery( + "SELECT obj FROM " + Parameter.class.getName() + " obj " + + "WHERE person.oid = $1 AND idSys = $2"); + oql.bind(acme.getOid()); + oql.bind("INVSEQ"); + QueryResults results = oql.execute(); + + Parameter invoiceSeq = null; + if (results.hasMore()) { + invoiceSeq = (Parameter) results.nextElement(); + } + int newInvoiceSeq = invoiceSeq.getIntValue().intValue() + 1; + invoiceSeq.setIntValue(new Integer(newInvoiceSeq)); + invoice.setNumber(invoiceSeq.getIntValue()); + + oql.close(); + results.close(); + + db.update(motorcycle); + db.create(invoice); + + db.commit(); + + db.begin(); + motorcycle = db.load(Motorcycle.class, "AAAAMT03"); + db.commit(); + + assertEquals(motorcycle.getHolder().getName(), judge.getName()); + + db.close(); + } + public void testQuerySameInvoiceSubsequent() throws Exception { // first try executeQuery(); @@ -155,9 +324,12 @@ } public void testQuerySameInvoiceMT() throws Exception { + // initialize JDOManager once before starting threads. + getJDOManager(DBNAME, MAPPING).getDatabase(); + CPAThreadedTestRunnable[] tcr = new CPAThreadedTestRunnable[10]; for (int i = 0; i < tcr.length; i++) { - tcr[i] = new TestReferenceRunnable(this); + tcr[i] = new TestReferenceRunnable(this, false); } runTestRunnables(tcr); } @@ -180,7 +352,45 @@ db.commit(); db.close(); + + assertTrue(invoice != null); + } + + public void testQuerySameInvoiceReadOnlyMT() throws Exception { + // initialize JDOManager once before starting threads. + getJDOManager(DBNAME_RO, MAPPING_RO).getDatabase(); + + CPAThreadedTestRunnable[] tcr = new CPAThreadedTestRunnable[10]; + for (int i = 0; i < tcr.length; i++) { + tcr[i] = new TestReferenceRunnable(this, true); + } + runTestRunnables(tcr); + } + + protected void executeQueryReadOnly() throws Exception { + Invoice invoice = null; + Database db = getJDOManager(DBNAME_RO, MAPPING_RO).getDatabase(); + db.begin(); + + String oql = "SELECT obj FROM " + Invoice.class.getName() + " obj " + + "WHERE emitter = $1 and invoiceItem.quantity > $2"; + OQLQuery query = db.getOQLQuery(oql); + query.bind("AAAAacme"); + query.bind(new Integer(0)); + QueryResults results = query.execute(Database.READONLY); + int invoiceCount = 0; + while (results.hasMore()) { + invoice = (Invoice) results.nextElement(); + invoiceCount++; + } + results.close(); + query.close(); + + db.commit(); + db.close(); + assertTrue(invoice != null); + assertTrue(invoiceCount == 2); } } Index: cpactf/src/test/java/org/castor/cpa/test/test2861/TestReferenceRunnable.java =================================================================== --- cpactf/src/test/java/org/castor/cpa/test/test2861/TestReferenceRunnable.java (revision 8508) +++ cpactf/src/test/java/org/castor/cpa/test/test2861/TestReferenceRunnable.java (working copy) @@ -9,13 +9,19 @@ @Ignore public class TestReferenceRunnable extends CPAThreadedTestRunnable { private final TestReferenceChange _test; + private boolean _readOnly; - public TestReferenceRunnable(final TestReferenceChange test) { + public TestReferenceRunnable(final TestReferenceChange test, boolean readOnly) { _test = test; + _readOnly = readOnly; } @Override public void runTest() throws Throwable { - _test.executeQuery(); + if (_readOnly) { + _test.executeQueryReadOnly(); + } else { + _test.executeQuery(); + } } } Index: cpactf/src/test/resources/org/castor/cpa/test/test2861/mappingReadOnly.xml =================================================================== --- cpactf/src/test/resources/org/castor/cpa/test/test2861/mappingReadOnly.xml (revision 0) +++ cpactf/src/test/resources/org/castor/cpa/test/test2861/mappingReadOnly.xml (revision 0) @@ -0,0 +1,141 @@ + + + + + Person + + + + + + + + + + + + + + + NaturalPerson + + + + + + + + + + + + LegalPerson + + + + + + + + + + + + Product + + + + + + + + + + + + Motorcycle + + + + + + + + + + + + + + + + + + Invoice + + + + + + + + + + + + + + + + + + + + + org.castor.cpa.test.test2861.InvoiceItem + + + + + + + + + + + + + + + + + + + + + + + + Invoice + + + + + + + + + + + + + + + + \ No newline at end of file Index: src/doc/release-notes.xml =================================================================== --- src/doc/release-notes.xml (revision 8525) +++ src/doc/release-notes.xml (working copy) @@ -34,6 +34,26 @@ + + + Polymorphism Issues with ClassMolder.load(). + + + Clovis Wichoski + clovis@supridatta.com.br + + + Ralf Joachim + ralf.joachim@syscon.eu + + + August Detlefsen + augustd@codemagi.com + + Bug + JDO + 20100131 + Reset resolvers only once after all ClassMolders are initialized.