Sunday, February 28, 2010

Eclipse Enterprise: Not so optimistic locking - a simple approach with XMPP

We work an a rather small enterprise app with a "Plain Old" Eclipse RCP Client. Due to some constraints our domain model is based on Pojos (Java Beans) that are persisted with a hibernate backend. In the meantime I look on those who use EMF/CDO/Riena with somehow jealous eyes, because of all the nice things they can do, but we cant do so easily. Since we don't have much concurrency, we rely on hibernates optimistic locking strategies to avoid conflicts. That means if two users work on the same entity, the one who saves early wins, the other one looses his changes and gets an OptimisticLock Exception. Thats sometimes annoying for the users. So I did a simple "not so optimistic" locking approach today that cost me less than half a day to implement for the whole thing. Our server communication -in terms of services- relies on Spring's HttpInvoker so far (keep it simple) but we cannot do callbacks to the client with that technology. We tried different ideas to solve that issue starting from JMS to finally ending with an XMPP based messaging solution. Every client sets up a permanent XMPP connection to the server over wich we can send messages to whatever party we want. With XMPP we can even see who is online for free. Now every time we open an editor we send a simple XMPP Message to the server that stores a unique id and the userId of the entity in a simple HashMap. If two users open the same entity, they both get a warning message about a potential conflict. With a simple PartListener on the client we know when an editor gets closed, send an unlock message and notify the other user(s) about the unlock. Thats what we get with about three hours of work. Never thought it would that simple. I become more and more a fan of XMPP Messaging.

Outlook:
What we could do now is register an hibernate "onPersist" event listener and send a message to the other parties that the object has changed on the server, so that they could refresh their editor content. Maybe thats something for the next weekend session.

PS: Some hours later...

I build an abstract base class for my Editors that does the following:


public void init(IEditorSite site, IEditorInput input)
throws PartInitException {
super.init(site, input);
Object entityFromInput = getEntityFromInput(input);
EntityPacketFilter filter = new EntityPacketFilter(entityFromInput);
Activator.getDefault().getXmppConnection().addPacketListener(this,filter);
LockHelper.sendLockMessage(entityFromInput, true);
site.getPage().addPartListener(LockPartListener.instance);


}


The Editor sends a lock message on init, and registers a Partlistener to send an unlock on close; It adds itself as a PacketListener to the XMPP Connection. The hibernate PostUpdateEvenListener sends an Update Stanza which goes to:



EntityChangedDialog dlg = 
new EntityChangedDialog(AbstractLockingFormEditor.this);
int ret = dlg.open();
if (ret == Dialog.CANCEL) {
 PlatformUI.getWorkbench().getActiveWorkbenchWindow()
  .getActivePage().closeEditor(AbstractLockingFormEditor.this,   
  false);
  return;
}
// reload from db
IEditorInput newInput = EntityEditorManager.getInstance()
 .createInputForEntity(dlg.getEntityClass(),dlg.getEntityId());
setInput(newInput);
rebind() // rebuild databinding;
firePropertyChange(EditorPart.PROP_INPUT);
dirty = false;
setDirty(false);
firePropertyChange(EditorPart.PROP_DIRTY);


That's it. No more Optimistic Lock Exceptions from today.

3 comments:

André said...

pretty interesting read! This area is what my interests are lead to lately. I am a committer to CDO. I do not have a lot of expertise with hibernate and the classic approach (I was in similar projects though). I am lately very interested in how the classic approach/'concurrence' would solve the problems we resolved for CDO. I'd be very glad to compare these approaches :-)

CDO unifies the client and server session, both peers have an identical unit that are bound to earch other. A session may have several views and that's where the transactions get into the play. CDO currently knows 3 kinds of strategies towards concurrent changes:

1.) default behaviour:
changes in a view are pushed to the other (session-)views and invalidate them. The conflicting, latter changes cannot be commited any more. You have to refresh your data. Default or custom written conflict resolvers may help to deal with these concurrent changes and view/session invalidations.

2.) passive updates are disabled (no more pushes):
this strategy is very comparable to the classic optimistic approach in hibernate. You apply your changes on the client-side and get aware of the conflicting changes when you commit them.

3.) pessimistic locking:
You may also lock data in pessimistic manner. Conflicting changes are not possible any longer.

Of course (and maybe unfortunately), CDO is based upon EMF aka ecore objects (not POJOs). That means that you have to switch your POJOs to ecore objects to get all this (almost for free). And that's probably where CDO gets its userbase restricted.

Thomas Kratz said...

HI Andre,

see my PS that I wrote lately.
I implemented the PostUpdate Listener already.

Scott Lewis said...

Hi Thomas.

FYI, you might be able to save a lot of effort by using ECF, since we've got support for using XMPP via Smack 3.0, and on top of XMPP various APIs for messaging (e.g. datashare API, sharedobject API, OSGi remote services API, etc). We also have support for the operational transformation approach to real-time shared editor synchronization (which, incidently, also runs on XMPP).

Using Mapstruct with Protobuf3