Combining GEF and Cayenne

By Marcel Gordon, August 2006

1 ABSTRACT

GEF is a powerful Eclipse-based framework for enabling visual interaction with models. Cayenne is an open-source project which provides object-relational mapping, translating from data in a relational database to objects in Java. The two technologies combine fluidly to allow visual editing of data from a database. This article gives a high level explanation of the structure of an application which utilises GEF and demonstrates the use of Cayenne persistent objects as a model for GEF through the development of a visual database editor.

2 INTRODUCTION

Graphical editing is one of the most intuitive and effective ways to allow users to manipulate data. Diagrams convey a wealth of information in a simple fashion. Simple for the user, in any event; creating such applications can be a time-consuming process for the developer. Enter GEF, the Eclipse project's Graphical Editing Framework. GEF allows the rapid development of visual editors by providing a framework for display and interaction, leaving the developer free to focus on the user experience.

GEF utilises the well-known Model-View-Controller pattern. Usually the application's model will comprise of simple Java objects, but more sophisticated applications can take advantage of the Eclipse Modeling Framework (EMF). However, for many applications the data which underlies the model resides in a database. Translating from the relational structure to Java objects can be burdensome and conceptually difficult.

Cayenne is a well-established open source project which is designed to alleviate that burden. Cayenne allows relational databases to be mapped to objects in Java. The resulting objects can be accessed via single-method queries, and changes to the Java objects can be committed through to the database. These objects are suitable for use as the model for a GEF-based application. Cayenne is presently released under an Apache-like license, and the next release will be under the Apache license itself as the project recently migrated to the Apache Software Foundation's incubator.

3 THE DATABASE EDITOR

This article explains the design of a simple database editing tool. The editor allows users to run a query and display the results, expand relationships between tables, insert and delete records, alter relationships between records and edit the value of fields.

Database Editor in action

3.1 Running the editor

The source code for the editor can be checked out of the repository. The application consists of two Eclipse projects. The client is an Eclipse plugin and the server is a web application. The following are required in order to run the editor:

In addition, Jettylauncher will - in conjunction with Jetty - allow the server to be launched directly from Eclipse. All other required libraries are included with the source.

The database is already configured and is hosted in an embedded instance of Derby. Once the server is running, launch the client plugin and change perspective to the 'ROP Browser' perspective. Using the configuration provided, the server will be located at http://localhost:8080/rop-browser. Connect and experiment!

3.1.1 Remote or local?

Cayenne can be used in either a two or three tier configuration. In the former, the Cayenne library is packaged with the application and the application uses the library to access the database directly. In the latter, a stripped-down version of the library is used to connect to a web application which uses the full library to access the database. The two modes of operation are almost identical from a developer's perspective. The main difference - constituting about two lines of code - is that in the three tier configuration the application must first make a connection to the server.

As packaged the database editor utilises the three-tier configuration in order to demonstrate some advanced features available in Cayenne (link). Modifying it to access a database directly would be a trivial task. See below, Connecting to the server, for more information.

3.2 Cayenne basics

First, some terminology. Cayenne translates from a relational world to an object-oriented one. In order to explain this translation it is necessary to map some words from each domain.

Database termObject-oriented term
TableEntity
RecordObject
FieldAttribute
Foreign key constraintRelationship

Cayenne provides a GUI tool known as the Modeler in order to facilitate the mapping of a database. If the database already exists, the Modeler can generate the mapping automatically. Alternatively, the Modeler can be used to design and create a database and the associated mapping. Either way, upon completion of the mapping process Cayenne has the metadata which it needs. This is used to generate Java classes for use in the application, and the developer never need deal with the database directly again.

Cayenne stores its mapping metadata in XML files. The database editor includes metadata files for a simple database involving three entities - Artist, Painting, and Gallery - commonly used in the Cayenne documentation. As we will see below, you can use the database editor with a different database simply by replacing these files with your own.

Entity type hierarchy

3.2.1 Cayenne's persistent classes

Cayenne generates persistent classes in pairs. For each entity, two persistent classes are generated: _Entity and Entity. The relationship between the two is shown in Figure 1. _Entity contains all of the generated code which Cayenne uses to handle database access. Entity is provided to allow the developer to add methods to the class as required. When the classes are regenerated, _Entity will be overwritten while Entity will not. PersistentObject provides Cayenne's shared persistence functionality.

As noted above, the persistent classes generated by Cayenne could correspond directly to visual elements in an application. This is the most straight-forward approach, but it has a significant limitation: knowledge of the attributes of the entities has to be programmed into the application. For instance, if an entity User with an attribute username is to be displayed, the view has to know about the getter method and the controller must know about the setter method (getUsername and setUsername respectively). This can be very inconvenient: where multiple entities are used there will be multiple instances of code that is essentially the same; and if the database is modified (say username becomes name) the application code will have to be updated.

For some applications, this will not be a concern. However, for most projects a more flexible, more generic approach is preferable.

Entity type hierarchy with custom super-class

3.2.2 Custom super-classes

Cayenne allows the developer to specify a super-class for all of the persistent classes which it generates. Figure 2 shows the type hierarchy after the introduction of a custom super-class. This allows the developer to specify behaviour to be shared between all Cayenne classes. Adding a custom super-class gives the opportunity to access attributes in a generic way. Cayenne has mechanisms for accessing attributes using the name of the entity and the name of the attribute. Shown below is a method from AbstractObject which accesses a named attribute.

01 /**
02  * Accesses a property of the object, returning the result as a String
03  @param id the name of the property
04  @return the value of the property as a String
05  */
06 public String getPropertyValue(Object id) {
07   if(objectContext != null) {
08     // prepare the object for access
09     objectContext.prepareForAccess(this, id.toString());
10   
11     EntityResolver resolver = objectContext.getEntityResolver();
12     ClassDescriptor descriptor = resolver.getClassDescriptor(getName());
13     Object value = descriptor.getProperty(id.toString()).readProperty(this);      
14 
15     ...
16 }

Using this approach, the view and controller no longer need to know anything about the individual persistent classes. It is sufficient to know that they all inherit from the same super-class. For the database editor, this means that the persistent classes can be substituted without having to modify or rebuild the application. To run the database editor with a different database, simply replace the Cayenne configuration files with your own and include generated classes for your entities in the appropriate directories of the server and the client. The only requirement is that you must specify AbstractObject as the super-class for your persistent objects.

For some applications, this will be a sufficient degree of abstraction as it allows all Cayenne entities to be treated the same way by the other parts of the application. If the entities are to be directly represented in the GEF diagram by components then the use of a super-class allows one view part to represent different entities and to be agnostic to changes in the underlying database.

However, in many cases a visual component will want to offer more than just the data from the database. In that case - and the database editor is such a case - the persistent classes will be wrapped inside other classes. With the basics of Cayenne under our belt, it is time to move on to using Cayenne with GEF.

3.3 GEF basics

There are a number of excellent introductory articles on GEF available, and it is recommended that the reader peruse those if they are new to the topic.[1] This article will focus on the design of the various elements employed by GEF in the context of this application, and the use of Cayenne with GEF.

3.4 Designing the model

GEF uses a factory to map different classes in the model to components on the screen. Not every class in the model need be represented by a visual component, and some components may represent more than one class in the model. Classes in the model which are represented on the screen are referred to hereafter as 'visual model elements', whilst others are referred to as 'business model elements'.[2] In some applications there may be no separation between business and visual model element; in the database editor the separation is relatively clean.

3.4.1 Business model elements

Type hierarchy for the database editor model

Although in some cases the model can be designed without regard to its visual representation, the database editor is not one of these cases. The first instinct in creating a database editor is that a visual element will represent an entity from the database. That way each record will appear on the screen as a separate component and can be manipulated by the user. All Cayenne persistent objects are descended from the same custom super-class - in the database editor, AbstractObject - so that would be the main visual model element.

However, displaying the contents of a database has some subtle challenges. Sometimes one entity will be linked to only one other entity; but in other cases it will be linked to a number of other entities, or none at all (a null value). If a gallery has two thousand paintings, how do we display all those paintings in the diagram, let alone show the relationship between the paintings and the gallery? It is clearly not feasible to put two thousand components in front of the user.

This complexity means that it is necessary to introduce more sophistication to the model. The final type hierarchy is shown in Figure 3. CollectionModelElement manages a collection of persistent objects - each of which extends AbstractObject - and SingleModelElement manages a single persistent object.

Why have a separate class in the model to manage single persistent objects? Isn't an AbstractObject itself just a single persistent object? One reason is that Java doesn't allow for multiple inheritance. AbstractObject must extend PersistentObject, and in order to share functionality common to parts of the model which are represented visually, SingleModelElement must extend ModelElement. Even without this limitation, it is still a good idea to separate the two as one deals with business logic (storing and retrieving data) while the other is visual.

Single and collection model elements in the display

3.4.2 Visual model elements

ModelElement is the base class for visual model elements which can be interacted with (as distinct from connections, discussed below). ModelElement offers three different groups of functionalities. First, it implements the org.eclipse.ui.views.properties.IPropertySource interface, allowing users to edit its properties through the standard Eclipse Properties view. Second, it can fire events when a property is changed. Finally, it manages non-database data - view data - such as size, location, name and connections with other elements. It makes sense to abstract this functionality to a super-class.

Enabling the Properties view is relatively simple for ModelElement. The AbstractObject treats its attributes - whatever they may happen to be - as properties. Calls to getPropertyValue , setPropertyValue , isPropertySet and resetPropertyValue are delegated to the AbstractObject methods of the same name. The method for retrieving property descriptors, which dictate the way in which a property is to be edited, is only slightly more complicated: it caches the results of a call to its equivalent AbstractObject method. This is done because generating the property descriptors involves some work, and need not be repeated every time the ModelElement is selected.

Properties view in action
01 public IPropertyDescriptor[] getPropertyDescriptors() {
02   if (objectContext != null) {
03     EntityResolver entityResolver = getObjectContext().getEntityResolver();
04     ObjEntity entity = entityResolver.getObjEntity(getName());
05     Iterator attributes = entity.getAttributes().iterator();
06     IPropertyDescriptor[] properties = new IPropertyDescriptor[entity.getAttributes().size()];
07     int i = 0;
08     while (attributes.hasNext()) {
09       ObjAttribute attribute = (ObjAttributeattributes.next();
10       properties[i++new TextPropertyDescriptor(attribute.getName(), attribute.getName());
11     }
12     return properties;
13   }
14   return new IPropertyDescriptor[0];
15 }

3.4.3 Other visual model elements

GEF requires some other elements in the model. They are visual model elements in the sense described above - they are represented by components on the screen. In some applications they may also handle some business logic, but in the database editor they do not.

Single and collection model elements in the display

In the database editor connections between elements represent relationships between different objects. If a painting belongs to an artist, the components which represent the painting and the artist on screen should be connected. Connections are represented in the model by the classes Connection, RelationshipConnection and MemberConnection. The first is abstract. The second represents a relationship between two database records - a foreign key constraint between tables.

The third is a consequence of the use of collections to manage more than one persistent object. While a persistent object may appear more than one collection, it should only appear once as a single element. Otherwise our representation of the database would not be accurate: we would be showing more than one copy of an object when in reality there is only one, linked to by a number of different objects. The solution is to let a number of collections point to the same single element and indicate that it is one of the objects which they manage. That is, there should be a connection between a SingleModelElement and a CollectionModelElement which indicates that the object contained in the single element is also contained in the collection. This is represented in the model as a MemberConnection.

Finally, every GEF program must have a diagram. The diagram is the part of the model used to manage all the other elements which make up the model. Other visual elements are added to and removed from the diagram in order to appear in the display. In the database editor this role is fulfilled by ElementDiagram.

3.5 Adding the controller

The majority of the work involved in developing a GEF-based application comes in writing the controller component. The controller is made up of two main elements: parts, which broker requests from the user and respond to changes in the model; and commands, bite-sized changes to the model representing one interaction. In addition, policies are used to handle the mapping of requests (provided to parts by GEF to indicate user interactions) to commands.

3.5.1 Parts

Generally, the hierarchy of part classes will reflect the hierarchy of visual model elements. In the database editor the ElementEditPart corresponds to ModelElement, and SingleEditPart and CollectionEditPart correspond to SingleModelElement and CollectionModelElement respectively. The actually mapping of the model elements to their parts is done by ElementEditPartFactory, which takes a model class as input and returns a part.

01 public EditPart createEditPart(EditPart context, Object modelElement) {
02   // get EditPart for model element
03   EditPart part = getPartForElement(modelElement);
04   // store model element in EditPart
05   part.setModel(modelElement);
06   return part;
07 }
08 
09 /**
10  * Maps an object to an EditPart. 
11  @throws RuntimeException if no match was found (programming error)
12  */
13 private EditPart getPartForElement(Object object) {
14   if (object instanceof ElementDiagram) {
15     return new ElementDiagramEditPart();
16   }
17   if (object instanceof CollectionModelElement) {
18     return new CollectionEditPart();
19   }
20   if (object instanceof SingleModelElement) {
21     return new SingleEditPart();
22   }
23   if (object instanceof Connection) {
24     return new ConnectionEditPart();
25   }
26   throw new RuntimeException(
27     "Can't create part for model element: "
28     ((object != null? object.getClass().getName() "null")
29   );
30 }

Parts are the heart of the controller. Each part listens for property change notification from its underlying model so that it can update the view. This is done through the Java Beans property change mechanism. Each persistent object inherits a java.beans.PropertyChangeSupport from the common super-class AbstractObject. When a change is made to the object - for instance by writing a property or deleting the object - a property change event is fired using the PropertyChangeSupport. These events are received by the ModelElement which contains the persistent object, but they are not handled there. They are merely fired onwards - the visual model element recognises that its data has changed, and therefore it fires a property change event of its own to notify its part.

01 public class AbstractObject extends PersistentObject {
02   /** Property indicating that the object has been modified */
03   public static final String PROP_MODIFIED = "AbstractObject.Modified";
04   
05   /** Delegate used to implemenent property-change-support. */
06   private transient PropertyChangeSupport pcsDelegate = new PropertyChangeSupport(this);
07 
08   ...
09 
16   protected void firePropertyChange(String property, Object oldValue, Object newValue) {
17     if (pcsDelegate.hasListeners(property)) {
18       pcsDelegate.firePropertyChange(property, oldValue, newValue);
19     }
20   }
21 
22   ...
23 
24   public void setPropertyValue(Object id, Object value) {
25     ...
26     firePropertyChange(PROP_MODIFIED, null, this);
27   }
28   
29   ...
30 }

Each part is produced by the factory when a visual model element is added to the diagram. The first thing that the part does is to add itself as a property change listener for events from its model. As well as passing on events caused by changes in persistent objects, the ModelElement fires events of its own. This occurs when connections are added or removed or data that the model element contains - data such as the size and location of the element and the number of records it contains - are altered. In this way, changes to both the business elements and the visual elements of the model are monitored in the part.

01 /**
02  @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
03  */
04 public void propertyChange(PropertyChangeEvent evt) {
05   String prop = evt.getPropertyName();
06     
07   if (ModelElement.PROP_BOUNDS.equals(prop)) {
08     Figure tableFigure = (FiguregetFigure();
09     Rectangle constraint = (Rectangleevt.getNewValue();
10     ElementDiagramEditPart parent = (ElementDiagramEditPartgetParent();
11     parent.setLayoutConstraint(this, tableFigure, constraint);
12   }
13   else if (ModelElement.PROP_SOURCE_CONN.equals(prop)) {
14     refreshSourceConnections();
15     // in case relationship button states have changed
16     ((ElementFiguregetFigure()).refresh();
17   }
18   else if (ModelElement.PROP_TARGET_CONN.equals(prop)) {
19     refreshTargetConnections();
20   }
21   else {
22     // for any other kind of property change, refresh the figure
23     if (Display.getCurrent() != null) {
24       ((ElementFiguregetFigure()).refresh();
25     }
26   }
27 }

Parts also handle requests prompted by user interactions, executing commands which will change the model. Whereas the mechanism for notification of changes to the model is chosen by the developer, the request system is mandated by GEF. When the user interacts with the diagram, standard requests are issued to the parts which are involved in the interaction. Each part must be capable of responding to the request with a command. For some commands this can involve some processing in order to determine what ought to be done; but for all the commands in the database editor a simple mapping is all that is required. Additionally, because all commands can be applied to both CollectionModelElement and SingleModelElement via their respective parts, the mapping is handled in the part super-class ElementEditPart.

01 @Override
02 public void performRequest(Request request) {
03   getViewer().getEditDomain().getCommandStack().execute(getCommand(request));
04 }
05 
06 @Override
07 public Command getCommand(Request request) {
08   if (request.getType().equals(HideAction.HIDE_REQUEST)) {
09     return new HideElementCommand(this.getModelCast());
10   }
11   if (request.getType().equals(InsertAction.INSERT_REQUEST)) {
12     return new ObjectInsertCommand(this.getModelCast());
13   }
14   if (request.getType().equals(RemoveAction.REMOVE_REQUEST)) {
15     return new RemoveObjectCommand(this.getModelCast());
16   }
17   if (request.getType().equals(ExistingObjectRequest.EXISTING_REQUEST)) {
18     return new ExistingObjectCommand(this.getModelCast()((ExistingObjectRequestrequest).getSelectedObject());
19   }
20   if (request.getType().equals(DeleteAction.DELETE_REQUEST)) {
21     return new DeleteObjectCommand(this.getModelCast());
22   }
23   if (request.getType().equals(SetNameElementRequest.SET_NAME_REQUEST)) {
24     return new SetNameElementCommand(this.getModelCast()((SetNameElementRequestrequest).getName());
25   }
26   return super.getCommand(request)
27 }

In addition, there are parts representing connections (ConnectionEditPart) and the diagram (ElementDiagramEditPart). The diagram part handles some important global interactions, such as adding and removing elements and changing the layout, and it is notified of changes in the underlying diagram model using the property change mechanism described above. The connection part merely specifies the way that it is to be represented in the display, distinguishing between RelationshipConnection and MemberConnection through the use of a dashed line in the latter.

3.5.2 Policies

Policies are used to generalise the translation of a request to a command, so that the same mapping can be applied to numerous parts. Even where this is not needed, policies are also a clean way of handling standard requests generated by GEF. Policies are installed in parts, and requests which are not handled by the part are passed on to its policies. Installing policies saves each part from having to separate out standard requests - they can simply be left to fall through to the appropriate policy.

The database editor uses policies sparingly. This is because most interaction in the editor is handled through the context menu (discussed below). Policies are used for open/double-click (CollectionSelectionPolicy) and direct edit/single-click (CollectionDirectEditPolicy) on CollectionEditPart in order to allow scrolling through records and 'popping out' of individual objects from the collection. Drag and drop is enabled for ElementEditPart through the use of a layout edit policy (ElementLayoutEditPolicy). Policies are also used on ElementEditPart to allow elements to be moved when manual layout is selected (ElementXYLayoutEditPolicy), and to ensure selection handles appear in automatic layout mode (SelectionHandlesEditPolicy).

3.5.3 Commands

All changes to the model should be encapsulated in commands. This encourages reuse of code between parts and reduces the complexity of the part. In addition, GEF commands can be undone and redone, enhancing the user experience with little development overhead. Commands need not worry about the view - changes to the model will result in notification of the part, which in turn will update the view.

Commands should generally be executed using the command stack, in order to ensure that undo and redo are available. The command stack is available through the EditDomain, which is itself accessible through the EditPartViewer. It is also available from within actions which are sensitive to the selection (see below).

01 /**
02  * Handles action events from its figure's relationship buttons,
03  * expanding or contracting a relationship.
04  */
05 public void actionPerformed(ActionEvent event) {
06   ButtonModel buttonModel = ((Buttonevent.getSource()).getModel();
07   if (buttonModel.isSelected()) {
08     getViewer().getEditDomain().getCommandStack().execute(new ExpandRelationshipCommand(getModelCast()(RelationshipbuttonModel.getUserData()));
09   }
10   else {
11     getViewer().getEditDomain().getCommandStack().execute(new ContractRelationshipCommand(getRelationshipConnection((RelationshipbuttonModel.getUserData())));
12   }
13 }

Some thought needs to be invested into the best manner in which to implement undo and redo, and what information needs to be stored in order to do so. Sometimes a command can involve the execution of another command. This is one case where the developer may wish to avoid using the command stack, instead executing the command directly, as it is not desired that the command be undoable separately from its parent. In this case, the sub-command needs to be stored in order to be able to be undone if the parent command is undone.

3.6 Implementing the view

3.6.1 Figures

Each part specifies a figure which will represent it on the canvas. GEF utilises Eclipse's Draw2D plugin for its canvas, meaning that figures are defined using Draw2D's drawing capabilities. The figures used in the database editor extend org.eclipse.draw2d.Figure, but it is possible to instead implement the IFigure interface from the same package.

Most of the figures used in the database editor are based on the CompartmentFigure explained in Daniel Lee's Eclipse Corner article.[3]. As the figures for CollectionEditPart and SingleEditPart - CollectionFigure and SingleFigure respectively - are largely the same, they share a common base class: ElementFigure.

Buttons on the CollectionFigure

Draw2D provides a simple and powerful API for producing geometric diagrams. This covers most of what is required in a visual editor. However, introducing custom interaction can be troublesome because it doesn't fit neatly within GEF's framework. Buttons have to be wired in by the user. They could utilise the request process, but given that they have to know which part to send the request to in any case it is just as simple to call a function in the part which carries out the desired action. The lesson is - introduce custom interaction as little as possible!

The database editor requires buttons in two places. The first is the expand relationship button found in SingleFigure. This notifies SingleEditPart that the button has been pressed, resulting in the expansion of a relationship (using a command - see code above).

Buttons on the SingleFigure

The second is a two stage process. Each attribute of the persistent object being displayed in a CollectionFigure is represented by a button. When the button is pressed, a list of buttons corresponding to the values of that attribute in all of the objects in the collection is displayed below the button. This is a purely visual change, and so it happens in the figure. When one of the buttons in the list is pressed, CollectionEditPart is notified and the model is told that the selected object ought to be displayed.

3.6.2 Layout

Layout is a complicated business. The layout policy determines whether parts in the diagram can be moved and resized. The layout can also arrange the parts automatically, whether based on some custom calculation or a best-effort graph layout algorithm.

The database editor utilises a slightly modified version of the layout manager found in Phil Zoio's Schema Editor example. It provides the ability to toggle between automated layout, using a graph layout algorithm built into Draw2D, and manual layout, where the user can move parts around the canvas. Since the Schema Editor example was published the graph layout algorithm can now handle graphs which are not fully connected, removing some complexity for the developer.

The mechanism for toggling between the layouts is not complicated. The ElementDiagramEditPart is assigned DelegatingLayoutManager to manage its layout. That class delegates layout responsibility to either an automatic layout class (GraphLayoutManager) or a manual layout class (GraphXYLayout), depending on the value of an attribute in ElementDiagram. Automatic layout constructs a graph from the diagram, utilises org.eclipse.draw2d.graph.DirectedGraphLayout to calculate the preferred layout and then applies the results to the diagram. Manual layout queries each element of the model to find out where it should be located. When the layout mode is changed, the DelegatingLayoutManager sets the diagram's layout policy to allow or disallow the manual movement of parts on the canvas.

3.7 Other components

The discussion above is restricted to GEF and its modus operandi. The database editor features a number of components which are involved in presenting GEF in a usable manner: an Eclipse view; the select query wizard; the editor and its entourage of perspective, plugin and input; and the plugin configuration files. These will be explained only as they relate to GEF.

3.7.1 The editor

The editor extends org.eclipse.gef.ui.parts.GraphicalEditor in order to provide an editor without a palette of tools. This is in spite of dire warnings not to do so in the API, as extending this class (and its relatives with tool palettes) seems to be the custom in all of the examples available for GEF. The main GEF-related task which the editor has to deal with is configuring the org.eclipse.gef.GraphicalViewer which it contains.

01 /**
02  * Configure the graphical viewer before it receives contents.
03  @see org.eclipse.gef.ui.parts.GraphicalEditor#configureGraphicalViewer()
04  */
05 @Override
06 protected void configureGraphicalViewer() {
07   super.configureGraphicalViewer();
08   
09   GraphicalViewer viewer = getGraphicalViewer();
10   viewer.setEditPartFactory(new ElementEditPartFactory());
11   viewer.setRootEditPart(new ScalableFreeformRootEditPart());
12   viewer.setKeyHandler(new GraphicalViewerKeyHandler(viewer));
13 
14   // configure the context menu provider
15   ContextMenuProvider cmProvider = new ObjectEditorContextMenuProvider(viewer, getActionRegistry());
16   viewer.setContextMenu(cmProvider);
17   getSite().registerContextMenu(cmProvider, viewer);
18 }

3.7.2 Actions and the context menu

In order to make actions available in the context menu, they need to be created and added to the action registry. Actions which are sensitive to the current selection on the canvas should also be registered as selection actions. This registration process happens in ObjectEditor.

01 @Override
02 protected void createActions() {
03   super.createActions();
04   
05   IAction action = new HideAction(this);
06   getActionRegistry().registerAction(action);
07   getSelectionActions().add(action.getId());
08     
09   ...
10 
11   action = new ToggleLayoutAction(this);
12   getActionRegistry().registerAction(action);
13 }

The next step is to implement an org.eclipse.gef.ContextMenuProvider to be added to the editor's GraphicalViewer when the editor is configured. As the context menu is built each time that the right mouse button is used in the diagram, the provider should check each action to see if it is enabled before adding it to the menu. In the database editor the ObjectEditorContextMenuProvider fills this role.

01 /**
02  * Creates context menu items for the editor.
03  @see org.eclipse.gef.ContextMenuProvider#buildContextMenu(org.eclipse.jface.action.IMenuManager)
04  */
05 @Override
06 public void buildContextMenu(IMenuManager menu) {
07   // Add standard action groups to the menu
08   GEFActionConstants.addStandardActionGroups(menu);
09   
10   // Add actions to the menu
11   menu.appendToGroup(
12       GEFActionConstants.GROUP_UNDO, // target group id
13       getAction(ActionFactory.UNDO.getId()))// action to add
14   ...
15 
16   IAction action = getAction(HideAction.ID);
17   if (action.isEnabled()) {
18     menu.appendToGroup(GEFActionConstants.GROUP_EDIT, action);
19   }
20 
21   ...
22 }

To make standard workbench actions - such as redo, undo and delete - available to the user, an org.eclipse.gef.ui.actions.ActionBarContributor must be specified in the plugin file. In the database editor, the ROPActionBarContributor enables the undo, redo and print actions. This also ensures that the standard keyboard shortcuts are available for these actions.

01 public class ROPActionBarContributor extends ActionBarContributor {
02 
03   @Override
04   protected void buildActions() {
05     IWorkbenchAction undo = ActionFactory.UNDO.create(getPage().getWorkbenchWindow());
06     addRetargetAction((RetargetActionundo);
07   
08     ...
09   }
10 
11   ...
12 }

Finally, the actions themselves need to be implemented. Actions which depend upon the current selection should extend org.eclipse.gef.ui.actions.SelectionAction; other actions can extend org.eclipse.jface.action.Action. Actions need to indicate whether they are enabled (for the context menu provider) and carry out an action when they are selected. For actions descended from SelectionAction, enablement usually depends upon whether all selected parts are of the right type and in an acceptable state, and execution usually involves sending a request to each selected part and executing the command which is returned via the command stack.

3.8 Three tier Cayenne

As mentioned earlier, Cayenne classically operates in a two tier configuration. A three tier mode - referred to as remote object persistence or ROP - has recently been introduced to the project. A three tier setup was chosen for this application in order to demonstrate some of the more advanced features of Cayenne. This section describes how to set up Cayenne in a three tier configuration and then explores the more advanced features which it offers.

3.8.1 Setup

Server project structure

In three tier mode Cayenne runs as a single-servlet web application: a servlet connects to the database and brokers requests from multiple clients. The servlet can be hosted in any number of containers, but a very convenient way to develop a three tier Cayenne application is using Jetty and JettyLauncher. This combination allows the server to be launched and debugged inside Eclipse. The required structure for a JettyLauncher project is detailed on the JettyLauncher site and demonstrated in the database editor.

Setting up the servlet is fairly simple. The required libraries are detailed in the Cayenne documentation (be sure to include the library for connecting to your chosen database), as is the servlet specification:

01 <web-app>
02   <!-- on session timeout server-side DataContext will be deallocated
03        shared DataChannels will be deallocated when no sessions are using them 
04   -->
05   <session-config>
06     <session-timeout>60</session-timeout>
07   </session-config>
08   
09   <!-- Deploying Cayenne distributed service using Hessian OPP transport -->
10   <servlet>
11     <servlet-name>rop-browser</servlet-name>
12     <servlet-class>org.objectstyle.cayenne.remote.hessian.service.HessianServlet</servlet-class>
13   </servlet>
14   <servlet-mapping>
15     <servlet-name>rop-browser</servlet-name>
16     <url-pattern>/rop-browser</url-pattern>
17   </servlet-mapping>
18 </web-app>

The map files generated from the Modeler need to be included in the project in a source folder to ensure they are available to the servlet. The servlet also needs to contain the client and server classes generated from Cayenne Modeler. These can be copied from the client project, but linking the Eclipse project for the client application as a dependency is better as it avoids having two copies of the client classes.

3.8.2 Connecting to the server

In terms of programming, using Cayenne in a three tier configuration is almost identical to using it in classic two tier mode; only one minor change is required. Whereas a traditional Cayenne application requests a DataContext via a local method of that class, a three tier application creates a connection to the server, wraps the connection in a DataChannel and then draws a CayenneContext from the channel. However, as both CayenneContext and DataContext implement the ObjectContext interface, the developer can avoid dependency on either a two or three tier configuration by treating the context as an ObjectContext. In both cases Cayenne looks after the actual database connection based on the settings contained in its metadata files.

1 HessianConnection connection = new HessianConnection(address);
2 DataChannel channel = new ClientChannel(connection);
3 ObjectContext context = new CayenneContext(channel, true, false);

3.8.3 Server-side tricks

In order to ensure that this application works 'out of the box' it was necessary to find a way to guarantee the existence of the database. Otherwise, someone running the application for the first time would get blank canvases in response to their queries. One possibility was to rely upon Derby's cross-platform capability and simply ship database files with the servlet. However, this proved annoying when combined with Subversion, as the database would have to be reverted each time before code could be committed. As a result - and to demonstrate another feature of Cayenne - a slightly more sophisticated approach has been taken.

Cayenne's DbGenerator class is capable of generating a database from the Cayenne mapping metadata. The database editor makes use of this class through a javax.servlet.Filter, checking to see if the database exists in init and creating it if it doesn't. The filter is specified in web.xml. For web application developers, Cayenne provides a WebApplicationContextFilter which automatically ensures that every request thread has a DataContext associated with it.

3.8.4 Advanced functionality

The web.xml file for the database editor's server has a section commented out. This section configures communication between different instances of the client using XMPP, an open messaging protocol. Cayenne uses this communication channel to share changes to the objects which it manages, transparently enabling collaborative editing. In terms of application code, adding this functionality to the application involves merely changing the connection to one which uses a shared session rather than individual session for each client.

01 <servlet>
02   <servlet-name>rop-browser</servlet-name>
03   <servlet-class>org.objectstyle.cayenne.remote.hessian.service.HessianServlet</servlet-class>
04   <init-param>
05     <param-name>cayenne.RemoteService.EventBridge.factory</param-name>
06     <param-value>org.objectstyle.cayenne.event.XMPPBridgeFactory</param-value>
07   </init-param>
08   <init-param>
09     <param-name>cayenne.XMPPBridge.xmppHost</param-name>
10     <param-value>localhost</param-value>
11   </init-param>
12   <init-param>
13     <param-name>cayenne.XMPPBridge.xmppPort</param-name>
14     <param-value>5222</param-value>
15   </init-param>
16   <init-param>
17     <param-name>cayenne.XMPPBridge.xmppChatService</param-name>
18     <param-value>conference</param-value>
19   </init-param>
20 </servlet>

The difficult part of setting up XMPP communication is in configuring the external elements. First, the smack and smackx libraries need to be made available to the servlet. Second, an XMPP server needs to be available to the clients. Jive Software's Wildfire XMPP server is freely available, as are many other servers. Third, the server needs to be configured to match the settings given in the web.xml file.

Of particular difficulty in this last step is the cayenne.XMPPBridge.xmppChatService parameter. Cayenne presumes that the XMPP server will be configured with the chat service on a sub-domain. This parameter specifies the name of the sub-domain. In order to ensure that the chat service resolve correctly when the XMPP server is running locally it is necessary to add an entry to the hosts file specifying the sub-domain. The service name defaults to 'conference', and the corresponding entry in the hosts file is:

127.0.0.1       conference.localhost

The location of the hosts file varies between platforms; see http://en.wikipedia.org/wiki/Hosts_file#Location.

With these steps complete, the XMPP server should register a new session for each client. When a connection is made to the servlet the client specifies the name of the shared session which they wish to join. Each shared session that is established has a corresponding group chat room on the XMPP server through which messages are passed. Changes to the objects in one client are immediately passed on to other clients in the same shared session and applied to the local objects there. It is automatic and transparent - no extra code, just join the same shared session on the same server.

3.8.5 The future

A project is currently underway to migrate ROP from the binary Hessian protocol which it presently uses to a more standard WSDL defined web service. This will allow for the implementation of the stripped-down client library in other languages, considerably extending Cayenne's reach. Implementations in other languages are yet to be undertaken, but the idea has surfaced on the development mailing list and interest in an Objective-C implementation has been noted.[4]

4 CONCLUSION

One of the most positive aspects of both GEF and Cayenne is the speed at which they can be adopted. Before developing this application I had used neither GEF nor Cayenne, yet eight weeks later I had developed a very functional database editor. Both have excellent documentation and support, and the Cayenne mailing list deserves particular mention for speed and enthusiasm.

GEF gives excellent support for diagrammatic representations of data. It simplifies development of visual editing tools by providing a standard framework and encourages robust design through the structure which it mandates. It depends upon the Eclipse platform, but with the ability to deploy the resulting product as either a plugin or a standalone application (using RCP), that is a very minor limitation.

Cayenne offers a mature solution for accessing databases in an object-oriented manner, an intuitive style for Java programmers. This article has only touched upon Cayenne's more basic capabilities in terms of persistence - it has much more to offer for more sophisticated applications. In particular, it is commonly used in web applications with frameworks such as Struts and Tapestry.

The database editor discussed in this article will remain in the repository at the Cayenne project. Just like the rest of Cayenne, it is open source, so if it is useful to you then feel free to develop and extend it.


[1] Randy Hudson, 'Create an Eclipse-based application using the Graphical Editing Framework' at http://www-128.ibm.com/developerworks/opensource/library/os-gef/; Bo Majewski, 'A Shape Diagram Editor' at http://www.eclipse.org/articles/Article-GEF-diagram-editor/shape.html; Phil Zoio, 'Building a Database Schema Diagram Editor with GEF' at http://www.eclipse.org/articles/Article-GEF-editor/gef-schema-editor.html; IBM, 'Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework' at http://www.redbooks.ibm.com/Redbooks.nsf/RedbookAbstracts/sg246302.html
[2] Randy Hudson's article (above n 1) introduces this idea with the terms 'business' and 'view'. I use 'visual' rather than 'view' in order to avoid confusion with the use of the term 'view' in MVC.
[3] Daniel Lee, 'Display a UML Diagram using Draw2D' at http://www.eclipse.org/articles/Article-GEF-Draw2d/GEF-Draw2d.html.
[4] See http://cwiki.apache.org/CAY/cocoa-cayenne.html.