StarTeam MPX is a framework for publish/subscribe messaging. The StarTeamMPX Server uses an advanced caching and communication technology that both improves the performance of StarTeam clients and extends the scalability of the StarTeam Server. StarTeamMPX benefits StarTeam SDK applications in two important ways: •Instant Refresh. An MPX-enabled SDK application can refresh changes from the server with very little overhead. For applications that perform frequent refresh operations, the resulting performance benefits are significant. •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. Enabling MPX To use the StarTeamMPX features, an application must explicitly enable MPX. There are several ways to do this, described in the following subsections: •Server.enableMPX() •Server Constructor Server.enableMPX() The Server object provides methods to explicitly enable MPX: public void enableMPX() throws MPXException; Enables MPX using the server's default client MPX profile. The default profile is configured by the StarTeam Administrator. public void enableMPX(String strProfileName) throws MPXException; Enables MPX using the MPX profile with the given name. public void enableMPX(EventHandlerProfile p) throws MPXException; Enables MPX using the given MPX profile. It may be a profile saved persistently on the StarTeam server, or a profile created programmatically by the client application. When MPX is enabled, the SDK establishes a connection to the StarTeamMPX message broker, and subscribes to events for this server. Even though MPX is enabled through a specific Server object, it is really the communication layer between the SDK and the StarTeam repository that is MPX-enabled. Thus, enabling MPX on a specific Server object also enables MPX for other Server objects that share the same address and port. Server Constructor The Server object provides the following constructor: public Server(ServerInfo info); Constructs a Server object using the given connection information. ServerInfo specifies all the information required to establish a connection to a given StarTeam server. This includes an optional MPX profile name. If a Server object was constructed using a ServerInfo that specifies a valid MPX profile, then MPX is automatically enabled when Server.connect() is called. (For StarTeam 5.2 servers, MPX is not enabled until logOn() is called.) Note that some applications do not call connect() explicitly; however, it is called automatically by logOn() if necessary. If enableMPX() fails during the connect(), the resulting MPXException is caught and ignored, and the connect() operation succeeds. If the application needs to know whether or not enableMPX() was successful, it can call isMPXEnabled(). Instant Refresh When a StarTeam Server is MPX-enabled, information about changes to the repository is broadcast in an encrypted format through a publish/subscribe channel to MPX-enabled StarTeam clients. The StarTeam SDK uses the information broadcast in MPX messages to keep its internal caches up-to-date. This allows refresh operations to be serviced with very little overhead. For applications that perform frequent refresh operations, the resulting performance benefits are significant. Instant refresh is described in detail in the following subsections: •refresh() •isRefreshRequired() •discard() •copy() and isEqualTo() refresh() In general, the SDK's refresh() APIs are MPX-enabled. Thus, when an SDK application is MPX-enabled, it automatically benefits from instant refresh. For example, consider the following code fragment: // Populate all Files in the View. f = view.getRootFolder(); f.populateNow(type, null, -1); // or view.getViewMembers(type).getCache().populate(); . . . // Refresh Files to reflect recent changes. f.refreshItems(type, null, -1); // or view.getViewMembers(type).getCache().refresh(); The call to Folder.refreshItems() refreshes all Items of the given type to reflect recent changes to the repository. In a non-MPX application, this could be a very expensive operation. In an MPX-enabled application, the refresh operation is nearly instantaneous. An SDK object represents a snapshot of some piece of information from the StarTeam repository, as of some point in time. When that information is changed in the repository, the changes are never visible to the SDK application until they are explicitly requested via a refresh() operation. From the SDK's point of view, "instant refresh" does not mean that changes are visible as soon as they are made on the server; it simply means that when MPX is enabled, refresh operations can be performed very efficiently. In the common case where MPX is enabled and nothing has changed, refresh() is optimized to be as close to a no-op as possible. One implication is that refresh() operations do not create new objects when there are no changes required. In fact, refresh() operations always modify existing objects in place whenever possible. isRefreshRequired() Each of the SDK's refresh() methods has a corresponding isRefreshRequired() method. isRefreshRequired() returns true if the refresh() method called at the same instant in time might have resulted in changes to the objects in question. For example: bChanges = f.isRefreshItemsRequired(type, null, -1); // or bChanges = view.getViewMembers(type).getCache().isRefreshRequired(); This determines whether or not it may be necessary to refresh any items of the given type to reflect recent changes to the repository. isRefreshRequired() may return false positives; that is, it may return true in some cases where no relevant changes were made to the repository. For example, isRefreshRequired() may return true when a new item is added, even though the new item is not visible in the current security context. If MPX is not enabled, isRefreshRequired() always returns true. isRefreshRequired() is not intended to be used merely to avoid unnecessary calls to refresh(). For example: // An inefficient use of isRefreshRequired(). if (f.isRefreshItemsRequired(type, null, -1)) { f.refreshItems(type, null, -1); } This is actually slower than simply calling Folder.refreshItems() by itself. On the other hand, isRefreshRequired() is very useful in the following situation: // Proper use of isRefreshRequired(). if (f.isRefreshItemsRequired(type, null, -1)) { f.refreshItems(type, null, -1); doSomethingExpensive(); } Here, isRefreshRequired() is used to avoid unnecessary calls to an expensive application-specific method that must be called whenever the item list changes. discard() The SDK's discard() methods free up objects in various internal caches. If, for example, the properties of an Item are discarded, the next reference to any property of the Item will cause all of its properties to be re-fetched from the server. discard() is called by an application to help control memory usage. It is not uncommon to see an SDK application do the following: f.discardItems(type, -1); // or view.getViewMembers().getCache().discard(): f.populateNow(type, null, -1); when what was really intended is: f.refreshItems(type, null, -1); Although this was never strictly correct, it really does not result in any unexpected behavior in non-MPX aware versions of the SDK. In an MPX-aware version of the SDK, these two code fragments have very different behaviors. discard() always causes the relevant items to be removed from the SDK's internal cache. Thus, the subsequent populate() call will always require that the Item data be re-fetched from the server, even when MPX is enabled. An MPX-enabled refresh() operation, on the other hand, will only issue server commands in a relatively small set of situations. In addition, when a server command is required, it will usually fetch a much smaller set of data than a full refresh would have required. For these reasons, it is important to ensure that MPX-enabled applications use discard() for memory management only, and not where refresh() would have been more appropriate. copy() and isEqualTo() refresh() operations always modify existing objects in place whenever possible. consider the following code fragment: // Find a specific item. item1 = view.findItem(type, id); // Refresh the items. // Any changes to item1 will be made in place. f = view.getRootFolder(); f.refreshItems(type, null, -1); // Find the item again. // item1 and item2 are the same object! item2 = view.findItem(type, id); // Call application-specific code to // compare the two items. if (myItemHasChanged(item1, item2)) { // We should never get here, because // the two items are the same physical object. } This code fragment makes an incorrect assumption about the behavior of refresh(), and will not work when running against an MPX-aware version of the SDK. In order to compare the state of an item before and after a refresh() operation, an application should make a copy of the item before the refresh, and then compare the copy to the original after the refresh. The SDK provides copy() and isEqualTo() methods to facilitate this. For example: // Find a specific item. item1 = view.findItem(type, id); // Make a copy of the item in its current state. item2 = item1.copy(); // Refresh the items. // Any changes to item1 will be made in place. f = view.getRootFolder(); f.refreshItems(type, null, -1); // Compare the two items. if (!item1.isEqualTo(item2)) { // We'll get here if the item changed // during the refresh operation. } The copy() method always ensures that the properties of the item are fully populated before making the copy. isEqualTo() compares all the properties of the item 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 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 C# APIs, using the standard Java or C# listener model. Event handling is described in detail in the following subsections: • Event Handling in Java • Scope • Security • Item Listeners • Writing Event Handlers • Threading Model • Design Considerations 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. server.addProjectListener(new ProjectListener() { public void projectAdded(ProjectEvent event) { Project p = event.getProject(); System.out.println("Added: " + p.getName()); } public void projectChanged(ProjectEvent event) {} public void projectDeleted(ProjectEvent event) {} }); 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 ItemListener() { public void itemAdded(ItemEvent event) { System.out.println("Item Added."); } public void itemChanged(ItemEvent event) { System.out.println("Item Changed."); } public void itemMoved(ItemEvent event) { System.out.println("Item Moved."); } public void itemRemoved(ItemEvent event) { System.out.println("Item Removed."); } }; 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 a ViewMemberListManager: viewMemberListManager.addItemListener(myListener); In this case, the application has the most flexibility in defining the scope of interest. The ViewMemberListManager can limit the scope to an arbitrary set of folders in the view, not necessarily contained within a single sub-tree. In addition, the ViewMemberListManager 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(myListener, 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 ProjectListener() { public void projectAdded(ProjectEvent event) { Project p = event.getProject(); Folder f = p.getDefaultView().getRootFolder(); System.out.println("Root folder: " + f.getName()); }, type); public void projectChanged(ProjectEvent event) {} public void projectDeleted(ProjectEvent event) {} 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 ItemListener() { public void itemAdded(ItemEvent event) { Folder f = view.getRootFolder(); f.refreshItems(type.getName(), null, -1); }, type); public void itemChanged(ItemEvent event) {} public void itemMoved(ItemEvent event) {} public void itemRemoved(ItemEvent event) {} 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. Whether you are writing an event handler in C# 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.
↧