Index: test.sh =================================================================== RCS file: /scm/castor/castor/test.sh,v retrieving revision 1.2 diff -u -r1.2 test.sh --- test.sh 9 Mar 2005 20:45:43 -0000 1.2 +++ test.sh 26 Aug 2005 13:45:28 -0000 @@ -18,7 +18,7 @@ CLASSPATH=`echo lib/*.jar | tr ' ' ':'`:$CLASSPATH CLASSPATH=`echo lib/tests/*.jar | tr ' ' ':'`:$CLASSPATH -$JAVA -cp $CLASSPATH Main -execute $1 $2 $3 $4 $5 $6 +$JAVA -Djava.net.preferIPv4Stack=true -cp $CLASSPATH Main -execute $1 $2 $3 $4 $5 $6 Index: src/bugs/AllTests.java =================================================================== RCS file: /scm/castor/castor/src/bugs/AllTests.java,v retrieving revision 1.2 diff -u -r1.2 AllTests.java --- src/bugs/AllTests.java 7 Apr 2005 20:52:19 -0000 1.2 +++ src/bugs/AllTests.java 26 Aug 2005 13:45:28 -0000 @@ -7,7 +7,7 @@ suite.addTestSuite(jdo.template.TestTemplate.class); // suite.addTestSuite(jdo.bug1900.TestFieldWithSpace.class); - + suite.addTestSuite(jdo.bug1196.TestLongTransaction.class); return suite; } Index: src/main/org/castor/persist/resolver/PersistanceCapableRelationResolver.java =================================================================== RCS file: /scm/castor/castor/src/main/org/castor/persist/resolver/PersistanceCapableRelationResolver.java,v retrieving revision 1.1 diff -u -r1.1 PersistanceCapableRelationResolver.java --- src/main/org/castor/persist/resolver/PersistanceCapableRelationResolver.java 22 Aug 2005 16:29:05 -0000 1.1 +++ src/main/org/castor/persist/resolver/PersistanceCapableRelationResolver.java 26 Aug 2005 13:45:33 -0000 @@ -258,33 +258,60 @@ /* (non-Javadoc) * @see org.castor.persist.resolver.ResolverStrategy#update(org.castor.persist.TransactionContext, org.exolab.castor.persist.OID, java.lang.Object, org.exolab.castor.mapping.AccessMode, java.lang.Object) */ - public void update(TransactionContext tx, OID oid, Object object, AccessMode suggestedAccessMode, Object field) + public void update(TransactionContext tx, OID oid, Object object, AccessMode suggestedAccessMode, Object oldValue) throws PersistenceException, ObjectModifiedException { - ClassMolder fieldClassMolder = fieldMolder.getFieldClassMolder(); LockEngine fieldEngine = fieldMolder.getFieldLockEngine(); - Object o = fieldMolder.getValue( object, tx.getClassLoader() ); - if ( fieldMolder.isDependent() ) { + Object fieldNewValue = fieldMolder.getValue( object, tx.getClassLoader() ); + + //get fieldClassMolder based on fieldNewValue with this we get correct + //ClassMolder based on value, for class hierarchy. + //e.g.: the field Class is Product but value is Computer + ClassMolder fieldClassMolder = null; + if (fieldNewValue == null) { + fieldClassMolder = fieldMolder.getFieldClassMolder(); + } else { + fieldClassMolder = fieldEngine.getClassMolder(fieldNewValue.getClass()); + // TODO [CW]: fieldEngine.getClassMolder returns null when fieldNewValue is dependent + // but note the referer points to a dependent of another class + // e.g.: B depends A and C references B. + // the trouble occurs on C field, follow the temporary workarround + if (fieldClassMolder == null) { + fieldClassMolder = fieldMolder.getFieldClassMolder(); + } + } + + if ( fieldMolder.isDependent() ) { // depedent class won't have persistenceInfo in LockEngine // must look at fieldMolder for it - if ( o != null && !tx.isRecorded(o) ) - tx.markUpdate( fieldEngine, fieldClassMolder, o, oid ); + if ( fieldNewValue != null && !tx.isRecorded(fieldNewValue) ) + tx.markUpdate( fieldEngine, fieldClassMolder, fieldNewValue, oid ); // load the cached dependent object from the data store. // The loaded will be compared with the new one - if ( field != null ) { + if ( oldValue != null ) { ProposedObject proposedValue = new ProposedObject (); - tx.load(fieldEngine, fieldClassMolder, field, proposedValue, suggestedAccessMode); + tx.load(fieldEngine, fieldClassMolder, oldValue, proposedValue, suggestedAccessMode); } } else if ( tx.isAutoStore() ) { - if ( o != null && !tx.isRecorded(o) ) - tx.markUpdate( fieldEngine, fieldClassMolder, o, null ); + if ( fieldNewValue != null && !tx.isRecorded(fieldNewValue) ) + tx.markUpdate( fieldEngine, fieldClassMolder, fieldNewValue, null ); - if ( field != null ) { + if ( oldValue != null ) { ProposedObject proposedValue = new ProposedObject (); - tx.load(fieldEngine, fieldClassMolder, field, proposedValue, suggestedAccessMode); + tx.load(fieldEngine, fieldClassMolder, oldValue, proposedValue, suggestedAccessMode); } + } else if (fieldNewValue != null) { + Object oIdentity = fieldClassMolder.getIdentity(tx,fieldNewValue); + if ( oIdentity != null + && ((oldValue == null) || !oldValue.equals(oIdentity)) + && !tx.isRecorded(fieldNewValue) + && !fieldClassMolder.hasBackReferenceTo(object.getClass())) { + ProposedObject proposedValue = new ProposedObject(); + tx.load(fieldEngine, fieldClassMolder, oIdentity, proposedValue,suggestedAccessMode); + fieldMolder.setValue(object,proposedValue.getObject(),tx.getClassLoader()); + } } } Index: src/main/org/exolab/castor/persist/ClassMolder.java =================================================================== RCS file: /scm/castor/castor/src/main/org/exolab/castor/persist/ClassMolder.java,v retrieving revision 1.28 diff -u -r1.28 ClassMolder.java --- src/main/org/exolab/castor/persist/ClassMolder.java 23 Aug 2005 02:01:55 -0000 1.28 +++ src/main/org/exolab/castor/persist/ClassMolder.java 26 Aug 2005 13:45:37 -0000 @@ -495,6 +495,28 @@ } /** + * Checks if data object represented by this ClassMolder, has a reference to a referer + * @param referer is a Class that references the data object of this ClassMolder + */ + public boolean hasBackReferenceTo(Class refererClass) { + ClassMolder fieldClassMolder; + int fieldType; + + for ( int i=0; i<_fhs.length; i++ ) { + fieldType = _fhs[i].getFieldType(); + fieldClassMolder = _fhs[i].getFieldClassMolder(); + switch (fieldType) { + case FieldMolder.PERSISTANCECAPABLE: + if (fieldClassMolder.isAssignableFrom(refererClass)) + return true; + + } + } + return false; + } + + + /** * Load an object with specified identity from the persistent storage. * * @param tx the TransactionContext in action @@ -877,15 +899,15 @@ ClassMolder fieldClassMolder; LockEngine fieldEngine; - Object[] fields; + Object[] objectOldValues; Object stamp; int fieldType; - Object o; + Object fieldNewValue; AccessMode accessMode = getAccessMode( suggestedAccessMode ); resetResolvers(); - fields = (Object[]) locker.getObject( tx ); + objectOldValues = (Object[]) locker.getObject( tx ); if ((!isDependent()) && (!_timeStampable)) { throw new IllegalArgumentException("A master object that involves in a long transaction must be a TimeStampable!"); @@ -911,27 +933,27 @@ if ( _timeStampable && objectTimestamp != lockTimestamp ) throw new ObjectModifiedException("Timestamp mismatched!"); - if ( !_timeStampable && isDependent() && fields == null ) { + if ( !_timeStampable && isDependent() && objectOldValues == null ) { // allow a dependent object not implements timeStampable - fields = new Object[_fhs.length]; + objectOldValues = new Object[_fhs.length]; Connection conn = tx.getConnection(oid.getLockEngine()); ProposedObject proposedObject = new ProposedObject(); proposedObject.setProposedClass(object.getClass()); proposedObject.setObject(object); - proposedObject.setFields(fields); + proposedObject.setFields(objectOldValues); stamp = _persistence.load(conn, proposedObject, oid.getIdentity(), accessMode); - fields = proposedObject.getFields(); + objectOldValues = proposedObject.getFields(); oid.setDbLock( accessMode == AccessMode.DbLocked ); - locker.setObject( tx, fields ); + locker.setObject( tx, objectOldValues ); } // load the original field into the transaction. so, store will // have something to compare later. try { for ( int i=0; i <_fhs.length; i++ ) { - _resolvers[i].update(tx, oid, object, accessMode, fields[i]); + _resolvers[i].update(tx, oid, object, accessMode, objectOldValues[i]); } } catch ( ObjectNotFoundException e ) { e.printStackTrace(); @@ -954,10 +976,21 @@ case FieldMolder.PERSISTANCECAPABLE: // create dependent object if exists - fieldClassMolder = _fhs[i].getFieldClassMolder(); + //fieldClassMolder = _fhs[i].getFieldClassMolder(); fieldEngine = _fhs[i].getFieldLockEngine(); - o = _fhs[i].getValue( object, tx.getClassLoader() ); - if ( o != null ) { + fieldNewValue = _fhs[i].getValue( object, tx.getClassLoader() ); + if ( fieldNewValue != null ) { + //get fieldClassMolder based on fieldNewValue with this we get correct + //ClassMolder based on value, for class hierarchy. + //e.g.: the field Class is Product but value is Computer + fieldClassMolder = fieldEngine.getClassMolder(fieldNewValue.getClass()); + // TODO [CW]: fieldEngine.getClassMolder returns null when fieldNewValue is dependent + // but note the referer points to a dependent of another class + // e.g.: B depends A and C references B. + // the trouble occurs on C field, follow the temporary workarround + if (fieldClassMolder == null) { + fieldClassMolder = _fhs[i].getFieldClassMolder(); + } if ( _fhs[i].isDependent() ) { // creation of dependent object should be delayed to the // preStore state. @@ -969,8 +1002,8 @@ // the only disadvantage for that appoarch is that an // OQL Query will not able to include the newly generated // dependent object. - if ( !tx.isRecorded( o ) ) { - tx.markCreate( fieldEngine, fieldClassMolder, o, oid ); + if ( !tx.isRecorded( fieldNewValue ) ) { + tx.markCreate( fieldEngine, fieldClassMolder, fieldNewValue, oid ); if ( !_fhs[i].isStored() && fieldClassMolder._isKeyGenUsed ) { updateCache = true; } @@ -980,12 +1013,12 @@ // if ( !tx.isDepended( oid, o ) ) // throw new PersistenceException("Dependent object may not change its master. Object: "+o+" new master: "+oid); } else if ( tx.isAutoStore() ) { - if ( !tx.isRecorded( o ) ) { + if ( !tx.isRecorded( fieldNewValue ) ) { // related object should be created right the way, if autoStore // is enabled, to obtain a database lock on the row. If both side // uses keygenerator, the current object will be updated in the // store state. - boolean creating = tx.markUpdate( fieldEngine, fieldClassMolder, o, null ); + boolean creating = tx.markUpdate( fieldEngine, fieldClassMolder, fieldNewValue, null ); // if _fhs[i].isStore is true for this field, // and if key generator is used // and if the related object is replaced this object by null @@ -1003,6 +1036,23 @@ updateCache = true; } } + } else { + // TODO [CW]: the test case ctf.jdo.tc2x.TestDependentKeyGenUpdate.test(TestDependentKeyGenUpdate.java:379) + // fails with NullPointerException if fieldClassMolder is null, because what a + // fieldClassMolder is null? this is the work around, i will only log + try { + Object oIdentity = fieldClassMolder.getIdentity(tx,fieldNewValue); + if ( oIdentity != null + && ((objectOldValues[i] == null) || !objectOldValues[i].equals(oIdentity)) + && !tx.isRecorded(fieldNewValue) + && !fieldClassMolder.hasBackReferenceTo(object.getClass())) { + ProposedObject proposedValue = new ProposedObject(); + tx.load(fieldEngine, fieldClassMolder, oIdentity, proposedValue,suggestedAccessMode); + _fhs[i].setValue(object,proposedValue.getObject(),tx.getClassLoader()); + } + } catch (NullPointerException nullException) { + _log.warn("field "+_fhs[i]+" of object "+object+" dont have fieldClassMolder!",nullException); + } } } break; @@ -1011,9 +1061,9 @@ // create dependent objects if exists fieldClassMolder = _fhs[i].getFieldClassMolder(); fieldEngine = _fhs[i].getFieldLockEngine(); - o = _fhs[i].getValue( object, tx.getClassLoader() ); - if ( o != null ) { - Iterator itor = ClassMolderHelper.getIterator( o ); + fieldNewValue = _fhs[i].getValue( object, tx.getClassLoader() ); + if ( fieldNewValue != null ) { + Iterator itor = ClassMolderHelper.getIterator( fieldNewValue ); while (itor.hasNext()) { Object oo = itor.next(); if ( _fhs[i].isDependent() ) { @@ -1042,9 +1092,9 @@ // create relation if the relation table fieldClassMolder = _fhs[i].getFieldClassMolder(); fieldEngine = _fhs[i].getFieldLockEngine(); - o = _fhs[i].getValue( object, tx.getClassLoader() ); - if ( o != null ) { - Iterator itor = ClassMolderHelper.getIterator( o ); + fieldNewValue = _fhs[i].getValue( object, tx.getClassLoader() ); + if ( fieldNewValue != null ) { + Iterator itor = ClassMolderHelper.getIterator( fieldNewValue ); // many-to-many relation is never dependent relation while (itor.hasNext()) { Object oo = itor.next(); Index: src/tests/jdo/sapdb.xml =================================================================== RCS file: /scm/castor/castor/src/tests/jdo/sapdb.xml,v retrieving revision 1.6 diff -u -r1.6 sapdb.xml --- src/tests/jdo/sapdb.xml 7 Aug 2005 09:55:01 -0000 1.6 +++ src/tests/jdo/sapdb.xml 26 Aug 2005 13:45:38 -0000 @@ -3,9 +3,9 @@ "http://castor.exolab.org/jdo-conf.dtd"> - - - + + + + + + + + + + + + + + Index: src/bugs/jdo/bug1196/mapping.xml =================================================================== RCS file: src/bugs/jdo/bug1196/mapping.xml diff -N src/bugs/jdo/bug1196/mapping.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/bugs/jdo/bug1196/mapping.xml 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,138 @@ + + + + + + + Country + + + + + + + + + + + + + State Country + + + + + + + + + + + + + + + + + Product + + + + + + + + + + + + + Computer + + + + + + + + + + + + + + + + + Order + + + + + + + + + + + + + + + + + OrderItem + + + + + + + + + + + + + + + + + + + + + Car + + + + + + + + + + + + + + + + + Driver + + + + + + + + + + + + + + + Index: src/bugs/jdo/bug1196/mysql.sql =================================================================== RCS file: src/bugs/jdo/bug1196/mysql.sql diff -N src/bugs/jdo/bug1196/mysql.sql --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/bugs/jdo/bug1196/mysql.sql 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,84 @@ +DROP TABLE if exists STATECOUNTRY; + +DROP TABLE if exists COUNTRY; + +CREATE TABLE COUNTRY ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + PRIMARY KEY (OID) +); + +CREATE TABLE STATECOUNTRY ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + MY_COUNTRY VARCHAR(8) NOT NULL, + PRIMARY KEY (OID) +); + +ALTER TABLE STATECOUNTRY ADD FOREIGN KEY (MY_COUNTRY) REFERENCES COUNTRY(OID); + +DROP TABLE if exists COMPUTER; + +DROP TABLE if exists ORDERITEM; + +DROP TABLE if exists PRODUCT; + +DROP TABLE if exists TBORDER; + +CREATE TABLE TBORDER ( + OID VARCHAR(8) NOT NULL, + NUMBER_OF INTEGER NOT NULL, + PRIMARY KEY (OID) +); + +CREATE TABLE PRODUCT ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + PRIMARY KEY (OID) +); + +CREATE TABLE ORDERITEM ( + OID VARCHAR(8) NOT NULL, + QUANTITY INTEGER NOT NULL, + MY_PRODUCT VARCHAR(8) NOT NULL, + MY_ORDER VARCHAR(8) NOT NULL, + PRIMARY KEY (OID) +); + +ALTER TABLE ORDERITEM ADD FOREIGN KEY (MY_PRODUCT) REFERENCES PRODUCT(OID); + +ALTER TABLE ORDERITEM ADD FOREIGN KEY (MY_ORDER) REFERENCES TBORDER(OID); + +CREATE TABLE COMPUTER ( + OID VARCHAR(8) NOT NULL, + SERIAL_NUMBER VARCHAR(20) NOT NULL, + MY_ORDER_ITEM VARCHAR(8), + PRIMARY KEY (OID) +); + +ALTER TABLE COMPUTER ADD FOREIGN KEY (OID) REFERENCES PRODUCT(OID); + +ALTER TABLE COMPUTER ADD FOREIGN KEY (MY_ORDER_ITEM) REFERENCES ORDERITEM(OID); + +DROP TABLE if exists CAR; + +DROP TABLE if exists DRIVER; + +CREATE TABLE CAR ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + MY_DRIVER VARCHAR(8), + PRIMARY KEY (OID) +); + +CREATE TABLE DRIVER ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + MY_CURRENT_CAR VARCHAR(8), + PRIMARY KEY (OID) +); + +ALTER TABLE CAR ADD FOREIGN KEY (MY_DRIVER) REFERENCES DRIVER(OID); + +ALTER TABLE DRIVER ADD FOREIGN KEY (MY_CURRENT_CAR) REFERENCES CAR(OID); + Index: src/bugs/jdo/bug1196/sapdb.sql =================================================================== RCS file: src/bugs/jdo/bug1196/sapdb.sql diff -N src/bugs/jdo/bug1196/sapdb.sql --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/bugs/jdo/bug1196/sapdb.sql 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,85 @@ +DROP TABLE COUNTRY +// +CREATE TABLE COUNTRY ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + PRIMARY KEY (OID) +) +// +DROP TABLE STATECOUNTRY +// +CREATE TABLE STATECOUNTRY ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + MY_COUNTRY VARCHAR(8) NOT NULL, + PRIMARY KEY (OID) +) +// +ALTER TABLE STATECOUNTRY ADD FOREIGN KEY (MY_COUNTRY) REFERENCES COUNTRY(OID) +// +DROP TABLE PRODUCT +// +CREATE TABLE PRODUCT ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + PRIMARY KEY (OID) +) +// +DROP TABLE COMPUTER +// +CREATE TABLE COMPUTER ( + OID VARCHAR(8) NOT NULL, + SERIAL_NUMBER VARCHAR(20) NOT NULL, + MY_ORDER_ITEM VARCHAR(8), + PRIMARY KEY (OID) +) +// +ALTER TABLE COMPUTER ADD FOREIGN KEY (OID) REFERENCES PRODUCT(OID) +// +ALTER TABLE COMPUTER ADD FOREIGN KEY (MY_ORDER_ITEM) REFERENCES ORDERITEM(OID) +// +DROP TABLE TBORDER +// +CREATE TABLE TBORDER ( + OID VARCHAR(8) NOT NULL, + NUMBER_OF INTEGER NOT NULL, + PRIMARY KEY (OID) +) +// +DROP TABLE ORDERITEM +// +CREATE TABLE ORDERITEM ( + OID VARCHAR(8) NOT NULL, + QUANTITY INTEGER NOT NULL, + MY_PRODUCT VARCHAR(8) NOT NULL, + MY_ORDER VARCHAR(8) NOT NULL, + PRIMARY KEY (OID) +) +// +ALTER TABLE ORDERITEM ADD FOREIGN KEY (MY_PRODUCT) REFERENCES PRODUCT(OID) +// +ALTER TABLE ORDERITEM ADD FOREIGN KEY (MY_ORDER) REFERENCES TBORDER(OID) +// + +DROP TABLE CAR +// +CREATE TABLE CAR ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + MY_DRIVER VARCHAR(8), + PRIMARY KEY (OID) +) +// +DROP TABLE DRIVER +// +CREATE TABLE DRIVER ( + OID VARCHAR(8) NOT NULL, + NAME VARCHAR(60) NOT NULL, + MY_CURRENT_CAR VARCHAR(8), + PRIMARY KEY (OID) +) +// +ALTER TABLE CAR ADD FOREIGN KEY (MY_DRIVER) REFERENCES DRIVER(OID) +// +ALTER TABLE DRIVER ADD FOREIGN KEY (MY_CURRENT_CAR) REFERENCES CAR(OID) +//