2

Let me start with I have a solution, but I don't really think it is elegant. So, I am looking for a cleaner way to do this.

I have an EntityProxy displayed in a view panel. The view panel is a RequestFactoryEditorDriver only using display mode. The user clicks on a data element and opens a popup editor to edit a data element of the EntityProxy with a few more bits of data than is displayed in the view panel. When the user saves the element I need the view panel to update the display.

I ran into a problem because the RequestFactoryEditorDriver of the popup editor flow doesn't let you get to the edited data. The driver uses the same passed in context that you use to send data the server, Yet the context returned out of flush only allows a Receiver<Void> even if you cast it to the type of context you stored in the editor driver in the edit() call. [It doesn't appear to send and EntityProxyChanged event either, so I couldn't listen for that and update the display view. - scratch this - I see now this event is not for this use case]

The solution I found was to change my domain object persist to return the newly saved entity. Then create the popup editor like this

editor.getSaveButtonClickHandler().addClickHandler(createSaveHandler(driver, editor));
                // initialize the Driver and edit the given text.
                driver.initialize(rf, editor);
                PlayerProfileCtx ctx = rf.playerProfile();
                ctx.persist().using(playerProfile).with(driver.getPaths())                      
                        .to(new Receiver<PlayerProfileProxy>(){
                    @Override
                    public void onSuccess(PlayerProfileProxy profile) {
                        editor.hide();
                        playerProfile = profile;
                        viewDriver.display(playerProfile);
                    }                               
                });
                driver.edit(playerProfile, ctx);
                editor.centerAndShow();

Then in the save handler I just fire() the context I get from the flush(). While this approach works, it doesn't seem right. [It would seem I should subscribe to the entitychanged event in the display view and update the entity and the view from there. - once again scratch, same reason as before ] Also this approach saves the complete entity, not just the changed bits, which will increase bandwidth usage.

What I would think should happen, is when you flush the entity it should 'optimistically' update the rf managed version of the entity and fire the entity proxy changed event. Only reverting the entity if something went wrong in the save. The actual save should only send the changed bits. In this way there isn't a need to refetch the whole entity and send that complete data over the wire twice.

Is there a better solution?

Deanna
  • 696
  • 1
  • 5
  • 15

2 Answers2

2

You don't seem to really understand the details of what's going on with RF; plus, your terminology doesn't really help in understanding (flush vs. fire).

A proxy in RF is a snapshot of the server's state at the time you retrieved it. You can do whatever you want with the entity elsewhere in your app (through other proxies), your proxy won't ever change to reflect those modifications.

An EntityProxyChange event is dispatched on the client-side (for an entity that is already known by the server and has been sent from the client) when the server has detected that it has changed: either its version (as returned by getVersion on the Locator) has changed, or it has been deleted (as told by the isLive method of the Locator). If you don't use a Locator, it'll use the getVersion of the entity and isLive will be replaced by finding the entity by its ID (as returned by its getId method) and checking for null (this is also the default implementation of isLive in the Locator).
In your case, if you don't see an EntityProxyChange being dispatched, then check that you correctly update the entity's version.

Finally, RF always sends a diff of your changes to the server (except for a ValueProxy, in which case a diff would have no meaning). As for retrieving data, it doesn't retrieve linked proxies by default, unless you explicitly ask for them using with; and this is independent of what you possibly sent about the entity.

In your case, to update the view panel, you have 3 possibilities:

  • retrieve the proxy back from the server (listening to EntityProxyChange events or after an explicit signal from your popup; you could use the find method of RequestContext with the proxy's stableId as argument, and the appropriate with for the properties you need).
    It's a bit inefficient as you're doing a second HTTP request, but on the other hand it can handle changes from elsewhere in your app (they'll fire EntityProxyChange events too)
  • retrieve the updated proxy in the same HTTP request as the one you use to save it: have the save method of your request context return the saved entity, or call the find method in the same request context to batch the save and find together in the same HTTP request.
    That's what you did. It sends a diff of the changes and retrieves the properties needed by your view panel. One could say it has the drawback of tightly coupling your popup and your view panel, it's up to you to decide whether it's an acceptable trade-off.
  • use the entity you edited and sent to the server right in your view panel, with no extra data over the wire.
    While this seems simpler, you'll miss any change that could have been done on the entity by another user (changes only the server know).

All in all, I think I'd go with the current solution. Regarding your code though, I'd launch the popup with the proxy and a callback, and leave the request context and editing editor driver as implementation details of the popup: you only need that it calls the view panel back when it's done, passing the updated proxy as an argument to the callback.

A last word regarding terminology: you flush an editor driver to copy the field's value back to the object/proxy, and (independently, but in your case sequentially) you fire a request context to send a batch of service methods and proxy changes to the server. Flushing an editor driver doesn't send anything to the server, these are distinct actions.

Thomas Broyer
  • 64,353
  • 7
  • 91
  • 164
  • Thanks for your answer, but I'm not sure where you think I mixed up flush and fire though. I used them exactly as you described. Also in RequestFactoryEditorDriver flush returns the rf context you initialized it with, not the edited object, which prevents your third option from being possible (unless there is some accessor I haven't found). Also looking at the http request sent, the entire entity is being sent to the server not just the delta's - which is different than the way rf is documented. And the condition you gave for when an EntityProxyChange event is what the condition is... – Deanna Mar 20 '12 at 18:09
  • I had considered your option 1, but as you noted it is an extra round trip to the server. I had used option 2, but it doesn't work as expected since it doesn't send a diff. Before this process began the entity was already existing on the server and fetched into the view panel. That is the playerProfile I referenced in the sample code. I use that to initialize the RequestFactoryEditorDriver, so I would expect an event to fire (eventbus fire not rf) when the driver flushed and/or the original proxy to be updated. Am I missing something that is preventing these things? – Deanna Mar 20 '12 at 18:16
  • Also, I didn't fully grok the use case of an entityproxy only being a snapshot when you grabbed it. In that you are right. I thought it was something a bit more functional like a remote/client copy of the entity that would be updated if I grabbed a new copy of the same entity from the server and an entityproxychange event sent. – Deanna Mar 20 '12 at 18:22
  • I wish I could edit comments - so scrap all that about the event not firing - I now understand the limitations of when it does fire. If we need client side notification of when proxies change on the client, I guess we have to roll that ourselves. That does seem like something that should be in RF, at least when caching is added. I still don't know why it is sending the full entity and not a diff though. – Deanna Mar 20 '12 at 18:39
  • I seem to have found another reason the EntityProxyChange event isn't firing. The version number isn't incrementing in the db. I'm using JPA and there is a known bug http://code.google.com/p/google-web-toolkit/issues/detail?id=6712 that would prevent any RF instance request from incrementing the version without an extra full entity copy [ meaning instead of just persisting the instance, you do an em.find(...) copy the current instance data to the instance from the db, then persist the one from the db back. Thats just to get the version to increment. – Deanna Mar 22 '12 at 15:45
  • I realize it isn't technically a bug (at least not the stated way JPA works), but the RF documentation gives example of using persist in a manner that would not let the version increment - here http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html . Since the version is important to RF, and this condition happens anytime you save an instance of an entity without it being attached, then the condition should probably be referenced and/or some work around given. – Deanna Mar 22 '12 at 16:36
  • The method I used to deal with this condition I took from here http://stackoverflow.com/questions/4988397/gwt-requestfactory-how-to-use-single-entitymanager-per-request – Deanna Mar 22 '12 at 19:32
1

I found a better solution. I make the proxy editable before calling the RequestFactoryEditorDriver edit() and save the editable proxy as my view proxy.

PlayerProfileCtx ctx = rf.playerProfile();
playerProfile = ctx.edit(playerProfile);
driver.edit(playerProfile, ctx);

Also, (and I thought I had tried this before and it didn't work but I must have done something wrong then) I can cast the context that comes back from the flush. It is the same context I sent to the driver with the edit() call so it is safe.

PlayerProfileCtx ctx  = (PlayerProfileCtx) driver.flush();

Doing this fixed the problem with rf sending the whole object with fire() and not just the diffs. I'm not sure why though. That might be a bug in the RF Editor Driver.

So now I have the data from the driver already in the view and don't have to depend on sending it back from the server. But I did register for EntityProxyChange events so I could detect and refetch if there was a conflict on the server.

Deanna
  • 696
  • 1
  • 5
  • 15