Thanks Gordon for your kind answer.
I modified the fix as we discussed and moved tests into
EntityManagerJUnitTestSuite.
There was no failure in entity-persistence-tests and quick look tests.
Please review and check in if it's OK. I hope this is the final :-)
Diffs are below and modified sources are in the attached file.
Regards
- Wonseok
[entity-persistence]
Index:
src/java/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java
===================================================================
RCS file:
/cvs/glassfish/entity-persistence/src/java/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java,v
retrieving revision 1.15
diff -c -r1.15 ObjectBuilder.java
***
src/java/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java
18 Aug 2006 17:59:26 -0000 1.15
---
src/java/oracle/toplink/essentials/internal/descriptors/ObjectBuilder.java
29 Sep 2006 00:48:17 -0000
***************
*** 2052,2061 ****
* This merge also prevents the extra step of calculating the changes
when it is not required.
*/
public void mergeIntoObject(Object target, boolean isUnInitialized,
Object source, MergeManager mergeManager) {
// PERF: Avoid synchronized enumerator as is concurrency
bottleneck.
Vector mappings = getDescriptor().getMappings();
for (int index = 0; index < mappings.size(); index++) {
! ((DatabaseMapping)mappings.get(index)).mergeIntoObject(target,
isUnInitialized, source, mergeManager);
}
// PERF: Avoid events if no listeners.
--- 2052,2077 ----
* This merge also prevents the extra step of calculating the changes
when it is not required.
*/
public void mergeIntoObject(Object target, boolean isUnInitialized,
Object source, MergeManager mergeManager) {
+ mergeIntoObject(target, isUnInitialized, source, mergeManager,
false);
+ }
+
+ /**
+ * INTERNAL:
+ * Merge the contents of one object into another, this merge algorthim
is dependent on the merge manager.
+ * This merge also prevents the extra step of calculating the changes
when it is not required.
+ * If 'cascadeOnly' is true, only foreign reference mappings are
merged.
+ */
+ public void mergeIntoObject(Object target, boolean isUnInitialized,
Object source, MergeManager mergeManager, boolean cascadeOnly) {
+ // cascadeOnly is introduced to optimize merge
+ // for GF#1139 Cascade merge operations to relationship mappings
even if already registered
+
// PERF: Avoid synchronized enumerator as is concurrency
bottleneck.
Vector mappings = getDescriptor().getMappings();
for (int index = 0; index < mappings.size(); index++) {
! DatabaseMapping mapping =
(DatabaseMapping)mappings.get(index);
! if(!cascadeOnly || mapping.isForeignReferenceMapping()){
! mapping.mergeIntoObject(target, isUnInitialized, source,
mergeManager);
! }
}
// PERF: Avoid events if no listeners.
Index:
src/java/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java
===================================================================
RCS file:
/cvs/glassfish/entity-persistence/src/java/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java,v
retrieving revision 1.23
diff -c -r1.23 EntityManagerImpl.java
***
src/java/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java
2 Sep 2006 00:15:37 -0000 1.23
---
src/java/oracle/toplink/essentials/internal/ejb/cmp3/base/EntityManagerImpl.java
29 Sep 2006 00:48:17 -0000
***************
*** 195,201 ****
throw new IllegalArgumentException(
ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new
Object[]{entity}));
}
try {
! return
getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity,
MergeManager.CASCADE_BY_MAPPING);
} catch (
oracle.toplink.essentials.exceptions.OptimisticLockException ole) {
throw new javax.persistence.OptimisticLockException(ole);
}
--- 195,201 ----
throw new IllegalArgumentException(
ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new
Object[]{entity}));
}
try {
! return
getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity,
MergeManager.CASCADE_BY_MAPPING, true);
} catch (
oracle.toplink.essentials.exceptions.OptimisticLockException ole) {
throw new javax.persistence.OptimisticLockException(ole);
}
Index:
src/java/oracle/toplink/essentials/internal/sessions/MergeManager.java
===================================================================
RCS file:
/cvs/glassfish/entity-persistence/src/java/oracle/toplink/essentials/internal/sessions/MergeManager.java,v
retrieving revision 1.6
diff -c -r1.6 MergeManager.java
***
src/java/oracle/toplink/essentials/internal/sessions/MergeManager.java 20
Apr 2006 20:32:05 -0000 1.6
---
src/java/oracle/toplink/essentials/internal/sessions/MergeManager.java 29
Sep 2006 00:48:18 -0000
***************
*** 82,87 ****
--- 82,91 ----
protected long systemTime = 0;// stored so that all objects merged by
a merge manager can have the same readTime
public static boolean LOCK_ON_MERGE = true;
+ /** Force cascade merge even if a clone is already registered */
+ // GF#1139 Cascade doesn't work when merging managed entity
+ protected boolean forceCascade;
+
public MergeManager(AbstractSession session) {
this.session = session;
this.objectsAlreadyMerged = new IdentityHashtable();
***************
*** 387,402 ****
ClassDescriptor descriptor = getSession().getDescriptor(rmiClone);
Object registeredObject =
registerObjectForMergeCloneIntoWorkingCopy(rmiClone);
! if (registeredObject == rmiClone) {
//need to find better better fix. prevents merging into
itself.
return rmiClone;
}
- boolean changeTracked = false;
try {
ObjectBuilder builder = descriptor.getObjectBuilder();
! if (descriptor.usesVersionLocking()) {
VersionLockingPolicy policy = (VersionLockingPolicy)
descriptor.getOptimisticLockingPolicy();
if (policy.isStoredInObject()) {
Object currentValue =
builder.extractValueFromObjectForField(registeredObject,
policy.getWriteLockField(), session);
--- 391,405 ----
ClassDescriptor descriptor = getSession().getDescriptor(rmiClone);
Object registeredObject =
registerObjectForMergeCloneIntoWorkingCopy(rmiClone);
! if (registeredObject == rmiClone && !shouldForceCascade()) {
//need to find better better fix. prevents merging into
itself.
return rmiClone;
}
try {
ObjectBuilder builder = descriptor.getObjectBuilder();
! if (registeredObject != rmiClone &&
descriptor.usesVersionLocking()) {
VersionLockingPolicy policy = (VersionLockingPolicy)
descriptor.getOptimisticLockingPolicy();
if (policy.isStoredInObject()) {
Object currentValue =
builder.extractValueFromObjectForField(registeredObject,
policy.getWriteLockField(), session);
***************
*** 410,417 ****
// Toggle change tracking during the merge.
descriptor.getObjectChangePolicy
().dissableEventProcessing(registeredObject);
// Merge into the clone from the original, use clone as backup
as anything different should be merged.
! builder.mergeIntoObject(registeredObject, false, rmiClone,
this);
} finally {
descriptor.getObjectChangePolicy
().enableEventProcessing(registeredObject);
}
--- 413,425 ----
// Toggle change tracking during the merge.
descriptor.getObjectChangePolicy
().dissableEventProcessing(registeredObject);
+ boolean cascadeOnly = false;
+ if(registeredObject == rmiClone){
+ // GF#1139 Cascade merge operations to relationship
mappings even if already registered
+ cascadeOnly = true;
+ }
// Merge into the clone from the original, use clone as backup
as anything different should be merged.
! builder.mergeIntoObject(registeredObject, false, rmiClone,
this, cascadeOnly);
} finally {
descriptor.getObjectChangePolicy
().enableEventProcessing(registeredObject);
}
***************
*** 693,698 ****
--- 701,710 ----
this.mergePolicy = mergePolicy;
}
+ public void setForceCascade(boolean forceCascade) {
+ this.forceCascade = forceCascade;
+ }
+
public void setObjectDescriptors(IdentityHashtable objectDescriptors)
{
this.objectDescriptors = objectDescriptors;
}
***************
*** 817,821 ****
--- 829,840 ----
*/
public boolean shouldRefreshRemoteObject() {
return getMergePolicy() == REFRESH_REMOTE_OBJECT;
+ }
+
+ /**
+ * This is used to cascade merge even if a clone is already
registered.
+ */
+ public boolean shouldForceCascade() {
+ return forceCascade;
}
}
Index:
src/java/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java
===================================================================
RCS file:
/cvs/glassfish/entity-persistence/src/java/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java,v
retrieving revision 1.14
diff -c -r1.14 UnitOfWorkImpl.java
***
src/java/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java
18 Jul 2006 20:23:02 -0000 1.14
---
src/java/oracle/toplink/essentials/internal/sessions/UnitOfWorkImpl.java
29 Sep 2006 00:48:19 -0000
***************
*** 2665,2671 ****
* @see #deepMergeClone(Object)
*/
public Object mergeCloneWithReferences(Object rmiClone, int
cascadePolicy) {
!
if (rmiClone == null) {
return null;
}
--- 2665,2689 ----
* @see #deepMergeClone(Object)
*/
public Object mergeCloneWithReferences(Object rmiClone, int
cascadePolicy) {
! return mergeCloneWithReferences(rmiClone, cascadePolicy, false);
! }
!
! /**
! * INTERNAL:
! * Merge the attributes of the clone into the unit of work copy.
! * This can be used for objects that are returned from the client
through
! * RMI serialization (or another serialization mechanism), because the
RMI object
! * will be a clone this will merge its attributes correctly to
preserve object
! * identity within the unit of work and record its changes.
! *
! * The object and its private owned parts are merged. This will
include references from
! * dependent objects to independent objects.
! *
! * @return the registered version for the clone being merged.
! * @see #shallowMergeClone(Object)
! * @see #deepMergeClone(Object)
! */
! public Object mergeCloneWithReferences(Object rmiClone, int
cascadePolicy, boolean forceCascade) {
if (rmiClone == null) {
return null;
}
***************
*** 2686,2691 ****
--- 2704,2710 ----
MergeManager manager = new MergeManager(this);
manager.mergeCloneWithReferencesIntoWorkingCopy();
manager.setCascadePolicy(cascadePolicy);
+ manager.setForceCascade(forceCascade);
Object mergedObject = manager.mergeChanges(implementation, null);
if (isSmartMerge()) {
return builder.wrapObject(mergedObject, this);
[entity-persistence-tests]
Index:
src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java
===================================================================
RCS file:
/cvs/glassfish/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java,v
retrieving revision 1.10
diff -c -r1.10 Employee.java
***
src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java
8 Sep 2006 18:53:37 -0000 1.10
---
src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Employee.java
29 Sep 2006 00:47:26 -0000
***************
*** 205,211 ****
this.manager = manager;
}
! @ManyToMany(cascade=PERSIST)
@JoinTable(
name="CMP3_EMP_PROJ",
// Default for the project side and specify for the employee side
--- 205,211 ----
this.manager = manager;
}
! @ManyToMany(cascade={PERSIST, MERGE})
@JoinTable(
name="CMP3_EMP_PROJ",
// Default for the project side and specify for the employee side
Index:
src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java
===================================================================
RCS file:
/cvs/glassfish/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java,v
retrieving revision 1.3
diff -c -r1.3 Project.java
***
src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java
20 Jan 2006 15:57:15 -0000 1.3
---
src/java/oracle/toplink/essentials/testing/models/cmp3/advanced/Project.java
29 Sep 2006 00:47:26 -0000
***************
*** 111,117 ****
this.description = description;
}
! @OneToOne
@JoinColumn(name="LEADER_ID")
public Employee getTeamLeader() {
return teamLeader;
--- 111,117 ----
this.description = description;
}
! @OneToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name="LEADER_ID")
public Employee getTeamLeader() {
return teamLeader;
Index:
src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java
===================================================================
RCS file:
/cvs/glassfish/entity-persistence-tests/src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java,v
retrieving revision 1.30
diff -c -r1.30 EntityManagerJUnitTestSuite.java
***
src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java
18 Sep 2006 20:48:29 -0000 1.30
---
src/java/oracle/toplink/essentials/testing/tests/cmp3/advanced/EntityManagerJUnitTestSuite.java
29 Sep 2006 00:47:27 -0000
***************
*** 2556,2561 ****
--- 2556,2697 ----
fail("No exception thrown when primary key update
attempted.");
}
+ // Test cascade merge on a detached entity
+ public void testCascadeMergeDetached() {
+ // setup
+ Project p1 = new Project();
+ p1.setName("Project1");
+ Project p2 = new Project();
+ p1.setName("Project2");
+ Employee e1 = new Employee();
+ e1.setFirstName("Employee1");
+ Employee e2 = new Employee();
+ e2.setFirstName("Employee2");
+
+ EntityManager em = createEntityManager();
+ em.getTransaction().begin();
+ try {
+ em.persist(p1);
+ em.persist(p2);
+ em.persist(e1);
+ em.persist(e2);
+
+ em.getTransaction().commit();
+ } catch (RuntimeException re){
+ em.getTransaction().rollback();
+ throw re;
+ }
+ em.close();
+ // end of setup
+
+ //p1,p2,e1,e2 are detached
+
+ // associate relationships
+ //p1 -> e1 (one-to-one)
+ p1.setTeamLeader(e1);
+ //e1 -> e2 (one-to-many)
+ e1.addManagedEmployee(e2);
+ //e2 -> p2 (many-to-many)
+ e2.addProject(p2);
+ p2.addTeamMember(e2);
+
+ em = createEntityManager();
+ em.getTransaction().begin();
+ try {
+ Project mp1 = em.merge(p1); // cascade merge
+ assertTrue(em.contains(mp1));
+ assertTrue("Managed instance and detached instance must not be
same", mp1 != p1);
+
+ Employee me1 = mp1.getTeamLeader();
+ assertTrue("Cascade merge failed", em.contains(me1));
+ assertTrue("Managed instance and detached instance must not be
same", me1 != e1);
+
+ Employee me2 = me1.getManagedEmployees().iterator().next();
+ assertTrue("Cascade merge failed", em.contains(me2));
+ assertTrue("Managed instance and detached instance must not be
same", me2 != e2);
+
+ Project mp2 = me2.getProjects().iterator().next();
+ assertTrue("Cascade merge failed", em.contains(mp2));
+ assertTrue("Managed instance and detached instance must not be
same", mp2 != p2);
+
+ em.getTransaction().commit();
+ } catch (RuntimeException re){
+ em.getTransaction().rollback();
+ throw re;
+ }
+ em.close();
+ }
+
+ // Test cascade merge on a managed entity
+ // Test for GF#1139 - Cascade doesn't work when merging managed entity
+ public void testCascadeMergeManaged() {
+ // setup
+ Project p1 = new Project();
+ p1.setName("Project1");
+ Project p2 = new Project();
+ p1.setName("Project2");
+ Employee e1 = new Employee();
+ e1.setFirstName("Employee1");
+ Employee e2 = new Employee();
+ e2.setFirstName("Employee2");
+
+ EntityManager em = createEntityManager();
+ em.getTransaction().begin();
+ try {
+ em.persist(p1);
+ em.persist(p2);
+ em.persist(e1);
+ em.persist(e2);
+
+ em.getTransaction().commit();
+ } catch (RuntimeException re){
+ em.getTransaction().rollback();
+ throw re;
+ }
+ em.close();
+ // end of setup
+
+ //p1,p2,e1,e2 are detached
+ em = createEntityManager();
+ em.getTransaction().begin();
+ try {
+ Project mp1 = em.merge(p1);
+ assertTrue(em.contains(mp1));
+ assertTrue("Managed instance and detached instance must not be
same", mp1 != p1);
+
+ //p1 -> e1 (one-to-one)
+ mp1.setTeamLeader(e1);
+ mp1 = em.merge(mp1); // merge again - trigger cascade merge
+
+ Employee me1 = mp1.getTeamLeader();
+ assertTrue("Cascade merge failed", em.contains(me1));
+ assertTrue("Managed instance and detached instance must not be
same", me1 != e1);
+
+ //e1 -> e2 (one-to-many)
+ me1.addManagedEmployee(e2);
+ me1 = em.merge(me1); // merge again - trigger cascade merge
+
+ Employee me2 = me1.getManagedEmployees().iterator().next();
+ assertTrue("Cascade merge failed", em.contains(me2));
+ assertTrue("Managed instance and detached instance must not be
same", me2 != e2);
+
+ //e2 -> p2 (many-to-many)
+ me2.addProject(p2);
+ p2.addTeamMember(me2);
+ me2 = em.merge(me2); // merge again - trigger cascade merge
+
+ Project mp2 = me2.getProjects().iterator().next();
+ assertTrue("Cascade merge failed", em.contains(mp2));
+ assertTrue("Managed instance and detached instance must not be
same", mp2 != p2);
+
+ em.getTransaction().commit();
+ } catch (RuntimeException re){
+ em.getTransaction().rollback();
+ throw re;
+ }
+ em.close();
+ }
+
public static void main(String[] args) {
// Now run JUnit.
junit.swingui.TestRunner.main(args);
On 9/29/06, Gordon Yorke <gordon.yorke_at_oracle.com> wrote:
>
> Hello Wonseok,
> Perhaps this...
> public void mergeIntoObject(Object target, boolean isUnInitialized,
> Object source, MergeManager mergeManager, boolean cascadeOnly) {
> // PERF: Avoid synchronized enumerator as is concurrency
> bottleneck.
> Vector mappings = getDescriptor().getMappings();
> for (int index = 0; index < mappings.size(); index++) {
> DatabaseMapping mapping =
> (DatabaseMapping)mappings.get(index);
> if( !cascadeOnly || mapping.isForeignReferenceMapping()){
> //cascadeOnly = F, frm = X, merge = true;
> //cascadeOnly = T, frm = T, merge = true;
> //cascadeOnly = T, frm = F, merge = false;
> mapping.mergeIntoObject (target, isUnInitialized,
> source, mergeManager);
> }
> }
> }
> This is a much cleaner method that performs optimally without the need for
> duplication of code. Calls to mergeIntoObject that should not be cascaded
> are already checked in mergeIntoObject code before any major work is
> attempted by these methods.
> --Gordon
>
>
> -----Original Message-----
> *From:* Wonseok Kim [mailto:guruwons_at_gmail.com]
> *Sent:* Wednesday, September 27, 2006 6:39 PM
> *To:* persistence_at_glassfish.dev.java.net
> *Subject:* Re1: Fix for issue 1139 "cascade merge"
>
> Gordon,
> Please review below message so I could submit the final fix.
> If you agree with adding a new method "cascadeMergeIntoObject()", I think
> there is no more remained issue.
>
> Thanks
> - Wonseok
>
> On 9/27/06, Wonseok Kim <guruwons_at_gmail.com> wrote:
> >
> > Hello Gordon,
> >
> > Removing the code block will work well, but I thought calling
> > mergeIntoObject() for non-cascade mappings is unnecessary overhead - it does
> > some comparison and invokes reflection call. If an entity has many mappings
> > but there are a few or no cascade mappings the unnecessary cost will
> > increase.
> >
> > How about making a new method "cascadeMergeIntoObject()" which I
> > mentioned in the second fix. Because adding a new flag to mergeIntoObject()
> > something like below does not look good. It looks like making the method
> > ...dirty. :-) Do you have a good idea?
> >
> > public void mergeIntoObject(Object target, boolean isUnInitialized,
> > Object source, MergeManager mergeManager, boolean cascadeOnly) {
> > // PERF: Avoid synchronized enumerator as is concurrency
> > bottleneck.
> > Vector mappings = getDescriptor().getMappings();
> > if(cascadeOnly){
> > for (int index = 0; index < mappings.size(); index++) {
> > DatabaseMapping mapping =
> > (DatabaseMapping)mappings.get(index);
> > if(mapping instanceof ForeignReferenceMapping
> > &&
> > ((ForeignReferenceMapping)mapping).shouldMergeCascadeParts(mergeManager)){
> > mapping.mergeIntoObject (target, isUnInitialized,
> > source, mergeManager);
> > }
> > }
> > } else {
> > for (int index = 0; index < mappings.size(); index++) {
> >
> > ((DatabaseMapping)mappings.get(index)).mergeIntoObject(target,
> > isUnInitialized, source, mergeManager);
> > }
> > }
> >
> > Thanks
> > -Wonseok
> >
> > On 9/27/06, Gordon Yorke <gordon.yorke_at_oracle.com > wrote:
> > >
> > > Hello Wonseok,
> > > Looks good, I would remove the code block :
> > > if(registeredObject == rmiClone){
> > > // GF#1139 Cascade merge operations even if it's
> > > already registered
> > > Vector mappings = descriptor.getMappings();
> > > for (int index = 0; index < mappings.size(); index++)
> > > {
> > > DatabaseMapping mapping =
> > > (DatabaseMapping)mappings.get(index);
> > > if(mapping instanceof ForeignReferenceMapping
> > > &&
> > > ((ForeignReferenceMapping)mapping).shouldMergeCascadeParts(this)){
> > > mapping.mergeIntoObject(registeredObject,
> > > false, rmiClone, this);
> > > }
> > > }
> > > As this is almost identical to what mergeIntoObject does now. I do
> > > not think there is a lot of value in limiting the merge to only foreign
> > > reference mappings but if you prefer to do that then it would be better to
> > > add that behaviour to the ObjectBuilder (perhaps through another flag).
> > > --Gordon
> > >
> > > -----Original Message-----
> > > *From:* Wonseok Kim [mailto:guruwons_at_gmail.com]
> > > *Sent:* Tuesday, September 26, 2006 1:51 AM
> > > *To:* persistence_at_glassfish.dev.java.net
> > > *Subject:* Re: Fix for issue 1139 "cascade merge"
> > >
> > > Hello Gordon,
> > >
> > > Overriding UoW.mergeCloneWithReferences() causes a code duplication,
> > > and introducing a new merge policy CLONE_INTO_WORKING_COPY_FORCE_CASCADE
> > > makes another complexity to maintain this policy.
> > >
> > > Instead of the approach I think introducing a new force-cascade flag
> > > is cleaner solution. Also when I reconsider moving the force-cascade logic
> > > into ObjectBuilder.mergeIntoObject(), it seems not a good idea because
> > > mergeIntoObject() is called by several different places. So I left the logic
> > > in MergeManager.
> > >
> > > Please review below.
> > >
> > > MergeManager:
> > > /** Force cascade merge even if a clone is already registered */
> > > // GF#1139 Cascade doesn't work when merging managed entity
> > > protected boolean forceCascade;
> > > ...
> > > protected Object mergeChangesOfCloneIntoWorkingCopy(Object
> > > rmiClone) {
> > > ClassDescriptor descriptor =
> > > getSession().getDescriptor(rmiClone);
> > > Object registeredObject =
> > > registerObjectForMergeCloneIntoWorkingCopy(rmiClone);
> > >
> > > if (registeredObject == rmiClone && !shouldForceCascade()) {
> > > //need to find better better fix. prevents merging into
> > > itself.
> > > return rmiClone;
> > > }
> > >
> > > try {
> > > ObjectBuilder builder = descriptor.getObjectBuilder();
> > >
> > > if (registeredObject != rmiClone &&
> > > descriptor.usesVersionLocking()) {
> > > VersionLockingPolicy policy = (VersionLockingPolicy)
> > > descriptor.getOptimisticLockingPolicy();
> > > if (policy.isStoredInObject()) {
> > > Object currentValue =
> > > builder.extractValueFromObjectForField(registeredObject,
> > > policy.getWriteLockField(), session);
> > >
> > > if (policy.isNewerVersion(currentValue, rmiClone,
> > > session.keyFromObject(rmiClone), session)) {
> > > throw
> > > OptimisticLockException.objectChangedSinceLastMerge (rmiClone);
> > > }
> > > }
> > > }
> > >
> > > // Toggle change tracking during the merge.
> > > descriptor.getObjectChangePolicy().dissableEventProcessing(registeredObject);
> > >
> > >
> > > if(registeredObject == rmiClone){
> > > // GF#1139 Cascade merge operations even if it's
> > > already registered
> > > Vector mappings = descriptor.getMappings();
> > > for (int index = 0; index < mappings.size(); index++)
> > > {
> > > DatabaseMapping mapping =
> > > (DatabaseMapping)mappings.get(index);
> > > if(mapping instanceof ForeignReferenceMapping
> > > &&
> > > ((ForeignReferenceMapping)mapping).shouldMergeCascadeParts(this)){
> > > mapping.mergeIntoObject(registeredObject,
> > > false, rmiClone, this);
> > > }
> > > }
> > > } else {
> > > // Merge into the clone from the original, use clone
> > > as backup as anything different should be merged.
> > > builder.mergeIntoObject(registeredObject, false,
> > > rmiClone, this);
> > > }
> > > } finally {
> > > descriptor.getObjectChangePolicy
> > > ().enableEventProcessing(registeredObject);
> > > }
> > >
> > > return registeredObject;
> > > }
> > > ...
> > >
> > > To set forceCascade flag, UnitOfWorkImpl and EntityManagerImpl are
> > > modified as follows.
> > >
> > > UnitOfWorkImpl:
> > > public Object mergeCloneWithReferences(Object rmiClone, int
> > > cascadePolicy) {
> > > return mergeCloneWithReferences(rmiClone, cascadePolicy,
> > > false);
> > > }
> > >
> > > ...
> > > public Object mergeCloneWithReferences(Object rmiClone, int
> > > cascadePolicy, boolean forceCascade) {
> > > if (rmiClone == null) {
> > > return null;
> > > }
> > > ClassDescriptor descriptor = getDescriptor(rmiClone);
> > > if ((descriptor == null) || descriptor.isAggregateDescriptor()
> > > || descriptor.isAggregateCollectionDescriptor()) {
> > > if (cascadePolicy == MergeManager.CASCADE_BY_MAPPING){
> > > throw new IllegalArgumentException(
> > > ExceptionLocalization.buildMessage("not_an_entity", new
> > > Object[]{rmiClone}));
> > > }
> > > return rmiClone;
> > > }
> > >
> > > //CR#2272
> > > logDebugMessage(rmiClone, "merge_clone_with_references");
> > >
> > > ObjectBuilder builder = descriptor.getObjectBuilder ();
> > > Object implementation = builder.unwrapObject(rmiClone, this);
> > >
> > > MergeManager manager = new MergeManager(this);
> > > manager.mergeCloneWithReferencesIntoWorkingCopy();
> > > manager.setCascadePolicy (cascadePolicy);
> > > manager.setForceCascade(forceCascade);
> > > Object mergedObject = manager.mergeChanges(implementation,
> > > null);
> > > if (isSmartMerge()) {
> > > return builder.wrapObject(mergedObject, this);
> > > } else {
> > > return mergedObject;
> > > }
> > > }
> > >
> > > EntityManagerImpl:
> > > protected Object mergeInternal(Object entity){
> > > verifyOpen();
> > > if (entity == null){
> > > throw new IllegalArgumentException(
> > > ExceptionLocalization.buildMessage("not_an_entity", new Object[]
> > > {entity}));
> > > }
> > > //gf830 - merging a removed entity should throw exception
> > > if
> > > (getActivePersistenceContext(checkForTransaction(!isExtended())).getDeletedObjects().contains(entity)){
> > >
> > > throw new IllegalArgumentException(
> > > ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new
> > > Object[]{entity}));
> > > }
> > > try {
> > > return
> > > getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity,
> > > MergeManager.CASCADE_BY_MAPPING, true) ;
> > > } catch (
> > > oracle.toplink.essentials.exceptions.OptimisticLockException ole) {
> > > throw new javax.persistence.OptimisticLockException(ole);
> > > }
> > > }
> > >
> > >
> > > Thanks
> > > -Wonseok
> > >
> > > On 9/26/06, Wonseok Kim <guruwons_at_gmail.com> wrote:
> > > >
> > > > Hello Gordon, thanks for review.
> > > >
> > > > Currently EntityManagerImpl is using UoW.mergeCloneWithReferences()
> > > > instead of mergeClone(). So mergeCloneWithReferences() should be overrided
> > > > as follows. right?
> > > >
> > > > public Object mergeCloneWithReferences(Object rmiClone, int
> > > > cascadePolicy) {
> > > > if (rmiClone == null) {
> > > > return null;
> > > > }
> > > > ClassDescriptor descriptor = getDescriptor(rmiClone);
> > > > if ((descriptor == null) ||
> > > > descriptor.isAggregateDescriptor() ||
> > > > descriptor.isAggregateCollectionDescriptor()) {
> > > > if (cascadePolicy == MergeManager.CASCADE_BY_MAPPING){
> > > > throw new IllegalArgumentException(
> > > > ExceptionLocalization.buildMessage("not_an_entity", new
> > > > Object[]{rmiClone}));
> > > > }
> > > > return rmiClone;
> > > > }
> > > >
> > > > //CR#2272
> > > > // logDebugMessage(rmiClone, "merge_clone_with_references");
> > > >
> > > > ObjectBuilder builder = descriptor.getObjectBuilder();
> > > > Object implementation = builder.unwrapObject(rmiClone,
> > > > this);
> > > >
> > > > MergeManager manager = new MergeManager(this);
> > > > manager.mergeCloneIntoWorkingCopyForceCascade();
> > > > manager.setCascadePolicy(cascadePolicy);
> > > > Object mergedObject = manager.mergeChanges(implementation,
> > > > null);
> > > > if (isSmartMerge()) {
> > > > return builder.wrapObject(mergedObject, this);
> > > > } else {
> > > > return mergedObject;
> > > > }
> > > >
> > > > }
> > > >
> > > > I'm curious, Why mergeCloneWithReferences() code is different from
> > > > mergeClone()?
> > > > It doesn't use SessionProfiler and handleException() as you see.
> > > > Also, to override this private logDebugMessage() should have
> > > > protected access.
> > > >
> > > > And the force-cascade logic which was in MergeManager can go to
> > > > ObjectBuilder for avoding a duplicate like below.
> > > >
> > > > public void mergeIntoObject(Object target, boolean
> > > > isUnInitialized, Object source, MergeManager mergeManager) {
> > > > // PERF: Avoid synchronized enumerator as is concurrency
> > > > bottleneck.
> > > > Vector mappings = getDescriptor().getMappings();
> > > > for (int index = 0; index < mappings.size(); index++) {
> > > > DatabaseMapping mapping =
> > > > (DatabaseMapping)mappings.get(index);
> > > > if(target != source
> > > > || (
> > > > mergeManager.shouldMergeCloneIntoWorkingCopyForceCascade()
> > > > && mapping instanceof ForeignReferenceMapping
> > > > &&
> > > > ((ForeignReferenceMapping)mapping).shouldMergeCascadeParts(mergeManager))){
> > > > // GF#1139 Cascade merge operations even if it's
> > > > already managed
> > > > mapping.mergeIntoObject(target, isUnInitialized,
> > > > source, mergeManager);
> > > > }
> > > > }
> > > >
> > > > ...
> > > > }
> > > >
> > > > What do you think?
> > > >
> > > > -Wonseok
> > > >
> > > > On 9/25/06, Gordon Yorke < gordon.yorke_at_oracle.com> wrote:
> > > > >
> > > > > Hello Wonseok,
> > > > > A slight change will have to be made to your code as it is not
> > > > > backward compatible with current TopLink behaviour. Instead of altering the
> > > > > MergeManger to always cascade the merge and duplicating code already found
> > > > > within the ObjectBuilder the RepeatableWriteUnitOfWork (which is specific to
> > > > > EJB3.0) should request the cascaded merge from the MergeManager
> > > > > while the UnitOfWork continues to request the abortive merge.
> > > > >
> > > > > The following method should be added to the
> > > > > RepeatableWriteUnitOfWork :
> > > > > /**
> > > > > * INTERNAL:
> > > > > * Merge the attributes of the clone into the unit of work
> > > > > copy.
> > > > > */
> > > > > public Object mergeClone(Object rmiClone, int cascadeDepth) {
> > > > > if (rmiClone == null) {
> > > > > return null;
> > > > > }
> > > > >
> > > > > //CR#2272
> > > > > logDebugMessage(rmiClone, "merge_clone");
> > > > >
> > > > > startOperationProfile(SessionProfiler.Merge);
> > > > > ObjectBuilder builder =
> > > > > getDescriptor(rmiClone).getObjectBuilder();
> > > > > Object implementation = builder.unwrapObject(rmiClone,
> > > > > this);
> > > > >
> > > > > MergeManager manager = new MergeManager(this);
> > > > > manager.mergeCloneIntoWorkingCopyForceCascade();
> > > > > manager.setCascadePolicy(cascadeDepth);
> > > > >
> > > > > Object merged = null;
> > > > > try {
> > > > > merged = manager.mergeChanges(implementation, null);
> > > > > } catch (RuntimeException exception) {
> > > > > merged = handleException(exception);
> > > > > }
> > > > > endOperationProfile(SessionProfiler.Merge);
> > > > >
> > > > > return merged;
> > > > > }
> > > > > Then the MergeManager should have the following method and
> > > > > corresponding property added:
> > > > > /**
> > > > > * This can be used by the user for merging clones from RMI
> > > > > into the unit of work.
> > > > > */
> > > > > public void mergeCloneIntoWorkingCopyForceCascade() {
> > > > > setMergePolicy(CLONE_INTO_WORKING_COPY_FORCE_CASCADE);
> > > > > }
> > > > >
> > > > > Then update the MergeManager to check for this setting in two
> > > > > places:
> > > > > mergeClone():
> > > > > } else if (shouldMergeCloneIntoWorkingCopy() ||
> > > > > shouldMergeCloneWithReferencesIntoWorkingCopy() ||
> > > > > shouldMergeCloneIntoWorkingCopyForceCascade()) {
> > > > > mergedObject =
> > > > > mergeChangesOfCloneIntoWorkingCopy(object);
> > > > > and mergeChangesOfCloneIntoWorkingCopy() :
> > > > > if (registeredObject== rmiClone && !
> > > > > shouldMergeCloneIntoWorkingCopyForceCascade() ){
> > > > > return rmiClone;
> > > > > }
> > > > >
> > > > >
> > > > > These changes preserve existing TopLink behaviour while updating
> > > > > the functionality for EJB 3.0 support and the code continues to
> > > > > use the current TopLink patterns for method call sequence.
> > > > > --Gordon
> > > > >
> > > >
> > >
> >
>