Event-Handling

An MPX-enabled SDK application can subscribe to specific server events (for example, an Item is added, modified or deleted), and provide event-handlers that are invoked when those events occur.

The event-handling APIs support a new class of SDK applications that, without MPX publish/subscribe services, would be very difficult to write and prohibitively expensive to run.

For example, imagine an application that wants to perform some operation whenever a new ChangeRequest is added to a View. Without MPX, the application would need to poll for changes. Every so often, it would need to wake up, refresh all ChangeRequests in the View, and figure out which, if any, were added since the last time it checked. Checking for changes is expensive, and therefore the polling interval needs to be long enough that the application does not consume too many system resources (on the client workstation or on the StarTeam server). This introduces a significant delay between the time the change occurs and the time the change is detected by the application. In addition, we have to pay the cost of checking even when no changes have in fact occurred.

With MPX, an application simply registers an event handler with the View, and waits for it to be invoked by the SDK.

Event handling is supported in both the Java and COM APIs. In Java, the StarTeam event-handling APIs use the standard Java listener model. In COM, event-handling is implemented by way of connectable objects. Although the general principles are the same in both models, there are significant differences between the two that affect how an application implements and registers event handlers.

Event handling is described in detail in the following subsections:

Event Handling in Java

In Java, the SDK event-handling APIs use the listener model. In this model, event-handlers are abstract methods defined in an interface. By convention, listener interfaces extend the standard java.util.EventListener interface, which is a marker interface defining no methods of its own.

Also by convention, each event-handler method takes a single parameter, derived from java.util.EventObject. The event object provides additional information relevant to the event, which the application may find useful in handling the event.

Application programs can provide an implementation of the listener interface, and register it with an appropriate object by way of an addListener() method. Once the listener is registered, its event-handler methods are invoked when the appropriate events occur. When the application is no longer interested in handling events, it can un-register the listener by way of a removeListener() method.

For convenience, the SDK implements a default adapter class for each defined listener interface. The adapter class provides an empty implementation of each event-handler method. An application can choose to extend the adapter class, rather than implement the corresponding listener interface directly, overriding only those event-handler methods that it cares about.

server.addProjectListener(new ProjectAdapter() {
    public void projectAdded(ProjectEvent event) {
        Project p = event.getProject();
        System.out.println("Added: " + p.getName());
    }
});

Here, the application registers a ProjectListener with a Server object. The projectAdded() method is invoked whenever a new project is added to the repository. The event parameter provides access to a Project object representing the newly added project.

Scope

Listeners are always registered with an SDK object that defines the scope of interest. In some cases, a given listener can be registered at different levels of the SDK object hierarchy, providing the application with a great deal of flexibility in defining the scope of interest.

For example, suppose an application is interested in knowing when a new Item of a given type is added to the repository:

ItemListener myListener = new ItemAdapter() {
    public void itemAdded(ItemEvent event) {
        System.out.println("Item Added.");
    }
};

The application may choose to register this listener with a specific View:

view.addItemListener(myListener, type);

In this case, the itemAdded() event-handler would be invoked whenever an Item of the appropriate type is added anywhere in the View. Alternatively, the application may choose to register the listener with a specific folder in the folder tree:

folder.addItemListener(myListener, type, depth);

In this case, the event-handler would only be invoked when a new Item is added to the given folder, or, optionally, to one of its child folders up to the given depth. Finally, the application may choose to register the listener with an ItemListManager:

itemListManager.addItemListener(myListener);

In this case, the application has the most flexibility in defining the scope of interest. The ItemListManager can limit the scope to an arbitrary set of folders in the view, not necessarily contained within a single sub-tree. In addition, the ItemListManager can provide a Filter (and associated Query) that further refines the scope of interest to include only those Items that match a very specific set of criteria.

There is no way to directly specify a scope that includes all Items in the entire repository, or even all Items across an entire Project. You may, however, register the same listener with multiple Views.

Security

Event-handlers are always attached to an SDK object that defines the scope of interest. That scope also defines the security context. Event-handlers use the same object model as the rest of the SDK; there is no way for an application to retrieve any data via an event-handler that it would not normally be able to retrieve outside the event-handler in the same security context.

For example, suppose an application has registered an ItemListener with some View. Also suppose that a new Item of the appropriate type is added to that View in the repository, but in a Folder where the logged-in user does not have access rights. In this case, there are no events triggered, and no event-handlers invoked in the application.

ItemListeners

In several cases, the SDK defines a listener interface that is itself a marker interface, with no methods of its own. In such cases, multiple listener interfaces that extend the marker interface will also be defined.

The most important example of this is the IItemListener marker interface, which has the following associated extended interfaces:

ItemListener: Implemented by applications that want to know detailed information about new, moved, changed and removed Items of a given type. The corresponding ItemEvent provides access to a valid Item object that describes the change.

ItemListListener: Implemented by applications that want to know when the set of available Items of a given type has changed, but are not interested in information about the changed Items themselves. The corresponding ItemListEvent provides access to the type of the changed Items and the Folder that contains them, but not the Items themselves. An ItemListEvent is less expensive to create than an ItemEvent; thus, ItemListListener is preferred over ItemListener for applications that do not require the detailed Item information.

ItemIDListener: Implemented by applications that need to know when an item has been added, changed or removed, but do not require Item objects in the proper Folder context. The corresponding ItemIDEvent provides only an ItemID. The application can use this ID to retrieve a disembodied Item, or it can use View.findItem() to find the item within an application-mantained folder tree. ItemIDListener is a compromise between ItemListListener and ItemListener; it provides more information than ItemListListener, but is not as expensive (particularly with respect to memory usage) as ItemListener.

NotificationListener: Implemented by applications that want near real-time information about "notifications" from the StarTeam server. Notifications are generated when certain Item types reach certain well-defined states. For further information, please consult the StarTeam user documentation.

All addItemListener() methods (of View, Folder and ItemListManager objects) take an IItemListener (the marker interface) as a parameter. An application will actually implement one (or more) of ItemListener, ItemListListener, ItemIDListener, or NotificationListener.

Writing Event Handlers

The SDK event-handling mechanisms are designed to make it as easy as possible for an application to write event handlers. For this reason, the event objects always describe events using SDK objects that StarTeam application developers are already familiar with.

Furthermore, very few restrictions are placed on what can be done via the event objects. For example, when the event object provides access to an Item, it is always a fully functional Item object (as opposed to, for example, a disembodied object). The Item object is always attached to a valid parent Folder, which is actually a member of a valid Folder tree attached to a valid View, and so on. The associated Type and Property information is up-to-date, and the Item properties are fully populated, representing a snapshot of the Item at the time the event was triggered.

For example, consider an application that wants to know when new Items of a given type are added to a given view:

view.addItemListener(new ItemAdapter() {
    public void itemAdded(ItemEvent event) {
        System.out.println("Item Added.");
}, type);

What if, sometime after the event handler is registered, new Folders are added to the View, and new Items are then added to the new Folders? What does the application need to do in its event handlers to ensure that an ItemEvent can provide its Item in the context of a valid parent Folder that the application didn't even know about?

The application doesn't need to do anything at all. It is the SDK's responsibility to make sure that the information in the event objects is valid, independent of what the application might be doing to its own objects, either inside or outside the context of its event-handlers. So, for example, the application does not need to keep refreshing the folder tree to guarantee that it will see all the right Item events in the proper context.

As another example, consider an application that wants to know when a new Project is created:

server.addProjectListener(new ProjectAdapter() {
    public void projectAdded(ProjectEvent event) {
        Project p = event.getProject();
        Folder f = p.getDefaultView().getRootFolder();
        System.out.println("Root folder: " + f.getName());
}, type);

When a new Project is created, a default View is created automatically, and a root Folder is then created for the new View. But is there a small window of time where the Project exists, without a default View? Can the application safely access the default View and its root Folder in the projectAdded() event handler?

As it turns out, there is a window where the Project exists without a default View. However, it is the SDK's responsibility to ensure that the ProjectEvent provides a fully functional Project object for use in the event-handler. Thus, the SDK guarantees that the event-handler is not invoked until the Project has a valid default View with a valid root Folder.

In summary, SDK application developers should be able to write event-handlers using the same basic techniques they have been using to write more traditional SDK applications. Proposed designs will always need to be evaluated with respect to performance, potential impact on server workload, and so on. There are no significant constraints that limit what can be done within the event-handler itself.

Threading Model

Event handlers are invoked from an event-handling thread that is separate from the main application thread.

Each Server object has its own event-handling thread that is used to invoke all event-handlers registered with that Server object, all event-handlers registered with Projects or Views obtained from that Server, all Folders obtained from those Views, and so on. The event-handling thread is created when the first event-handler is registered.

Many event-handling applications will register some event handlers from the main application thread, and then wait for events to occur. For convenience, the Server object supports a handleEvents() method:

public void handleEvents();

Places the current thread in an event-handling state. In this state, the thread is usually asleep, waking occasionally just long enough to ping the server (keeping the connection alive).

The event-handling loop continues until some other thread (presumably, the event-handling thread for this Server object) calls interruptHandleEvents().

Events destined for a particular event-handling thread are inserted into a queue, and are handled in the order that they are received. The precise order of closely timed events is not completely predictable, since the StarTeamMPX event transmitter generates MPX messages asynchronously.

An event-handler that runs for a particularly long time will block other events waiting to be processed on the same event-handling thread. However, it will not prevent events from being processed by the event-handling threads of other Server objects, or otherwise impact correct operation of other MPX-related features of the SDK.

Design Considerations

Suppose an application needs to keep a list of Items up-to-date with respect to the latest contents of the StarTeam repository. One somewhat simplified way to do this might be as follows:

view.addItemListener(new ItemAdapter() {
    public void itemAdded(ItemEvent event) {
        Folder f = view.getRootFolder();
        f.refreshItems(type.getName(), null, -1);
}, type);

Is this an appropriate use of the event-handling APIs? For some applications, it might be. For other applications, there might be better ways to accomplish the same thing. In order to make the right decision, there are several implementation-related issues to consider.

When the itemAdded() event-handler is invoked, the event parameter provides access to an Item object representing the newly added item. The event-handler has the full power of the SDK at its disposal; it can access the properties of the Item, the parent folder of the Item, other Items of the same Type with the same parent folder or, for that matter, Items in any other folder in the tree. All of this information must be available and valid independent of what else the application might be doing with its own SDK objects.

Obviously, the SDK event-handling mechanisms maintain their own state information to help make this whole process as efficient as possible. Still, there is some overhead required to prepare and maintain the objects to be passed to the event-handler. In addition, there is some overhead required to queue up the event, and marshal it to the proper event-handling thread.

On the other hand, since MPX is enabled, we know that the call to Folder.refreshItems() is itself highly optimized. Internally, the SDK has its own event-handlers, implemented at a lower level and wired directly into the SDK's caching mechanisms. These event-handlers are running whether or not the application has registered its own event handlers, essentially providing instant refresh services.

So, one alternative approach that the application should consider in this case is to move the call to Folder.refreshItems() from an event-handler to somewhere else: invoke it periodically in the application's idle loop, invoke it on a timer, etc.

Another approach that might be considered is to use an event-handler, but implement the ItemListListener interface instead of the ItemListener interface. The ItemListEvent object does not provide access to an Item; thus, using ItemListListener is more efficient than ItemListener, and would have been sufficient in this case.

Complex event-handling applications should carefully consider the available options during the design process. Prototyping might help to ensure that the approach selected is the best one given the specific requirements of the application.

Event Handling in COM

In COM, event handling is supported by way of connectable objects. A connectable object is simply an object that exposes events. The client application implements handlers for those events, and registers them by connecting the object to its event handlers.

Exactly how an object is connected to its event handlers varies from one programming environment to another. In Microsoft Visual Basic, an object is connected to its event handlers automatically whenever it is declared with the WithEvents keyword. For example, suppose Widget is a type that exposes a changed event. An application would indicate that it wants to handle this event by declaring an instance of Widget as follows:

Dim WithEvents myWidget As Widget

Visual Basic then automatically connects this object with its event handlers, which have specific names based on the name of the connectable object and the name of the event. The event handler for the myWidget changed events would be:

Private Sub myWidget_changed()
. . .
End Sub	  

Other automation-aware programming environments, such as the Windows Script Host, provide similar mechanisms.

Internally, what happens when an object is connected to its event handlers is fairly complex. You do not need to be concerned with the details unless you are implementing event handlers in C or C++, or are implementing a connectable COM object.

What is important to note here, though, is that the COM model imposes certain restrictions on the design of the StarTeam event-handling APIs:

For this reason, the SDK objects that expose events in the Java APIs (for example, Project, View, and Folder) are not themselves connectable objects in the COM APIs. Instead, new EventSource objects have been introduced specifically to support event handling.

There is one EventSource type in COM for each listener interface in Java. For example, StItemEventSource is the connectable object that supports ItemEvents; StItemListEventSource is the connectable object that supports ItemListEvents, and so on.

EventSource objects are created by factory methods defined in the relevant parent classes. For example, newItemEventSource() is a factory method defined in the View class (and also in the Folder class and the ItemListManager class) that returns an StItemEventSource. Where necessary, the factory methods take parameters that define the scope of interest. If a given parent class supports more than one type of EventSource, it will have a factory method for each type.

For example, to listen for ItemEvents in a given View, a Visual Basic application would do the following:

Dim WithEvents source As StItemEventSource
Set source = view.newItemEventSource(type)

Here, source is a connectable object that is an instance of StItemEventSource. It is created via the factory method newItemEventSource() of the view object. The item type is passed as a parameter to the factory method.

To handle itemAdded() events from this EventSource, the application would define the following:

Private Sub source_itemAdded(ByVal arg As IStItemEvent)
. . .
End Sub


Alternatively, the event handler could be registered with a folder instead of a view, as follows:

Dim WithEvents source As StItemEventSource
Set source = folder.newItemEventSource(type, depth)

To instead listen for ItemListListener events, the application would use a different factory method:

Dim WithEvents source2 As StItemListEventSource
Set source2 = folder.newItemListEventSource(type, depth)

The application would also implement a different event handler:

Private Sub source2_itemsChanged(ByVal arg As IStItemListEvent)
. . .
End Sub


Whether you are writing an event handler in COM or Java, the same basic principles apply. Event-handlers use the same object model as the rest of the SDK. Application developers should be able to write event-handlers using the same basic techniques they have been using to write more traditional SDK applications. There are no significant constraints that limit what can be done within the event-handler itself.

Examples

Several sample applications are provided that demonstrate StarTeam MPX event handling in different programming environments:

EventMonitor
An application that displays information about events relevant to a given StarTeam server as they occur. Demonstrates how to use the StarTeam MPX event-handling APIs. Implemented as a command-line program written in Java.
CRWatcher
A simple application that creates a few Change Requests, and waits for the corresponding itemAdded events. Compares StarTeam MPX event-handling in several popular programming languages. Implemented in Java, Visual Basic, JScript, and C++.

Limitations

StarTeamMPX does not currently publish events for all types of changes that can be made to a StarTeam repository. For example, no events are published for adding, changing or removing Links, for moving a Label on an Item, and so on.

 


 Back: Instant Refresh       Home