Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » JFace » master detail databinding and conflicting interests in designing the equals method
master detail databinding and conflicting interests in designing the equals method [message #545330] Wed, 07 July 2010 13:05 Go to next message
SlowStrider Mising name is currently offline SlowStrider Mising name
Messages: 115
Registered: July 2009
Senior Member
I have had trouble with databinding JPA entities, especially in master-detail databinding when the master changes. When my master changes and the equals method returns true between the old and new value, the binding will go to the old master value and not to the new one. This is because java's PropertyChangeSupport does not fire an event in this case.

Also with TableViewers I get subtle problems. I have a BeansObservables.observeDetailList which is a List property inside my master observable. When the master changes but an element in the new list says it is equals() to the element in the same index in the old list, the TableViewer's getElementAt(int) method returns the old element and cell editing changes the old element contained in the old master object, not in the new one. This is true even while TableViewer.getInput() is up to date!!!

Ok the solution to his problem is simple: make an equals method which compares all fields including the parent entity (but what if it doesn't need to know it's parent??).
Another way could be to keep the standard equals method which compares objects by VM identity.

So far so good, but according to good practice in using business entities in Caches, Maps and Lists, the equals() method should NOT return false when you only change some of it's properties. It should return true for the whole persistent lifetime of an entity. This makes it easy to use Collection methods to find an existing entity in the Collection. This is why we want to use the gerrypower technique for our equals method: we base the equals method on an identity field.

But this does not work well with data binding as outlined above. It does however work if equals uses an additional version field in the master entity and list element entity which is increased each time the master changes.

The problem is that multiple concerns come together in only one possible equals method. (We already had to make an isEqualData method to compare object graphs...).

I think the easiest and simplest solution is to just use the default equals method and be careful when looking up entities from a list of map.

Another solution would be to use a different PropertyChangeSupport that fires events based on VM identity and not on equals. See also JGoodies ExtendedPropertyChangeSupport. (I tried this once but ran into other problems I don't remember at the moment).

Can anyone think of an ideal solution?

[Updated on: Wed, 07 July 2010 13:07]

Report message to a moderator

Re: master detail databinding and conflicting interests in designing the equals method [message #545647 is a reply to message #545330] Thu, 08 July 2010 13:52 Go to previous messageGo to next message
SlowStrider Mising name is currently offline SlowStrider Mising name
Messages: 115
Registered: July 2009
Senior Member
Ok now I am going crazy because there is also a huge problem with equals based on reference.

Suppose you want to bind a property of entity type B contained in a model entity of type A to one entity in a list of type B in a ComboViewer. I fetch the contents of the ComboViewer independently from the model entity A. This means (at least in our system but I think it will be true in most) that the reference to B inside A is a copy of an element in the list of B, not a direct reference to the same object. So setting the selected object to one in the list will not work because the equals doesn't recognize it is an equal object.
Re: master detail databinding and conflicting interests in designing the equals method [message #545698 is a reply to message #545647] Thu, 08 July 2010 16:06 Go to previous messageGo to next message
Thomas Schindl is currently offline Thomas Schindl
Messages: 5330
Registered: July 2009
Senior Member
You can set an IElementComparer to find the matching objects. I advice
everybody NOT to overload equals (which requires overloading hashCode)
because you are running a lot of problems with hashCodes who are
calculated based upon changing values (e.g. HashMap, HashSet are broken
then!).

Tom

Am 08.07.10 15:52, schrieb SlowStrider:
> Ok now I am going crazy because there is also a huge problem with equals
> based on reference.
>
> Suppose you want to bind a property of entity type B contained in a
> model entity of type A to one entity in a list of type B in a
> ComboViewer. I fetch the contents of the ComboViewer independently from
> the model entity A. This means (at least in our system but I think it
> will be true in most) that the reference to B inside A is a copy of an
> element in the list of B, not a direct reference to the same object. So
> setting the selected object to one in the list will not work because the
> equals doesn't recognize it is an equal object.
Re: master detail databinding and conflicting interests in designing the equals method [message #545790 is a reply to message #545698] Fri, 09 July 2010 07:26 Go to previous messageGo to next message
SlowStrider Mising name is currently offline SlowStrider Mising name
Messages: 115
Registered: July 2009
Senior Member
Wow many thanks, so if I do not overload equals and hashcode (compare them by object reference) I can simply set my own IElementComparer on my StructuredViewers (including ComboViewers) to compare on entity id and then the binding should work.


Like you point out, I was not really planning on using changing values for equals; I based it on an id field which does not change so it would work great in viewers.

This approach would also be best for using my entities in Collections outside of databinding. The real problem with this approach is however that when a property containing a reference to an entity changes to a reference to a newer version of the same entity (id stays the same, other fields may change), the propertychangesupport does not fire a change!

So if I understand it well this is a restriction of Java's PropertyChangeSupport and not with JFace?
Re: master detail databinding and conflicting interests in designing the equals method [message #545799 is a reply to message #545790] Fri, 09 July 2010 08:14 Go to previous messageGo to next message
Thomas Schindl is currently offline Thomas Schindl
Messages: 5330
Registered: July 2009
Senior Member
Am 09.07.10 09:26, schrieb SlowStrider:
> Wow many thanks, so if I do not overload equals and hashcode (compare
> them by object reference) I can simply set my own IElementComparer on my
> StructuredViewers (including ComboViewers) to compare on entity id and
> then the binding should work.
>
>
> Like you point out, I was not really planning on using changing values
> for equals; I based it on an id field which does not change so it would
> work great in viewers.
>
> This approach would also be best for using my entities in Collections
> outside of databinding. The real problem with this approach is however
> that when a property containing a reference to an entity changes to a
> reference to a newer version of the same entity (id stays the same,
> other fields may change), the propertychangesupport does not fire a change!
>
> So if I understand it well this is a restriction of Java's
> PropertyChangeSupport and not with JFace?

Well JFace solves the problem as described. I'm not 100% sure about
Eclipse Databinding which also uses equals/hashCode to some extend and
you'll probably run into problems with master-detail, list and map
diffs, ... .

If I get your problem right it starts at the very moment where you load
a fresh object from your persistence layer, right?

Can you describe in more detail how you replace the old and new object
because I think this is where the trouble starts, right?

Tom
Re: master detail databinding and conflicting interests in designing the equals method [message #546272 is a reply to message #545799] Mon, 12 July 2010 11:17 Go to previous messageGo to next message
SlowStrider Mising name is currently offline SlowStrider Mising name
Messages: 115
Registered: July 2009
Senior Member
Tom, thank you for your interest in our real-world problem, I hope my description here is not too long (at least it helps me get the problem more clear). I describe the actual situation of the problems of my first post in detail.

We mainly have forms for editing a single JPA-annotated entity.

We have an EntityModel class which wraps an entity and listens to update notifications from the server. When a notification comes in (on a non-UI thread), we reload the entity from the server and set the loaded entity to the "entity" property of our EntityModel class. Inside EntityModel we then do
		propertyChangeSupport.firePropertyChange("entity", oldEntity,
				this.entity);

where propertyChangeSupport is of type java.beans.PropertyChangeSupport.

Most of our databinding is to detail properties of our "entity" property in our EntityModel (which is a Javabean independent of eclipse databinding), for example to get the observable value for a String "name" which can be bound to a TextField we do
    IObservableValue modelObservable = BeanProperties.value(
				EntityModel.class, "entity", model.getEntity().getClass())
				.observe(model);
		IObservableValue modelObservableValue = BeansObservables
				.observeDetailValue(modelObservable, "name", null);


Problem 1:
When the master entity changes, we MUST get a property change event for the binding to pick up the new version of the entity which of course HAS THE SAME ID.

So when using java.beans.PropertyChangeSupport we can not write our equals method to only check the id field. To fix this problem our equals method is now based both on an "id" field and a "version" field which is increased by one each time an entity is saved.

Problem 2:
Unfortunately this breaks using the entities inside collections but we can work around this. It does seem to work fine with binding an entity property to a single selection in a ComboViewer populated with a list of entities whose version doesn't change. Actually we have a WritableList in our ComboViewer models that can also get automatically updated by the server so I can now see that we should use an IElementComparer which only checks the id and not the version.

Problem 3:
Now finally when binding a list to a TableViewer we again get into trouble.
Suppose our entity owns a list of DetailEntity's in a property called "detailEntities". We use a TableViewer with an IObservableList as input which is created as follows from our EntityModel:
		IObservableValue master = BeansObservables
				.observeValue(model, "entity");
		IObservableList detailList = BeansObservables.observeDetailList(master,
				"detailEntities", DetailEntity.class);


Our DetailEntity is only saved through the main entity. For technical reasons (*) we do not use a version field in this entity and comparison is on id only. Suppose that an update of the entire entity comes in and the size of the detail list remains the same. But some of the entities in the detail list have changed properties. For each element our equals method will report them as the same.

What I experience in this case is that the IObservableList detailList points to the freshly loaded detail entities, so the data we get from viewer.getInput() is correct.
However the EditingSupport we use on the table columns is passed a reference to the OLD DETAIL ENTITY by jface. Also when I do

viewer.getElementAt(0)

I get a reference to the old first entity in the list, not the new one inside the first element of viewer.getInput()!!!!!!

When I do not override the equals method in my DetailEntity I don't get this behavior and my binding to a table works fine, but as I said before my ComboViewer's break. I will solve this with an IElementComparer.

Is the moral of the story that I should not override the equals method at all? Or can I write my equals method to compare only the "id" field and use a PropertyChangeSupport hack that always fires? Will this solve problem 3?


(*) We increase the version of the main entity that is saved ourselves. It's a pain to walk the entire object graph to change versions of detail entities. Actually JPA can in principle do this out of the box with it's optimistic locking version, but unfortunately OpenJPA which we use does not increase the version of the main entity when we save a main entity that only has a change in the detail entity.

[Updated on: Mon, 12 July 2010 11:25]

Report message to a moderator

Re: master detail databinding and conflicting interests in designing the equals method [message #546356 is a reply to message #546272] Mon, 12 July 2010 14:34 Go to previous messageGo to next message
SlowStrider Mising name is currently offline SlowStrider Mising name
Messages: 115
Registered: July 2009
Senior Member
Not overriding equals/hashcode in my entity and setting an IElementComparer on my StructuredViewers seems to solve most of my problems.

I did encounter the following: For multiple selection I use a dialog with a CheckboxTableViewer where I "bind" to the checked elements by calling setCheckedElements(Object[]) when the dialog opens and calling getCheckedElements() on close. By looking at jface internals I saw that the setCheckedElements(Object[]) does not use the IElementComparer. I fortunately also found that the.setChecked(Object, boolean) does and it works nicely now.

Not sure if I should file this as a bug or if I'm not supposed to use setCheckedElements(Object[]). It could also be useful to be able to do real data binding to the checked elements inside a CheckboxTableViewer.

Edit: now I still have the problem with binding a textfield to a property of the selected row element in a TableViewer. When the entity in the model is changed the binding fails until the row is selected again. Not sure where the problem is but this is a lower priority for me.

[Updated on: Mon, 12 July 2010 15:03]

Report message to a moderator

Re: master detail databinding and conflicting interests in designing the equals method [message #546384 is a reply to message #546272] Mon, 12 July 2010 15:04 Go to previous messageGo to next message
Thomas Schindl is currently offline Thomas Schindl
Messages: 5330
Registered: July 2009
Senior Member
Am 12.07.10 13:17, schrieb SlowStrider:
> Tom, thank you for your interest in our real-world problem, I hope my
> description here is not too long, I actually describe the situation of
> all the problems of my first post in detail.
>
> We mainly have forms for editing a single JPA-annotated entity.
>
> We have an EntityModel class which wraps an entity and listens to update
> notifications from the server. When a notification comes in (on a non-UI
> thread), we reload the entity from the server and set the loaded entity
> to the "entity" property of our EntityModel class. Inside EntityModel we
> then do
>
> propertyChangeSupport.firePropertyChange("entity", oldEntity,
> this.entity);
>
> where propertyChangeSupport is of type java.beans.PropertyChangeSupport.
>
> Most of our databinding is to detail properties of our "entity" property
> in our EntityModel (which is a Javabean independent of eclipse
> databinding), for example to get the observable value for a String
> "name" which can be bound to a TextField we do
>
> IObservableValue modelObservable = BeanProperties.value(
> EntityModel.class, "entity", model.getEntity().getClass())
> .observe(model);
> IObservableValue modelObservableValue = BeansObservables
> .observeDetailValue(modelObservable, "name", null);

Why not using a WritableValue() directly as the master?

Tom

>
>
> When the master entity changes, we MUST get a property change event for
> the binding to pick up the new version of the entity which of course HAS
> THE SAME ID.
>
> So when using java.beans.PropertyChangeSupport we can not write our
> equals method to only check the id field. To fix this problem our equals
> method is now based both on an "id" field and a "version" field which is
> increased by one each time an entity is saved.
>
> Unfortunately this breaks using the entities inside collections but we
> can work around this. It does seem to work fine with binding an entity
> property to a single selection in a ComboViewer populated with a list of
> entities whose version doesn't change. Actually we have a WritableList
> in our ComboViewer models that can also get automatically updated by the
> server so I can now see that we should use an IElementComparer which
> only checks the id and not the version.
>
> Now finally when binding a list to a TableViewer we again get into trouble.
> Suppose our entity owns a list of DetailEntity's in a property called
> "detailEntities". We use a TableViewer with an IObservableList as input
> which is created as follows from our EntityModel:
>
> IObservableValue master = BeansObservables
> .observeValue(model, "entity");
> IObservableList detailList =
> BeansObservables.observeDetailList(master,
> "detailEntities", DetailEntity.class);
>
>
> Our DetailEntity is only saved through the main entity. For technical
> reasons (*) we do not use a version field in this entity and comparison
> is on id only. Suppose that an update of the entire entity comes in and
> the size of the detail list remains the same. But some of the entities
> in the detail list have changed properties. For each element our equals
> method will report them as the same.
>
> What I experience in this case is that the IObservableList detailList
> points to the freshly loaded detail entities, so the data we get from
> viewer.getInput() is correct.
> However the EditingSupport we use on the table columns is passed the OLD
> DATE by jface. Also when I do
>
>
> viewer.getElementAt(0)
>
> I get a reference to the old first entity in the list, not the new one
> inside the first element of viewer.getInput()!!!!!!
>
> When I do not override the equals method in my DetailEntity I don't get
> this behavior and my binding to a table works fine, but as I said before
> my ComboViewer's break. I will solve this with an IElementComparer.
>
> So is the moral of the story that I should not override the equals
> method at all?
>
>
> (*) We increase the version of the main entity that is saved ourselves.
> It's a pain to walk the entire object graph to change versions of detail
> entities. Actually JPA can in principle do this out of the box with it's
> optimistic locking version, but unfortunately OpenJPA which we use does
> not increase the version of the main entity when we save a main entity
> that only has a change in the detail entity.
Re: master detail databinding and conflicting interests in designing the equals method [message #546390 is a reply to message #546384] Mon, 12 July 2010 15:43 Go to previous messageGo to next message
SlowStrider Mising name is currently offline SlowStrider Mising name
Messages: 115
Registered: July 2009
Senior Member
1. I didn't think of it.
2. This can solve the equals method problem for the master entity but not for detail properties. for example entities inside a List or inside the master entity that has a simple one-to-one relation to another entity that can change (I guess it would be a horrible practice to use jface WritableValue and WritableList inside JPA entities...)
3. Extra jface dependency (if we later want it we could use our EntityModel on the server side/behind a webpage/whatever)
Re: master detail databinding and conflicting interests in designing the equals method [message #546571 is a reply to message #546390] Tue, 13 July 2010 12:17 Go to previous message
SlowStrider Mising name is currently offline SlowStrider Mising name
Messages: 115
Registered: July 2009
Senior Member
You suggestion is not bad though... It does solve problem 1 and I might solve problem 2 and 3 with IElementComparers.

I will now spend some time writing unit/integration tests for my problems so that I can later switch between the solutions relatively easily and test if everything still works...
Previous Topic:Contents of ComboViewer bigger than its size
Next Topic:Using Buttons for triggering Actions
Goto Forum:
  


Current Time: Sun Sep 21 08:18:16 GMT 2014

Powered by FUDForum. Page generated in 0.02580 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software