Contents
The EMF transaction framework provides a means to manage multiple read/write threads on one
EMF-style editing domain. Transactional editing domains can be registered with a unique identifier
and later retrieved (or constructed). These editing domains conform to the standard EMF editing
domain contract and can be used with little modification to client code to make it work in the context
of regular EMF-style commands.
Listeners receive events in batches and can be registered
against particular editing domain(s) so that they are automatically added once the editing domain
is constructed. When a transaction is finished and about to commit listeners can be given the
opportunity to append more changes onto the end of the transaction. If a transaction is rolled back
then listeners are not given the notification of the post-commit.
[
back to top]
This tutorial assumes that the reader has knowledge of EMF and the eclipse PDE development
environment. It is essential that the reader understands the basic reflective mechanisms of EMF
as well as its adapter/notifier system for broadcasting events.
[
back to top]
In order to demonstrate EMF Model Transaction, we will be making use of the library metamodel. This metamodel
is a variant of the standard EMF example metamodel used in many of its tutorials.
The goal of this tutorial is to create a single standard transactional editing domain with a single listener.
Once that is done, code will be written to get (or construct) the editing domain so that changes
are made on multiple threads (sequentially, not concurrently). These changes will be undone and redone
automatically because the changes were done in a recording command.
[
back to top]
The following XML declares a new transactional editing domain and a new listener
for that editing domain. Both operations can be done programmatically using the
TransactionalEditingDomain.Registry
, TransactionalEditingDomain.Factory
and the
TransactionalEditingDomain.addResourceSetListener()
.
<extension
point="org.eclipse.emf.transaction.editingDomains">
<editingDomain
factory="transactionexample.MyEditingDomainFactory"
id="myExample"/>
</extension>
<extension
point="org.eclipse.emf.transaction.listeners">
<listener class="transactionexample.MyListener">
<editingDomain id="myExample"/>
</listener>
</extension>
Here is the implementation of the editing domain factory:
public class MyEditingDomainFactory
implements Factory {
public TransactionalEditingDomain createEditingDomain() {
return TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain();
}
public TransactionalEditingDomain createEditingDomain(ResourceSet rset) {
return TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain(rset);
}
public TransactionalEditingDomain getEditingDomain(ResourceSet rset) {
return TransactionalEditingDomain.Factory.INSTANCE.getEditingDomain(rset);
}
}
The editing domain factory simply delegates to the singleton factory. If some
customizations were needed for the editing domain then the factory could handle
them here.
public class MyListener
extends ResourceSetListenerImpl {
public void resourceSetChanged(ResourceSetChangeEvent event) {
System.out.println("A change has been made with "+event.getNotifications().size()+" notifications produced.");
}
public Command transactionAboutToCommit(ResourceSetChangeEvent event)
throws RollbackException {
List notifications = event.getNotifications();
CompoundCommand cc = new CompoundCommand();
Set handledLibraries = new HashSet();
for (Iterator i = notifications.iterator(); i.hasNext();) {
Notification n = (Notification)i.next();
if (n.getNotifier() instanceof EObject && !handledLibraries.contains(n)) {
EObject notifier = (EObject)n.getNotifier();
if (notifier.eClass() == EXTLibraryPackage.eINSTANCE.getLibrary()) {
final Library l = (Library)notifier;
String name = l.getName();
// Libraries should have some name
if (name == null || name.equals("")) {
// We can use any EMF command here
cc.append(new SetCommand(event.getEditingDomain(),
l, EXTLibraryPackage.eINSTANCE.getLibrary_Name(),
"SomeName"));
}
handledLibraries.add(l);
}
}
}
// It is important to return null if we have nothing to
// contribute to this transaction.
return cc.isEmpty() ? null : cc;
}
}
The listener serves two purposes: automatically name Library objects if they have no name and
report to the console how many notifications were batched at the completion of each root transaction.
It declares that it is interested in both the post commit and pre commit events by overriding
the isPrecommitOnly
and isPostCommitOnly
methods.
Note that the listener makes no attempt to modify the state of any EObjects
as this would constitute a protocol violation. The listener makes an attempt to modify EObjects
only as a command that it returns in the transactionAboutToCommit
. At no point
did the listener attempt to execute this command however. As an optimization,
the listener ensures that it will return null for transactionAboutToCommit
if
it has nothing to add to the transaction.
The listener could override the getFilter
method in order to filter out any notifications
that do not match the filter. For now, it will only filter out "touch" events.
[
back to top]
The editing domain, once registered is made available through the transactional editing domain
registry singleton.
final TransactionalEditingDomain domain =
TransactionalEditingDomain.Registry.INSTANCE.getEditingDomain("myExample");
Once we have the editing domain we can begin working with it and editing it. We must make
changes inside a command that is executed on the editing domain's command stack. The
editing domain will ensure that the command is executed exclusively from any other
commands being executed in other threads.
final Resource r = domain.getResourceSet().createResource(URI.createURI("file://foo.extlibrary"));
// We execute this command on the command stack because otherwise, we will not
// have write permissions on the editing domain.
domain.getCommandStack().execute(new RecordingCommand(domain) {
protected void doExecute() {
Library l = EXTLibraryFactory.eINSTANCE.createLibrary();
r.getContents().add(l);
l.setName("");
}
});
We can have multiple threads attempting to access this editing domain. Each
will be given exclusive access as long as we use the runExclusive()
method on the editing domain.
Runnable getLibraryName = new Runnable() {
public void run() {
// Any reading that is done on the editing domain must be done inside an
// runExclusive call to ensure we have the read lock
String libraryName = null;
try {
libraryName = (String)domain.runExclusive(new RunnableWithResult.Impl() {
public void run() {
// Find the library's name and pass it back to the caller.
setResult(((Library)r.getContents().get(0)).getName());
}
});
} catch (InterruptedException e) {
// Handle the interrupted exception in an graceful way ...
}
// The library name won't be empty because our listener will
// give it a default name since we gave it an empty name.
System.out.println(libraryName);
}
};
Thread t1 = new Thread(getLibraryName);
Thread t2 = new Thread(getLibraryName);
t1.start();
t2.start();
[
back to top]
In this tutorial, we did the following:
- Declared a transactional editing domain with a unique identifier against
the extension point
- Registered a listener against that transactional editing domain using the
listeners extension point
- Made changes to the editing domain using a recording command that can undo/redo
itself automatically
- Read information from the editing domain exclusively using
RunnableWithResult
so that we could more easily pass data back to the caller.
[
back to top]
Copyright (c) 2006 IBM Corporation and others. All Rights Reserved.