Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Documentation

InfoBus 1.2 Specification

 

InfoBus 1.2 Specification

The widespread adoption of the Java programming language by the Internet community creates an opportunity for developers to create a new class of interactive applications. The language and environment specifications provide mechanisms for the creation and management of small reusable feature sets known as Java Beans, whose functions generally represent only a portion of a Web application. However, the specifications do not suggest methods by which these beans should dynamically exchange data.

This work offers a solution to the problem of interconnecting beans by defining a small number of interfaces between cooperating beans, and specifying the protocol for use of those interfaces. In this specification, the fundamental building block for data exchange is the "data item." The specification details how various forms of information are represented as data items, the lifetime management of these items, and the protocols for querying and extracting information from these items.

The protocols described here are based on the notion of an info bus. That is, all components which implement these interfaces can plug into the info bus. As a member of the bus, any component can exchange information with any other component in a structured way. Generally, the bus is asynchronous and is symmetric in the sense that no component may be considered the master of the bus. However, provision is made in the protocol for a controlling component that can act as the bus master or arbitrator of bus conversations.

InfoBus 1.2 adds a new access interface, ReshapeableArrayAccess, that permits consumers to change dimensions of, or insert or delete rows or columns of an array data item.

InfoBus 1.2 Specification Mark Colan, Lotus Development Corp

February 10, 1999

© 1999 Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, California 94303, U.S.A. All rights reserved.

RESTRICTED RIGHTS LEGEND: Use, duplication, or disclosure by the United States Government is subject to the restrictions set forth in DFARS 252.227-7013(c)(1)(ii) and FAR 52.227-19. The release described in this document may be protected by one or more U.S. patents, foreign patents, or pending applications. Sun Microsystems, Inc.

(SUN) hereby grants to you a fully-paid, nonexclusive, nontransferable, perpetual, worldwide limited license (without the right to sublicense) under SUN's intellectual property rights that are essential to practice this specification. This license allows and is limited to the creation and distribution of clean room implementations of this specification that:

  • (i) include a complete implementation of the current version of this specification without subsetting or supersetting;
  • (ii) implement all the interfaces and functionality of the standard java.* packages as defined by SUN, without subsetting or supersetting;
  • (iii) do not add any additional packages, classes or methods to the java.* packages;
  • (iv) pass all test suites relating to the most recent published version of this specification that are available from SUN six (6) months prior to any beta release of the clean room implementation or upgrade thereto;
  • (v) do not derive from SUN source code or binary materials; and
  • (vi) do not include any SUN binary materials without an appropriate and separate license from SUN.
Sun, Sun Microsystems, Sun Microsystems Computer Corporation, the Sun logo, the Sun Microsystems Computer Corporation logo, Java, JavaSoft, JavaScript, and HotJava are trademarks or registered trademarks of Sun Microsystems, Inc. UNIX® is a registered trademark in the United States and other countries, exclusively licensed through X/Open Company, Ltd. All other product names mentioned herein are the trademarks of their respective owners.

THIS PUBLICATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THIS PUBLICATION COULD INCLUDE TECHNICAL INACCURACIES OR TYPOGRAPHICAL ERRORS. CHANGES ARE PERIODICALLY ADDED TO THE INFORMATION HEREIN; THESE CHANGES WILL BE INCORPORATED IN NEW EDITIONS OF THE PUBLICATION. SUN MICROSYSTEMS, INC. MAY MAKE IMPROVEMENTS AND/OR CHANGES IN THE PRODUCT(S) AND/OR THE PROGRAM(S) DESCRIBED IN THIS PUBLICATION AT ANY TIME.

Preface

Acknowledgments

This technology is the result of the work of several people in a few companies.

The InfoBus concept was created at Lotus by Douglass Wilson, who wrote the first draft of the spec and provided technical guidance.

The InfoBus development effort at Lotus is lead by Mark Colan, who is lead architect, and principal author and editor of the spec. Wendy Clarke was logistical lead. Chris Karle designed and wrote the specification for data controllers, policy helpers, and our security policies, gave lots of feedback on all aspects of the spec, and did most of the programming of the core classes and interfaces. Margaret O'Connell took over core and JCK development starting with InfoBus 1.2. Tom McGary designed and wrote the specification for the database access interfaces.

Andrew Peace assured the quality of the releases and write most of the test procedures. Jessica Goble wrote the JCK test suite for InfoBus 1.1 and helped qualify releases; she now serves as product management for InfoBus. Gwen Walton added new JCK tests for InfoBus 1.1.1 features. Lance McVay wrote an early draft of the spec and, along with Claudia Salzberg, gave editorial feedback. Dave Millen provided managerial support and plenty of free rein.

We would like to thank several key contributors at JavaSoft. Larry Cable proposed a better rendezvous mechanism which is part of the current design, suggested a naming convention, participated in partner reviews, and taught us all a lot about Java and beans. Krystyna Polomski helped us with the JCK test suite. Onno Kluyt is the logistical liason. Graham Hamilton, Gina Centoni, and Jeff Jackson have also provided valuable support.

Special thanks to Yuri Sharonin, Michael Riccio, Donald King, Mark Clark, and Larry Harris at (or formerly at) Oracle Corp. for extensive and detailed comments that led to several improvements, especially in the database access interfaces.

Finally, we thank the many people out there in the industry who read the spec and took the time to write detailed comments and suggestions. These ranged from reporting typos to suggesting an idea which led to a redesign of data items; all of these are helpful and welcome.

Target environments for this release

This specification describes a point release of InfoBus, called version 1.1.1. It adds to the first release (called InfoBus 1.1) a new interface for better Beans support, and four support classes. This release requires JDK 1.1.x or Java 2 Platform (see details in release notes); it does not use any Java 2 Platform features.

InfoBus release 2.0 is in development, and will require Java 2 Platform. Because it will make use of Java 2 Platform security features, it will not work with JDK 1.1.x.

InfoBus and its applications should work with any JVM that supports 100% Pure Java classes and has the correct version of the JDK environment. See the release notes for information on platforms we have specifically tested.

Questions and Feedback

Please send comments to infobus-comments@java.sun.com. To stay in touch with the InfoBus project, visit our web site at: http://java.sun.com/beans/infobus. Most comments and questions get a prompt answer when mailed to the infobus-comments address; harder questions take longer.

1. Overview

This specification is intended to provide standards by which a wide range of Java components acting as data producers and consumers can communicate data. It does this by defining a set of Java interfaces called the InfoBus interfaces. Java components that implement these interfaces are referred to as InfoBus components.

The InfoBus architecture facilitates the creation of applications built from Java Beans that exchange data asynchronously. This can be done by way of applets in an HTML page, or by way of beans assembled by a builder environment. InfoBus can also be used by arbitrary Java classes, including applets, servlets, and so on.

This first release of the InfoBus is called version 1.1 because it supports JDK 1.1. It can also be used with Java 2 Platform, though it does not use any Java 2 Platform features. The InfoBus 2.0 toolkit will exploit Java 2 Platform security and other features, and thus will not support JDK 1.1. Appendix A gives a preview of InfoBus 2.0 features.

1.1 Design of data flow between components

The InfoBus is designed for components working together in the same Java Virtual Machine* (JVM). The current design does not directly deal with components working in separate JVMs, such as on different processors, though it does provide facilities that allow a bridge between JVMs to be built.

In general, all Java Beans loaded from a single class loader can "see" other beans from the same loader and make direct method calls on those beans. However, these cross-bean calls are currently based on well-known interfaces or base classes. Beans use "introspection" to "learn" or "discover" information about peer beans at run time. In such a case, one bean can infer an API supported by another by detecting certain "design patterns" in the names of methods discovered through introspection. By contrast, the InfoBus interfaces form a tightly typed contract between cooperating beans. No inferring is required, and procedure calls are direct.

The InfoBus interfaces allow the application designer to create data flows between cooperating beans. In contrast to an event/response model, where the semantics of an interaction depend upon understanding a bean-specific event and then responding to that event with bean-specific callbacks to the event raiser, the InfoBus interfaces have very few events and have an invariant set of method calls for all components. The semantics of the data flow are based on interpreting the contents of data that flows across the InfoBus interfaces, not in the names or parameters from events, nor in the names or parameters of callbacks.

1.2 Component types

Beans in an InfoBus application can be classified in three types: data producers, data consumers, and data controllers. An individual component can act as both data producer and data consumer. Between components, data flows in named objects known as "data items." Data controllers are specialized components that mediate the rendezvous between producers and consumers.

1.3 Principal requirements for the InfoBus

The structure of an InfoBus application leads to two principal requirements for the InfoBus:
  • The InfoBus should support the creation of interactive applications without requiring support of a "builder" application. That is, application designers should be able to assemble these applications using conventional web page editing tools. Further, these applications should run in standard HTML interpreted environments (browsers) without requiring specific extensions or support beyond the basic Java language environment. Note that this does not preclude enhanced capabilities in the presence of a JavaBeans-enabled environment.
  • The InfoBus must support semantics that allow data to be communicated in a canonical format for consumption by multiple consumers. A canonical format involves both the encoding of data (numbers, strings, etc.) and navigation of data structure (rows, columns, tuples, etc.). Our intent is that mechanisms used to format and recover data be based as closely as possible on mechanisms already available from Java itself and JavaBeans.
InfoBus interfaces and properties should make sense in the context of JavaBeans-based builders. Builders should recognize the InfoBus properties, and be able to create useful wirings between components.

1.4 The InfoBus protocol for data exchange

Step 1. Membership - establishing InfoBus participation

Any Java component can connect to the InfoBus. This is done by implementing an InfoBusMember, obtaining an InfoBus instance, and having the member join it.

Step 2. Listening for InfoBus events

Once an object is a member of an InfoBus, it receives bus notifications by implementing an interface and registering it with the InfoBus. Two event listener interfaces are defined to support two basic types of InfoBus applications. A data consumer receives announcements about data availability by adding a consumer listener to the bus. Similarly, a data producer receives requests for data by adding a producer listener to the bus.

Step 3. Rendezvous on the data to be exchanged

In the InfoBus model, data producers announce the availability of new data as the data becomes ready (e.g., completion of a URL read, completion of a calculation, etc.). Consumers solicit data from producers as they require that data (applet initialization, button event, etc.). The rendezvous is by the name of the data. It is up to the application designer to designate the names for data items that can be exchanged.

Thus, all data producers and consumers must provide some mechanism for the application designer to specify data item names for rendezvous. For example, in a spreadsheet component, the user can "name" ranges on the sheet. This name is a natural mechanism for naming data that can be exported by the sheet in a role as a data producer. Similarly, a chart component needs a means of telling it what named data should be displayed in the chart.

Step 4. Navigation of structured data

Different data producers often use markedly different internal representations of data that is superficially similar. For example, a spreadsheet and a database both deal with tables, but store them quite differently. In a spreadsheet, the table of data might be represented as the output of a calculation (like matrix transpose), or as an array of formulas, whereas in a database the same information might be represented as the result of a join query.

A data consumer should not need a detailed understanding of the data producer's internal data structures to use its data. A charting component should be able to draw a chart of a table from either a spreadsheet or a database whenever the table makes sense as a chart. In practice, this sort of information sharing requires consumer and producer to agree on a common encoding of the data. We have designed a set of interfaces for various standard protocols which are used to create data items with common access.

Step 5. Retrieval of an encoding of the data value

A data item can be retrieved as a String or a Java object. Java objects are typically object wrappers for primitive types such as Double or instances of other core classes such as Collection. The intent is to require as little specialized understanding of data formats on the part of the data consumer as possible.

Step 6. Optional: changing data

A consumer can attempt to change the value of data items. The producer enforces a policy on whether anyone can change data. With Java 2 Platform, it can also check for permissions from various consumers.

1.5 The nature of data items

To have useful communication between disparate data producers and consumers, some understanding of the content of the data streams is necessary. We need to establish what kinds of data are suitable for transport over the InfoBus. While the InfoBus protocols do not prohibit the exchange of very detailed data streams between components with understanding of each other's semantics (e.g., a spreadsheet file import filter passing internal representations of the spreadsheet data to a sheet display component), it does not particularly facilitate this.

When deciding if data is suitable for transport, it is useful to ask if there is (1) more than one data consumer interested in this item, and (2) more than one data producer likely to produce an item of equivalent encoding. Essentially, does this item and its structure represent a class of data to be transferred? If yes is the answer, InfoBus is the framework.

2. Membership

2.1 InfoBus instances

The InfoBus class is at the heart of this technology. An instance of the InfoBus class is the meeting place where members can join.

A named InfoBus instance is one created on the basis of a name, which can be specified via a builder environment, externally-specified property, user input, or an application string. Any class that implements the InfoBusMember or InfoBusBean interfaces can join the InfoBus by name, even if they are not Applets.

A default InfoBus instance is one which is created on the basis of a generic name calculated from a DOCBASE in a Component context. To find a default InfoBus the component must either be an Applet itself, or have an applet in its AWT containment hierarchy, so that we can traverse upwards to the applet in order to get the DOCBASE. The caller itself need not be such a component, but it must be able to supply a Component context.

The advantage of using a default InfoBus is convenience. An Applet that joins its default InfoBus will be able to see other components on the same frame of the same web page that have joined their default InfoBus: because they have a common DOCBASE, they share a common default InfoBus. This is convenient for Applets and other Java components that can supply a Component context.

Classes that do not have access to a Component context with a DOCBASE (e. g., classes that are not part of an Applet) cannot join the Applet default InfoBus, but they can join an InfoBus by name.

The InfoBus class makes use of static methods and static data to manage and access a list of active InfoBus instances. In particular, a static method called InfoBus.get() is called by a prospective member to obtain an InfoBus instance for all future transactions. The get() method locates or creates an instance of the InfoBus class on the basis of the name or Component context, and manages it by way of a list of active instances.

Figure 2-1 shows the static parts of the InfoBus in a separate box, as if it were a different object, along with two InfoBus instances which members can join. It also shows a producer and a consumer which are about to join:

Figure 2-1 - InfoBus instance management before members have joined

2.2 Details of the membership process

A component must implement InfoBusMember to join the InfoBus, in preparation for receiving events about data on the bus. Membership is typically established during class initialization (for example, in applets, in the init() method). A component can join more than one InfoBus, but it must use a separate InfoBusMember object for each InfoBus it wishes to join. A component that has created separate members to join more than one bus can use the same event listener object (described in the next chapter) to listen for events from more than one bus.

InfoBusMember can be implemented easily by creating an instance of InfoBusMemberSupport and delegating each method to the support class. The support class provides a method to join the bus called joinInfoBus(). This method starts a complicated sequence of calls between InfoBus and InfoBusMember to establish membership:

  • joinInfoBus() calls get() to obtain an InfoBus instance to join, then calls InfoBus.join() to join it.
  • join() checks the InfoBus instance to see if it is stale. If it is, StaleInfoBusException is thrown. The InfoBus instance can become stale if join() is erroneously called after the InfoBus instance has been released.
  • join() calls the member's setInfoBus() method to set the member's InfoBus property to the InfoBus instance returned by get(). This can result in throwing PropertyVetoException, in which case the member was not allowed to join the bus returned by get().
  • When exceptions are not thrown, the member's setInfoBus() implementation calls InfoBus.register(). register() adds the member to the list of members currently registered on this InfoBus instance, and calls the member's addInfoBusPropertyListener() to detect departures from the bus.
  • When InfoBus.join() returns, joinInfoBus() finishes by calling release(), regardless of whether an exception was thrown.
Following a successful return from joinInfoBus(), a member can call methods on the InfoBus it joined by obtaining a reference to the bus by way of its own getInfoBus() method. This allows the member to add one or more event listeners and to fire events to other members, and is discussed in the next chapter.

Figure 2-2 shows the relationships between the InfoBus instance used for communications between a data producer applet and a data consumer applet after they have both joined the InfoBus:

Figure 2-2 - A producer and consumer as InfoBus members

When the member calls the InfoBus.leave() method on the instance it previously joined, the InfoBus removes the member from its list, removes its property change listener from the member, and sets the member's InfoBus property to null. The close() method is called to check whether the InfoBus instance has lost its last members or event listener, and if so, releases the instance from the list of active InfoBus instances so that it can be garbage collected.

For maximum flexibility, an InfoBus component should support these three means of joining an InfoBus:

  • If an InfoBus name is specified by way of a property or parameter, this name should be used when calling InfoBusMemberSupport.joinInfoBus().
  • In the absence of a name to be used for an InfoBus, it should join the default InfoBus, if possible, supplying a Component context that can be used to locate a DOCBASE parameter.
  • All beans should be prepared to accept a change to the "InfoBus" property on their InfoBusMember implementation, to support their use in a bean builder environment.

2.3 Revoking data items on changes to membership

When an InfoBus component is changing membership, leaving a bus with or without joining another, some questions arise about how it should deal with data items it produces or consumes. This specificaion allows such decisions to be made by the implementor, and we suggest that the behavior that is chosen be documented. In this section we mention some of the questions that arrive and some possibilities for answering them.

When a producer is leaving a particular InfoBus, it may choose to fire an InfoBusItemRevokedEvent on the InfoBus it is leaving, and a DataItemRevokedEvent on any outstanding data items still held by consumers, to indicate that it can no longer service methods called on such items. However, it may choose to continue servicing methods called on outstanding data items, even though it may not be on the same InfoBus as the consumers that are using the items, in which case it must not fire the revoked events. Upon joining the new bus, it may wish to reannounce its available items on the new bus.

Similarly, when a consumer is leaving a bus, it can choose to continue using a data item it has already obtained, or it can release it. It may wish to reannounce its requests for data items on the new bus.

2.4 InfoBus naming conventions

InfoBus instances can be named with any number of `unreserved' characters as defined by the URI specification, except that they may not begin with `%' or `-'. Names beginning with `%' are reserved and must not be used. Illegal names may cause an exception to be thrown. See "Data item naming conventions" in the next chapter for more details.

2.5 Security considerations for membership

The InfoBus defines security policies in the InfoBusPolicyHelper interface, described in detail in Chapter 8. Some of these policies control whether a member is allowed to get, join, or register with an InfoBus instance. The DefaultPolicy class in InfoBus 1.1 implements this interface without any security checks, because this release supports JDK 1.1, which does not offer security support. InfoBus 2.0 will include a new implementation that provides security checks by using Java 2 Platform security features.

2.6 Monitoring member departures

A member ordinarily joins and leaves an InfoBus by way of the methods in InfoBusMember designed for these functions. In a browser environment, once a member joins a particular InfoBus, it generally remains as a member of the same bus until it terminates.

Many InfoBus participants will also be beans. When a bean is used in a builder environment, the builder may specify the InfoBus instance it wants the bean to join. Since beans will often join a default InfoBus as they initialize themselves, the builder may can reset the bean's "InfoBus" property to put it onto a different bus.

An InfoBus instance that is losing a member needs to release itself after losing the last member. Since changing the InfoBus property on a member happens outside of calling the leave() method, the InfoBus implements PropertyChangeListener and adds itself as a listener for each of its members.

The InfoBus event listeners (InfoBusDataConsumer and InfoBusDataProducer) also need to know when their associated InfoBusMember is changing membership, so they also set property change listeners. When they are notified, they must remove themselves from the listener list for the old InfoBus instance, and (if the new property value is not null) add themselves to the new instance.

Figure 2-3 shows how the InfoBus and event listener objects use PropertyChangeListener objects as member data, and PropertyChangeSupport objects in the InfoBusMember implementation we provide via InfoBusMemberSupport:

A member object that has properties of its own, other than the "InfoBus" property, should override setInfoBus() to notify their own property change listeners, then call InfoBusMemberSupport.setInfoBus().

_

Figure 2-3 - InfoBus mechanism for monitoring member departures

2.7 About "Stale" InfoBus Instances

InfoBus instances are managed internally by keeping the active instances on a list. Whenever a particular InfoBus instance is losing a member, listener, or controller, the InfoBus checks to see whether there are any remaining members, listeners, or controllers, and if there are not, the InfoBus instance is removed from the active list. This allows the InfoBus instance to be reclaimed during garbage collection.

If an application has a reference to an InfoBus instance that has been removed from the active list, we refer to this instance as "stale." Anyone who asks for an InfoBus instance that has the same name as this stale instance will get a different instance returned to them, because only the active instances can be found.

Methods called on a stale InfoBus instance (such as join(), addDataProducer(), addDataConsumer(), addDataController(), or InfoBusMember.setInfoBus()) will cause StaleInfoBusException to be thrown. When this RuntimeException is thrown, it generally indicates an error in the caller's code. For example, consider this code snippet:

myMember.joinInfoBus("myBus");
InfoBus myBus = myMember.getInfoBus();
myMember.leaveInfoBus();
myBus.addDataConsumer( myConsumer );

Suppose also that the bus named "myBus" is not otherwise being used. Adding a consumer would throw an exception when called, because the bus was released when leaveInfoBus() was called, so myBus refers to a stale InfoBus instance.

A Bean container might have a similar bug by holding a reference to a stale bus, as in the following example:

InfoBus beanBus = InfoBus.get("beanBus");
bean1.setInfoBus(beanBus); // this works ok
beanBus.release();
// suppose bean1 leaves the bus on its own, then the following happens:
bean2.setInfoBus(beanBus); // this throws StaleInfoBusException

In this case, "beanBus" becomes stale when bean1 leaves the bus, and beanBus refers to the stale bus. Setting the InfoBus on bean2 to beanBus throws StaleInfoBusException because beanBus is stale.

The get() method adds an artificial reference to the bus it gets to ensure that the bus does not go stale before join() is called. In a multi-threading environment, it is possible that after successfully getting an InfoBus instance, another thread could cause leave the same bus, in which case the InfoBus would be cleaned up if nobody else was using it. The artificial reference is added to prevent it from being cleaned up before allowing the original thread to join() it.

The artificial reference must be released by calling the release() method immediately after joining the bus. Although the artificial reference is removed, the bus is not cleaned up (and does not become stale) when it still has at least one current member, producer listener, consumer listener, or data controller in place. If the release() method is not called, the InfoBus instance will not be reclaimed, even when it has no other members, listeners, or controllers associated with it.

In the Bean container example above, suppose a different thread is scheduled immediately after calling InfoBus.get("beanBus"), and this thread joins then leaves the same bus. Without the artificial reference, when the first thread tries to set bean1's InfoBus, it would throw an exception. The artificial reference guarantees that beanBus does not become stale until it is released.

In summary, most InfoBus applications will never see a stale InfoBus if they use InfoBusMemberSupport methods joinInfoBus() and leaveInfoBus() for joining and leaving the bus, and instead of keeping a reference to the bus they joined, they call methods on their bus by getting the current property value, as in this example:

myMember.getInfoBus().addProducer( myProducer );

2.8 The InfoBus class

The InfoBus class must be used as we provide it (without subclassing) in the Java virtual machine. Because subclassing is not possible, the behaviors that a JVM may wish to override are collected into an interface called InfoBusPolicyHelper; we provide a default implementation of this interface in a class called DefaultPolicy.

The InfoBus plays a central role in membership, and the methods involved are described in this section. Most of the methods for membership are called by the InfoBusMemberSupport or InfoBusBeanSupport methods, not directly by applications.

The InfoBus class is also central to the rendezvous and event model used in the InfoBus, and these InfoBus methods are described in the next chapter.

All methods described in this section except for getName() are used by the InfoBusMemberSupport implementation class, and ordinarily are not called directly by most applications. Most InfoBus applications will join and leave the bus by way of InfoBusMemberSupport methods joinInfoBus() and leaveInfoBus(). A bean container may need to use the get() method when it wishes to force a contained bean to be a member of a particular InfoBus, in which case it must call release() after setting the InfoBus property on the contained beans.

public static synchronized InfoBus get(Component component)

public static synchronized InfoBus get(String busName)

These methods are used to get a reference to an InfoBus instance that can be used directly for joining, as for an ordinary InfoBus bean or applet, or indirectly to force membership on InfoBus-aware beans, as for a builder environment.

The first version specifies the component to be used for determining the name of the default InfoBus to be used for the Component's context. component, or one of its parent Components in the AWT containment hierarchy, must specify a DOCBASE, which is used to create a name according to rules established in InfoBusPolicyHelper. If no DOCBASE is found, this method returns null.

The second version uses a busName string as the name of the desired InfoBus to be specified. Named infobuses are useful for builder environments, classes that are not Components, and applications that wish to specify security constraints on particular buses by name. See section 2.3, "Infobus naming restrictions," for rules on naming InfoBuses. IllegalArgumentException may be thrown on an illegal name.

Whether the name is constructed by default or specified explicitly, both versions do their work by searching the existing InfoBus instances for a match and creating one if necessary. The reference to the found or created InfoBus is returned.

The get() method introduces an artificial reference to the InfoBus to ensure that the InfoBus instance is kept alive until a member joins it. This reference must be released by calling release() on the instance after completing the work (calls to join(), setInfoBus(), etc). Every get() must have a matching release(), regardless of whether the work in between succeeds or throws an exception. See also the example in the join() method for this class.

public synchronized void join(InfoBusMember member) throws PropertyVetoException

This method causes an InfoBusMember to join the specified InfoBus instance. It is generally used on an InfoBus instance returned by get(). For example:

InfoBus x = get("my InfoBus"); // get named InfoBus
x.join( myMember ); // join it
x.release(); // release artificial reference

When this method is called on a stale InfoBus instance, the StaleInfoBusException is thrown.

We recommend that applications use the higher-level InfoBusMemberSupport.joinInfoBus() methods instead of calling InfoBus.join().

public synchronized void release()

This method is used for removing the artificial reference to an InfoBus instance set by calling get(). It should be called immediately after at least one member joins the bus obtained by get(). See also the example in the join() method.

When this method is called, it checks to see whether the InfoBus instance is no longer used, and allows it to be reclaimed thru garbage collection if the instance has no members, listeners, or artificial references. In the common case where it follows a join() call, the InfoBus instance is not garbage collected, because it has at least one member, i.e. the one that just joined.

InfoBus participants that use InfoBusMemberSupport.joinInfoBus() will typically not need to use this method. However, a Bean builder environment would use get() and release() as brackets around calls that set the InfoBus property on their contained Beans.

public String getName()

Returns a String with the name of the InfoBus. When the InfoBus was created by name, the name is returned. When the InfoBus took the default name for the DOCBASE, the name derived from DOCBASE is returned.

public void register( InfoBusMember member )

This method registers an InfoBusMember on the InfoBus's list of active members, and also causes the InfoBus to register itself as a PropertyChangeListener on the InfoBusMember's InfoBus property. It is called by InfoBusMemberSupport.setInfoBus(), and is not typically called directly by an InfoBus participant. When this method is called on a stale InfoBus instance, the StaleInfoBusException is thrown.

member is a reference to the InfoBusMember to add to the active member list.

public synchronized void leave(InfoBusMember member) throws PropertyVetoException

This method is called by implementations of InfoBusMember.leave when a member wishes to remove itself from the InfoBus it previously joined. We recommend that InfoBus applications use the InfoBusMemberSupport methods joinInfoBus() and leaveInfoBus() instead of InfoBus methods join() and leave().

public void propertyChange(PropertyChangeEvent event)

This method is called whenever an existing member has its "InfoBus" property changed by some means other than calling the leave() method, for example when a builder calls InfoBusMember.setInfoBus() to force it to talk to a different bus of its choice.

InfoBus applications do not call this method directly. It is called by methods in InfoBusMemberSupport when a member is leaving a given InfoBus instance.

2.9 The InfoBusMember interface

This interface must be implemented by classes that want to join an InfoBus. Using an interface to define the methods ensures that any container of an InfoBusMember can know how to cause a member to join an InfoBus.

The interface defines methods for joining and leaving an InfoBus, managing the "InfoBus" property via methods that conform to the Beans specification for a bound property of this name, and adding listeners for property changes on "InfoBus," as well as vetoable listeners.

To facilitate the implementation of this interface, we supply a class called javax.infobus.InfoBusMemberSupport which provides all required methods and member data and which can be used directly by using the class as a data member. We strongly recommend that this class be used for the implementation, rather than rolling your own.

public void setInfoBus(InfoBus newInfoBus) throws PropertyVetoException

This method is called by InfoBus.join() to set the member data reference to newInfoBus during the processing of InfoBusMember.join(). It can also be called by others that wish to force membership to a given InfoBus, such as by a builder tool that is arranging to have to applets talk over the same bus. Finally, it can be called with a null argument, such as in handling a call to InfoBusMember.leave().

The InfoBus requires that an implementation of this method does the following:

  • Broadcasts a PropertyChangeEvent to its VetoableChangeListeners and PropertyChangeListeners.
  • Explicitly uses the PropertyName "InfoBus" in creating the event.
  • Does not use null for the PropertyName field (which is allowed in the Java Beans spec, for example if multiple properties change).
  • Sets the InfoBusMember as the Event source.
public InfoBus getInfoBus()

This method is an accessor for the current setting of the InfoBus property.

public void addInfoBusVetoableListener(VetoableChangeListener vcl)

public void removeInfoBusVetoableListener(VetoableChangeListener vcl)

These methods are called by a class that wishes to have a say about whether the "InfoBus" property for this class can be changed, to add or remove a vetoable listener object for the "InfoBus" property.

Listeners should allow the InfoBus property to be set to null, which indicates a class that is leaving the InfoBus, often because the class is shutting down.

public void addInfoBusPropertyListener(PropertyChangeListener pcl)

public void removeInfoBusPropertyListener(PropertyChangeListener pcl)

These methods are called by a class that wishes to be notified when the "InfoBus" property for this class is about to change. The methods add or remove a listener object to enable notification.

2.10 The InfoBusBean interface

This interface extends InfoBusMember to add support for a new property called infoBusName property. This property allows InfoBus membership to be configured in an Bean builder environment, even when the builder is not InfoBus-aware, by setting it to a String using the property editor.

Changing this property to a new name has the side effect of attempting to leave any bus the member has previously joined, and then joining a bus with the new name. Setting the infoBusName property to an empty string causes the member to leave the current bus and not attempt to join another. When the new name is set to "-default", we recommend that components that are also Applets respond by joining the Applet default InfoBus.

Changing the infoBusName also causes the InfoBus property to change, so that it refers to one that has the indicated name. The latter property may have listeners to monitor or prevent changes, in which case the listeners effectively monitor or prevent changes to both properties. In the event that a listener on the InfoBus property vetoes the change, the infoBusName property must not change, and an exception is thrown.

Most of the time, the infoBusName and InfoBus properties track each other's values, so that when one is null, the other is also null, and when one is non-null, the other is also non-null. This occurs because setting the name affects membership, and methods affecting membership usually change the name. However, there are times when the infoBusName property can be non-null even though the InfoBus property is null. One case is when the two-argument constructor in InfoBusBeanSupport is called to specify an initial value for the property; this constructor does not cause membership to occur. The other is when the infoBusName property has been set in a builder, then the Bean is serialized. When such a Bean is deserialized, infoBusName has a non-null value, yet the Bean is not a member of the named bus, so infoBusName is null.

Just as the InfoBusMember interface is accompanied by a full implementation class, allowing the programmer to delegate all method calls to the corresponding methods in InfoBusMemberSupport, the InfoBusBean interface is accompanied by an implementation class, allowing the programmer to delegate method calls to the corresponding methods in InfoBusBeanSupport.

Synchronization requirements

The implementations of setInfoBus(), getInfoBus(), setInfoBusName(), and getInfoBusName() must be synchronized on the same Object to prevent more than one of these from running at a time in different threads.

Persistence requirements

The InfoBus property should be treated as transient. When the InfoBusBean implementation is Serializable, which we recommend, the variable that manages the setting of the InfoBus property should be declared as transient. In addition, the BeanInfo for the Bean should return a PropertyDescriptor for the InfoBus property, with these two attributes:

  • PropertyDescriptor.setHidden(true) should be called to mark this property as hidden.
  • PropertyDescriptor.setValue("transient", "true") should be called to mark this property as transient.
The infoBusName property should have persistent state, and should be configurable in a property sheet. Therefore it should not be hidden, and the transient value can be omitted or explicitly set to false.

public void setInfoBusName(Strings newBusName) throws InfoBusMembershipException

This method must set the infoBusName property to newBusName, and as a side effect, cause a change of membership to the indicated bus.

newBusName specifies the name of a new bus to join, as defined in Section 2.3, InfoBus naming conventions. newBusName can be an empty String or null reference (the two must be treated the same) to indicate that the old membership should be terminated and no new bus should be joined. newBusName can also be "-default", in which case this method should attempt to join the Applet default InfoBus, if possible, and leave the infoBusName property set to "-default". The infoBusName property must be maintained as persistent member data.

Unless a vetoable listener prevents a change to membership, setting this property must terminate any prior membership and, if newBusName is not an empty string or null reference, join a bus obtained with newBusName (or the Applet default InfoBus if it is "-default"). If newBusName is null or an empty String, setInfoBus(null) must be called to terminate the current membership without joining a new bus.

When a vetoable listener on the InfoBus property throws PropertyVetoException to prevent a change in membership, setInfoBusName() must throw InfoBusMembershipException, and the value of the infoBusName property and the existing bus membership must remain unchanged.

public String getInfoBusName()

This method returns a String holding the current value of the infoBusName property, or null if there is no name.

In most cases, the String returned from this method is the same as the one returned by calling getInfoBus().getName(). However, if the infoBusName property has been set to "-default", getInfoBusName() returns "-default", whereas getInfoBus().getName() returns the actual, resolved name of the Applet default bus. If the member has not joined a bus, or has left a bus, getInfoBusName() returns null and getInfoBus().getName() is invalid (because getInfoBus() is null).

public void setInfoBus(InfoBus newInfoBus) throws PropertyVetoException

This method must change the InfoBus property, as detailed in InfoBusMember.setInfoBus(), but it must also change the value of the infoBusName property, unless a vetoable listener prevents a change. The latter property is set to the name of the newInfoBus, or to an empty String if newInfoBus is null.

2.11 The InfoBusMemberSupport class

This class provides code that can be used for implementing the InfoBusMember interface. Classes that implement the InfoBusMember interface can create an instance of this implementation class as member data, and expose the InfoBusMemberSupport methods to the outer class by creating a wrapper method. Some methods in this class (joinInfoBus(), leaveInfoBus()) are not required by the interface, but are handy for users of the class. In the following example, we show the wrapper for setInfoBus(). Other wrappers are similar to this approach:

class myMember implements InfoBusMember
{
private InfoBusMemberSupport m_memberSupport = new InfoBusMemberSupport();
public void setInfoBus(String name) throws PropertyVetoException
{
m_memberSupport.setInfoBus(name);
}

// other wrapper methods go here
}

Constructor

public InfoBusMemberSupport(InfoBusMember member)

The constructor sets the InfoBus reference member to null, and creates an instance of each of the VetoableChangeSupport and PropertyChangeSupport objects.

The member parameter is a reference to the InfoBusMember instance that contains this InfoBusMemberSupport, and is used for property change notifications on the "InfoBus" property.

Membership methods

public synchronized void joinInfoBus(String busName)
throws InfoBusMembershipException, PropertyVetoException

public synchronized void joinInfoBus(java.awt.Component component)
throws InfoBusMembershipException, PropertyVetoException

These methods are the preferred way for an InfoBus component to join an InfoBus. Following a successful return from these methods, the InfoBus property has a reference to the bus it joined, which can be used for adding event listeners, firing events, or making other calls on the InfoBus instance it has joined.

If either method is called when the member has previously joined a bus but has not left it, InfoBusMembershipException is thrown, and the membership is unchanged.

The first method allows a component to join an InfoBus by a busName it specifies as an argument. Legal names are described in section 2.3, InfoBus naming restrictions. This method can be used by any member to join a bus, whether the application is an Applet or not.

The second method is convenient for classes that either have a Component with a DOCBASE or have a parent in the AWT hierarchy that has a DOCBASE. An Applet is a common example of such Components. It allows these classes to join the "Applet default InfoBus," which is simply a bus whose name is calculated from the DOCBASE. Applets with the same DOCBASE can expect to see each other's events after joining with this method.

public synchronized void leaveInfoBus()
throws InfoBusMembershipException, PropertyVetoException

This method is called by an application after removing its event listeners when it is finished with a given InfoBus. It must be called before the application shuts down or before joining a different bus.

Methods to manage the "InfoBus" property

public synchronized void setInfoBus(InfoBus newInfoBus) throws PropertyVetoException

This method is called to set the InfoBus property for a given member. Setting this property results in changes to membership: any prior membership is terminated, and if newInfoBus is not null, the member joins that bus. Any vetoable or property change listeners are notified about the change according to the standard rules.

This method is typically called by a container application, such as a Bean builder environment, to cause InfoBus members it contains to be members of a particular bus.

PropertyVetoException is thrown when a VetoablePropertyListener on the member refuses to allow the change in membership. StaleInfoBusException is thrown when newInfoBus refers to InfoBus instance which is stale.

The implementation in this class does the following:

public InfoBus getInfoBus()

The implementation in this class returns the current value of the "InfoBus" property.

public void addInfoBusVetoableListener(VetoableChangeListener vcl)

public void removeInfoBusVetoableListener(VetoableChangeListener vcl)

These methods call addVetoableChangeListener() or removeVetoableChangeListener() on a VetoableChangeSupport object in member data.

public void addInfoBusPropertyListener(PropertyChangeListener pcl)

public void removeInfoBusPropertyListener(PropertyChangeListener pcl)

These methods call addPropertyChangeListener or removePropertyChangeListener on a PropertyChangeSupport object in member data.

2.12 The InfoBusBeanSupport class

public class InfoBusBeanSupport extends InfoBusMemberSupport
implements InfoBusBean, java.io.Serializable

This class provides code that is recommended for implementing the InfoBusBean interface. It implements InfoBusBean in compliance with all requirements specified for that interface.

InfoBusBeanSupport implements the Serializable interface to provide persistance for the setting of the infoBusName property. The InfoBus property in this class is declared as transient. Users of this class should also provide PropertyDescriptors for these two properties according to the requirements described for the InfoBusBean interface.

The get and set methods for the InfoBus and infoBusName properties are synchronized to prevent more than one of these methods from running at the same time, as is required for the InfoBusBean interface.

Classes that implement the InfoBusBean interface can create an instance of this implementation class as member data, and expose the InfoBusBeanSupport methods to the outer class by creating a wrapper method.

public InfoBusBeanSupport(InfoBusMember parent)

The constructor initializes an InfoBusBeanSupport object. The parent parameter, usually a reference to an InfoBusBean that created the instance of InfoBusBeanSupport, is used as the source of all PropertyChangeEvents. If member is null, this InfoBusBeanSupport object is specified as the source of PropertyChangeEvents.

If parent is an instanceof Component, calling setInfoBusName("-default") will attempt to use this Component to locate the DOCBASE parameter for resolving the name of the Applet default InfoBus.

public InfoBusBeanSupport(InfoBusBean parent, String initialBusName)

This constructor calls the other constructor, then sets the initial value of the infoBusName property to the value specified by initialBusName. Although the property has an initial value, this constructor does not cause membership to occur. Use of this constructor variant allows a Bean to have a preset default value for the infoBusName property other than null.

public void setInfoBusName(String newBusName) throws InfoBusMembershipException

This method is a complete implementation of InfoBusBean.setInfoBusName() as described in Section 2.9, The InfoBusBean interface.

When newBusName is specified as "-default", this method attempts to use the parent of this instance (see constructor) as a Component for resolving the name of the Applet default InfoBus. For applications that can provide such a Component, but it is different from the constructor's parent parameter, we recommend that the implementation of InfoBusBean.setInfoBusName() first compare newBusName to "-default", and if they match, join the Applet default InfoBus by calling joinInfoBus(Component) instead of delegating to this implementation.

public String getInfoBusName()

This method returns a String holding the current value of the infoBusName property, or an empty String if there is no current name set.

public void setInfoBus(InfoBus newInfoBus ) throws PropertyVetoException

This method overrides that of InfoBusMemberSupport to change to the indicated bus, but it also changes the value of the infoBusName property. The property is set to the name of the newInfoBus, or to null if newInfoBus is a null reference.

Except for the addition of changing the infoBusName property, the method functions identically to InfoBusMemberSupport.setInfoBus().

public void rejoinInfoBus() throws InfoBusMembershipException

This method can be called after deserializing a member to cause the member to join a bus whose name was specified before the member was serialized. It does nothing if called when the member has already joined a bus (InfoBus property is not null), or if the infoBusName property is an empty String.

InfoBusMembershipException is thrown when a VetoableChangeListener refuses to permit the InfoBus property to be changed.

3. Rendezvous

This chapter describes the event-based mechanism used by InfoBus components to announce data availability and request data among other components on the bus. We refer to the negotiation for data as the `rendezvous.'

3.1 Events

Events are sent by the InfoBus to listeners for each component on the bus. Three types of events are defined:
  • InfoBusItemAvailableEvent - an event which is broadcast on behalf of a producer to let potential consumers know about the availability of a new data item through the InfoBus.
  • InfoBusItemRevokedEvent - an event which is broadcast on behalf of a producer to let consumers know that a previously available data item is no longer available.
  • InfoBusItemRequestedEvent - an event which is broadcast on behalf of a consumer to let producers know about the need for a particular data item that they may be able to supply.
The three InfoBus events are subclasses of a common base class, each with methods needed for their particular task.

The InfoBus class provides methods that create and broadcast these event objects on behalf of producers and consumers, including fireItemAvailable() and fireItemRevoked() for use by producers, and findDataItem() and findMultipleDataItems() for use by consumers.

3.2 Event listeners

Once a class has joined an InfoBus, it needs to provide an event listener to the InfoBus in order to receive events from the InfoBus. InfoBus components listen for events to discover the availability or revocation of data items, or to hear requests for data items, or both. InfoBus defines interfaces InfoBusDataProducer and InfoBusDataConsumer which extend InfoBusEventListener to indicate whether a component is a data producer, a data consumer, or both. The API details for InfoBusEventListener, InfoBusDataProducer, and InfoBusDataConsumer are discussed later in this chapter.

Figure 3-1 - A producer and a consumer on the bus, ready to receive events

Data producers

A data producer is an InfoBus participant that implements InfoBusDataProducer to listen for requests and announces data availability or revocation by firing events on the bus.

The producer calls addDataProducer() to begin receiving events. Applets typically do this during the execution of their start() method so they begin to receive events when the page is activated, and call removeDataProducer during the execution of the stop() method. Following this protocol reduces overhead when the page is not active. (However, with some browsers it may be possible to use an instance of the InfoBus for communication between applications on different web pages. The browser must not "prune" Java applets as pages are changed for this to work, and the InfoBus applications must not remove their listener on stop() in order to receive or send InfoBus events.)

Producer events are generated by calling methods fireItemAvailable() and fireItemRevoked() on the InfoBus class, which send these events to registered consumers. The producer handles request events via dataItemRequested(). If the producer can provide the requested data, it stores a reference to the data item by calling setDataItem() on the event, otherwise it simply returns.

Data consumers

A data consumer is an InfoBus participant that implements InfoBusDataConsumer to listen for availability and revocation events, and requests data items by firing events on the bus. Similar to the producer mechanism, it controls events by calling addDataConsumer() and removeDataConsumer().

The consumer finds out about new data by handling dataItemAvailable() and revocation of announced data by handling dataItemRevoked(). It decides whether it is interested in the data by inspecting the data item name or data flavors obtained from the event via getDataItemName() or getDataFlavors(). If it wants the data, it can obtain it directly from the producer by calling requestDataItem() on the event.

The consumer can request a data item by name even if it has not been announced by a producer. For example, findDataItem() can be called to find a data item by name. Such blind requests are often required when a consumer initializes, in case the producer announced the data item before the consumer had completed initialization. If the data is unavailable from any producer, null is returned to the caller.

If more than one registered producer is capable of producing the data, the first one that supplies the data item satisfies the request. A consumer can call findMultipleDataItems() to get data items from all registered producers that are capable of supplying the data, and choose for itself which one it will take.

A component can be both a producer and a consumer by implementing InfoBusDataProducer and InfoBusDataConsumer. An applet might do this to filter data or translate it to a new form for a consumer.

Producers and consumers cannot directly create and broadcast events to others on the bus, because the constructors for events are not public, and because producers and consumers do not have access to the list of other producers and consumers. The InfoBus design intentionally prevents the use of custom events, since event traffic on the bus limits scalability.

Data controllers do have the ability to send events to producers and consumers they choose, to allow them to mediate the flow of events among other components. However, data controllers use a different mechanism for handling events from consumers and producers, and are therefore not event listeners themselves.

Figure 3-1, above, shows a producer and a consumer just after they have provided event listeners to the InfoBus instance they belong to. The InfoBus instance has a list of consumer listeners and a list of producer listeners that is separate from other InfoBus instances, in order to control the scope of conversations between applets and to reduce traffic. Although the membership connections and change listeners are not shown in Figure 3-1 in the interest of keeping the diagram simple, they would still be in place.

Provision for multiple event listeners

Figure 3-1 shows applications that have only one InfoBusEventListener associated with each InfoBusMember. The reason we did not combine these into one interface is that it will often be convenient for an application to have more than one event listener, each specialized to a particular data item of interest. InfoBus components can register as many event listeners as they need.

For example, consider a shared technology that supports the notion of the currently active selection by way of a data item called "CurrentSelection." The provider of this item is likely to be in a different class than, for example, the provider of a collection of cells, and the use of multiple event listeners makes structuring the classes more convenient.

3.3 Security considerations for rendezvous

Security during the rendezvous process can be approached from two granularities: security checks before permitting distribution of an InfoBusEvent constitute a large-grained approach, while security checks upon delivery of an InfoBusEvent to a producer or consumer constitute a fine-grained approach.

The fine-grained security approach occurs in the producers and consumers themselves. An example using Java 2 Platform mechanisms is a producer who creates a data access permission akin to the FilePermission class, with system security policy files that enumerate classes that have that access permission. When this producer receives an InfoBusItemRequested event, it can call the AccessController's checkPermission method to verify that all objects in the call stack - which will include the requesting consumer - have the necessary access permission before releasing data.

Consumers that wish to implement this kind of fine-grained permission checking need to take the additional step of implementing the javax.infobus.SecureConsumer interface (forthcoming; see Appendix A). Without the SecureConsumer interface, the data that a consumer requests is returned by the InfoBus, and the producer providing that data has no presence in the call stack. By implementing SecureConsumer, each producer that returns data is actually calling the SecureConsumer.setDataItem() method, allowing the SecureConsumer to perform an AccessController checkPermission() before accepting and processing the data.

The InfoBusPolicyHelper provides the methods necessary to implement the large-grained approach: for each of the three event types, there is a matching InfoBusPolicyHelper call that is made before distributing the requested event. The implementer of InfoBusPolicyHelper can use security mechanisms such as those in Java 2 Platform to determine if the calling participant is permitted to generate the InfoBusEvent. Chapter 8 describes the InfoBusPolicyHelper in more detail.

The current, 1.1-compatible version of the DefaultController performs none of the large-grained checks in the rendezvous methods. Although the next version of the DefaultController will use 1.2 security checks on other InfoBus activity, it will also not implement the large-grained security checks before distributing events. Performing the rendezvous checks by default produces unwanted overhead in code execution as well as overhead in management of system security policies. The InfoBusPolicyHelper checks done during membership processing, combined with fine-grained checks done by individual consumers and producers during rendezvous, are a sufficient and optimal means of creating a secure InfoBus application. Systems that wish the additional layer of security described here as large-grained rendezvous checks have the means of implementing it by providing a custom policy helper.

An InfoBus participant should create classes that implement InfoBusDataProducer and InfoBusDataConsumer separately from one that defines other methods or implements other interfaces, especially InfoBusMember. We suggest this separation because the listener interfaces are available from events and data items, and introspection allows access to other methods available on these objects. In particular, if InfoBusMember is in the same class, it would allow access to setInfoBus(), which a malicious application could use to force a member onto a different bus.

3.4 Data item naming conventions

Data items can be announced as available by name, and consumers can request data items by name. Data items can be named using the recommended naming conventions described in this section, based on Univeral Resource Identifiers (see http://ds.internic.net/rfc/rfc1738.txt). However, data items are not required to follow these conventions. The only requirements for data item names are:
  • No data item name can begin with the `%' character, which defines a reserved space for data item names.
  • If the data item name starts with "infobus:" or "/", the same as a URI does, it must follow all of the rules for the convention.
URI-based InfoBus naming convention

<infobus_uri> ::= <abs_infobus_uri> | <rel_infobus_uri>

<abs_infobus_uri> ::= <infobus_scheme> `:' <rel_infobus_uri>

<infobus_scheme> ::= `infobus'

<rel_infobus_uri> ::= `/' <infobus_name> { <producer_id }
`/' <infobus_data_item_name>

<infobus_name> ::= <unreserved>*

<producer_id> ::= `/' <producer_class> { `/' <producer_discriminator> }*

<producer_class> ::= fully-qualified Java class name (with dot separators)

<producer_discriminator> ::= <unreserved>*

<infobus_data_item_name> ::= <unreserved>*

<unreserved> = ALPHA | DIGIT | safe | extra

<extra> = `!' | ``' | `(` | `)' | `,'

<safe> = `$' | `-' | `_' | `.'

These notes apply to item names, though they are not suggested by the BNF description above:

  • The infobus_name is the one specified for a named InfoBus, or when using a default name for a DOCBASE, can be obtained by calling myMember.getInfoBus().getName(). Note that infobus_name cannot begin with `-', and the use of `%' as the leading character is reserved. (see Chapter 2).
  • The producer_class is a qualified Java class name with dot separators, like com.lotus.esuite.sheet.

3.5 Data item name properties

Many producers and consumers support only one data item at a time, and the rendezvous takes place when a name for the item matches the name in an InfoBusEvent. For such components, we recommend that the name of the item they consume or produce be externally configurable by supporting a property and/or Applet parameter called inputItemName or outputItemName, respectively. Following this convention will make it easier for developers to configure InfoBus-aware components.

Components that consume or produce more than one data item at a time, or do something other than a simple name match for the rendezvous, may expose names for the data items as makes sense to these components.

The property is intended to be preconfigured or set during initialization. However, if the value of a property with a data item name changes after the item has been announced, we recommend that the application revoke the item and send a new available event with the new name.

3.6 DataFlavors and describing data items

DataFlavors and the MIME type strings they expose can be used to describe data items provided in the InfoBus rendezvous. This is intended as a hint to the consumer, to determine before requesting data from an available event whether it can make use of it. It is also a hint to the producer, to determine whether it can supply the data in response to a request event in a useful form.

The DataFlavors can be exposed in available and request events with MIME strings. The InfoBus class defines static constant Strings that should be used for this purpose. The strings are in the following form:

application/x-java-infobus;class=<fully-qualified-interface-name>

where <fully-qualified-interface-name> is one of the following:

  • the package qualifier plus the name of one of the access interfaces defined in this specification (ImmediateAccess, ArrayAccess, RowsetAccess, ScrollableRowsetAccess, and DbAccess). Example: MIME_TYPE_IMMEDIATE_ACCESS is defined as
  • "application/x-java-infobus;class=javax.infobus.ImmediateAccess"
  • the package qualifier plus the name of one of the four access interfaces defined in JDK Collections (Collection, Map, Set, and List). Example: MIME_TYPE_COLLECTION is defined as
  • "application/x-java-infobus;class=java.util.Collection"
  • the package qualifier plus the name of the DataItem class, which is used to indicate that if none of the preceding DataFlavors are available, that any other access type should be provided. Example: MIME_TYPE_ANY_ACCESS is defined as "application/x-java-infobus;class=javax.infobus.DataItem".
In announcing the availability of a data item, a producer can supply an array of DataFlavors available for the item by way of a parameter on the InfoBus.fireItemAvailable() method. The value of this parameter, whether a reference to an array or just null, is available to the consumer by way of InfoBusItemAvailableEvent.getDataFlavors(). The flavors are the union of the three groups listed above. See also Chapter 4 for more discussion on data flavors.

Similarly, a consumer can indicate its preferred format for a data item by providing an array of DataFlavors, ordered by preference, on the InfoBus.findDataItem() and findMultipleDataItems() methods.. The value of this parameter, whether a reference to an array or just null, is available to the producer by way of InfoBusItemRequestedEvent.getDataFlavors(). Because this is a hint to the producer, and the producer may supply an item that is not one of the preferred flavors, the consumer must code defensively to ensure that it has an item of a type it can use, for example by using the instanceof operator.

3.7 The InfoBus class: listener management

The InfoBus class is involved with membership and with rendezvous. The membership methods are discussed in detail in the previous chapter. This section discusses the InfoBus methods used to manage event listeners.

public synchronized void addDataProducer(InfoBusDataProducer producer)

public synchronized void removeDataProducer(InfoBusDataProducer producer)

public synchronized void addDataConsumer(InfoBusDataConsumer consumer)

public synchronized void removeDataConsumer(InfoBusDataConsumer consumer)

These methods add or remove event listeners from the list of data producers or data consumers maintained by each InfoBus instance.

The add methods should be called after the component has joined a bus (see Chapter 2). After adding an event listener, the class will begin to receive requests from data consumers on the same bus. If the add methods are called on a stale InfoBus instance, the StaleInfoBusException is thrown.

The remove methods must be called to remove all listeners before shutting down the application to release references from the InfoBus to the application class and to allow the InfoBus instance to be released.

3.8 The InfoBus class: firing events

This section discusses InfoBus methods used by producers, consumers, and data controllers for firing events, and is organized by the type of event fired. The first method in each group is one that is used by a producer or consumer to fire an event. This method actually defers the distribution of events to registered data controllers, or the default controller if no data controllers are registered; data controllers must not call this method.

Each event group also has methods to fire events to a specific target or a Vector of targets. Events fired with these methods are delivered directly to the indicated targets, not to other controllers. These methods can be called by data controllers, and must not be called by data producers; see Chapter 7 for more information.

Any method that fires events (including the `find' methods) can throw java.security.AccessControlException. Because this is a runtime exception, the use of `try...catch' clauses is optional.

Synchronization requirements for firing events

The InfoBus unites multiple components that work together as one application. Each InfoBus component must be aware that their code may execute in a multithreaded fashion, even when they do not spawn threads themselves, since they may be called from other components that do use multiple threads.

The InfoBus requires that when an available event for a particular data item name and producer is fired, it must be received by all listeners before the corresponding revoked event (i.e., a revoked event from the same producer with the same data item name) is fired. In order to guarantee that this requirement is met, before firing the available event, the producer must temporarily disable its ability to send the corresponding revoked event by using synchronization techniques appropriate for multithreaded operation.

This can be accomplished using a synchronization block around code that fires available and revoked events. In implementing this, a component must not specify its InfoBus as the parameter to the Java synchronized keyword, as this can cause a deadlock to occur.

Firing the item available event

public void fireItemAvailable(String dataItemName, DataFlavor[] flavors,
InfoBusDataProducer producer)

This method is called by producers to create an instance of InfoBusItemAvailableEvent and send it to data consumers on the bus, indicating the dataItemName and its producer. Producers can specify the flavors of data they can supply, or use null if they don't care to describe the data. Consumers can examine the offered flavors and decide whether they can use the data, or decide after requesting the data. Data controllers must not call this method.

public void fireItemAvailable(String dataItemName, DataFlavor[] flavors,
InfoBusDataProducer source, InfoBusDataConsumer target)

public void fireItemAvailable(String dataItemName, DataFlavor[] flavors,
InfoBusDataProducer source, Vector targets)

These methods are designed for use by data controllers. The first method creates an InfoBusItemAvailableEvent and delivers it to target. The second method creates a single InfoBusItemAvailableEvent and delivers it to all consumers specified in the Vector targets. All elements specified in targets must implement InfoBusDataConsumer. The targets Vector is copied by the InfoBus before distribution begins.

Firing the item revoked event

public void fireItemRevoked(String dataItemName, InfoBusDataProducer producer)

This method is called by producers to create an instance of InfoBusItemRevokedEvent and send it to data consumers on the bus, indicating the dataItemName and its producer. Data controllers must not call this method. Producers should call this method when a data item that has been announced as available will no longer be available.

public void fireItemRevoked(String dataItemName,
InfoBusDataProducer source, InfoBusDataConsumer target)

public void fireItemRevoked(String dataItemName,
InfoBusDataProducer source, Vector targets)

These methods are designed for use by data controllers. The first method creates an InfoBusItemRevokedEvent and delivers it to target. The second method creates a single InfoBusItemRevokedEvent and delivers it to all consumers specified in the Vector targets. All elements specified in targets must implement InfoBusDataConsumer. The targets Vector is copied by the InfoBus before distribution begins.

Firing the item requested event (find methods)

public Object findDataItem(String dataItemName, DataFlavor[] flavors,
InfoBusDataConsumer consumer)

This method is called by consumers to create an instance of InfoBusItemRequestedEvent and send it to data producers on the bus, indicating the dataItemName and consumer that wants it.

The consumer can specify its preferred flavors or just say null; see "DataFlavors and describing data items" in this chapter for more information.

The event is sent to each registered producer until a producer supplies a data item, at which time the data item is returned to the caller. The order of polling the data producers for such requests is indeterminate. If no producer satisfies the request, the method returns null to indicate that the requested data item is not available. Because data controllers control the distribution of this type of event, they must not call this method.

public Object findDataItem(String dataItemName, DataFlavor[] flavors,
InfoBusDataConsumer consumer, InfoBusDataProducer target)

public Object findDataItem(String dataItemName, DataFlavor[] flavors,
InfoBusDataConsumer consumer, Vector targets)

These methods are designed for use by data controllers. The first method creates an InfoBusItemRequestedEvent and delivers it to target, then returns a response to the request or null if target does not fill the request.

The second method creates a single InfoBusItemRequestedEvent and delivers it to the producers found in the targets Vector until one producer responds by filling the request or all producers have been queried. The method returns the response object if a producer filled the request, or null if no producer responded. All elements specified in targets must implement InfoBusDataProducer. The targets Vector is copied by the InfoBus before distribution begins.

public Object[] findMultipleDataItems(String dataItemName, DataFlavor[] flavors,
InfoBusDataConsumer consumer)

This method creates an instance of InfoBusItemRequestedEvent and sends it to all data producers on the bus, indicating the dataItemName and the consumer that requested it. The consumer can specify its preferred flavors or just say null.

Each data item supplied by a producer is stored in an array, which is returned to the caller. If no producers offer the requested data item, this method returns null.

3.9 The InfoBusEvent class

This is the base class for InfoBus events used for a rendezvous to provide a data item from a producer to a consumer. Subclasses are defined for each event type for the purpose of determining the event type via the Java "instanceof" operator.

public String getDataItemName()

This is an accessor that allows an event handler to look at the data item name to see if it can produce or use the named data item.

3.10 The InfoBusItemAvailableEvent class

This event is sent on behalf of a data producer to announce the availability of new data to all data consumers that have joined a given InfoBus instance. A producer creates and broadcasts the event by calling InfoBus.fireItemAvailable(). Because the constructor is package access, the event cannot be created directly by an application.

public Object requestDataItem(InfoBusDataConsumer consumer, DataFlavor[] flavors)

This method can be called by a consumer to request a data item announced by way of the InfoBusItemAvailableEvent. The method creates an InfoBusItemRequestedEvent and sends it directly to the producer that announced the item. The producer returns a reference to the item. When flavors is not null, it specifies an array of flavors the consumer can use. The producer may decide not to return an item if it cannot provide it in one of these flavors.

public InfoBusDataProducer getSourceAsProducer()

This method returns a reference to the source of the event, i.e. the event handler of the producer that generated the InfoBusItemAvailableEvent. The source of available events is always an InfoBusDataProducer. Event.getSource returns an Object reference to the producer. The consumer can use the reference to the producer to uniquely identify the producer of an announced item.

public DataFlavor[] getDataFlavors()

This method allows a consumer to consider the type of information being announced as available before requesting a data item. It returns a reference to array of DataFlavor objects that describe the formats the producer can provide either in the data item itself, or by way of Transferable.getTransferData(). If this method returns null, it means the producer did not specify the DataFlavors in announcing this data. See "DataFlavors and describing data items" in this chapter for more information.

3.11 The InfoBusItemRevokedEvent class

This event is sent on behalf of a data producer to announce the revocation of a previously announced item. It is used by consumers (who should release their reference to the item if they hold it) and controllers (who may wish to update a list of currently available items).

The event is created and broadcast by calling InfoBus.fireItemRevoked(). Because the constructor is package access, the event cannot be created directly by an application.

public InfoBusDataProducer getSourceAsProducer()

This method returns a reference to source of the event, i.e. the event handler of the producer that generated the InfoBusItemRevokedEvent. The source of revoked events is always an InfoBusDataProducer.

3.12 The InfoBusItemRequestedEvent class

This event is sent on behalf of a data consumer to find a named data item it would like to receive. For example, when an applet is starting, it cannot know whether a given data item has been announced, so it asks for the item by calling one of the find methods in InfoBus, which generate this event. Because the constructor is package access, the event cannot be created directly by an application.

public void setDataItem(Object item)

This is an accessor which the data producer uses to set a data item it is supplying in response to the request event. If the source of this RequestedEvent is an InfoBusSecureConsumer (forthcoming; see Appendix A), the call to setDataItem() will also call the SecureConsumer's setDataItem() method to permit the SecureConsumer to perform permission checks and determine trustworthiness of the responding producer.

Note that setDataItem() is a write-once method: if the data item in the event is non-null, it cannot be overwritten. The field will be null [writable] when the RequestedEvent is first delivered to a producer.

public Object getDataItem()

This is an accessor which is used by the InfoBus or a data controller to get a reference to the data item previously set by a data producer via setDataItem(). If no producer responded to the event, calling this method will return null. The method is also used in the implementation of InfoBus.findMultipleDataItems to get each data item available from the data producers on a given InfoBus instance.

public InfoBusDataConsumer getSourceAsConsumer()

This method returns a reference to the source of the event, ie the event handler of the consumer that generated the InfoBusItemRequestedEvent. The source of request events is always an InfoBusDataConsumer.

public DataFlavor[] getDataFlavors()

This method exposes the DataFlavors that the consumer prefers, as a hint to producers that can supply data in more than one format. If this method returns null, the consumer did not provide any DataFlavor preferences when it requested the event. The consumer may specify Mime types in the order it prefers, including InfoBus and standard Mime types. A special Mime string "x-InfoBus/AnyAccess" indicates that a consumer will accept any type of InfoBus access interface available for the item. Flavors are a hint to the producer, which is not required to consider the requested DataFlavors in supplying the data item. See also "DataFlavors and describing data items" in this chapter.

3.13 The InfoBusEventListener interface

This interface extends java.util.EventListener and java.beans.PropertyChangeListener to serve as a base class for the data producer and data consumer interfaces. Figure 3-2 shows the class hierarchy for these interfaces.

The event listeners must be registered with the InfoBus after joining it in order to receive events; this is accomplished by calling InfoBus.addDataProducer or addDataConsumer, as appropriate for the type of event listener interface. An object that serves as both consumer and producer would add itself via both add methods. The listener should be added during the applet's start() method (or its moral equivalent) and removed during the applet's stop() method in order to reduce event traffic when the browser switches to a different page.

Figure 3-2: Interface hierarchy for InfoBus event listeners

public void propertyChange(PropertyChangeEvent event)

This method is called whenever the member associated with this producer or consumer has its "InfoBus" property changed by a means other than calling the leave method, for example when a builder calls InfoBusMember.setInfoBus() to force it to talk to a different bus. The method isn't really a part of InfoBusEventListener, rather it is inherited from PropertyChangeListener. The suggested implementation includes:

  • Check event.getPropertyName() is "InfoBus" and event.getSource() is your parent InfoBusMember.
  • If the event.getOldValue() is not null, call event.getOldValue().removeDataProducer() to stop listening to the old InfoBus instance.
  • If the event.getNewValue() is not null, call event.getNewValue().addDataProducer() to listen for events from the new InfoBus instance.

3.14 The InfoBusDataProducer interface

This interface, which extends InfoBusEventListener, is implemented by classes that wish to serve as a data producer. A class that implements this interface should be registered with the InfoBus via addDataProducer() during the applet's start() method (or the moral equivalent if not an applet), and removed via during the applet's stop() method.

public void dataItemRequested(InfoBusItemRequestedEvent event)

This method is called by the InfoBus class on behalf of a data consumer that is requesting a data item by name. The suggested implementation:

  • check the data item name (obtained via event.getDataItemName()) to see if it is an item that can be supplied by this producer. If not, return.
  • [in Java 2 Platform] optionally, call AccessController.checkPermission() to determine permissions to decide whether to provide the item to the caller.
  • create an instance of the data item, or get a reference if it already exists, and set it via event.setDataItem().

3.15 The InfoBusDataConsumer interface

This interface, which extends InfoBusEventListener, should be implemented by a class that wishes to serve as a data consumer. The class should be registered with the InfoBus via addDataConsumer() during the applet's start() method (or the moral equivalent if not an applet), and removed during the applet's stop() method.

public void dataItemAvailable(InfoBusItemAvailableEvent event)

This method is called by the InfoBus class on behalf of a data producer that is announcing the availability of a new data item by name. A consumer that obtains a data item from a producer should be prepared to release it when the producer announces that the item is being revoked via InfoBusDataConsumer.dataItemRevoked(). The suggested implementation:

  • Optionally, call AccessController.checkPermission() to determine permissions in deciding whether to request the item from the producer.
  • Check the data item name (obtained via event.getDataItemName()) to see if the item is wanted. If not, return.
  • Get a reference to the data item by calling the event.requestDataItem() method.
  • If desired, and if a DataItemChangeManager is present, set a DataItemChangeListener on the data item.
public void dataItemRevoked(InfoBusItemRevokedEvent event)

This method is called by the InfoBus class on behalf of a data producer that is revoking the availability of a previously announced data item. A consumer that is using this data item should release it upon receiving this notification. The suggested implementation:

  • Check the data item name (obtained via event.getDataItemName()) and the producer sending the event (obtained via event.getSourceAsProducer() ) to see if this is an item held by this consumer. If not, return.
  • Remove any change listeners set on this data item.
  • Release all references to the data itemheld by this consumer.

3.16 The Proxy Listener classes

The InfoBus specification requires a reference to a data producer or data consumer, representing the source of an event, to be returned by the getSourceAsConsumer() and getSourceAsProducer() calls in the various InfoBus event classes. The specification also requires a reference to the owner of a data item to be returned by DataItem.getSource(). The purpose of these references is to uniquely identify the source of the event or data item, and is expressed as a reference of type InfoBusDataProducer or InfoBusDataConsumer. The unique identifier allows components to distinguish events or data items, in case more than one data item with the same name is present in a system.

The references also provide an opportunity for trouble from an errant component. Suppose that the source Object that implements one of these event listeners also happens to implement InfoBusMember or support properties for a Bean. Any component can introspect on this reference. If InfoBusMember is present, the recipient could call it's setInfoBus() method, for example passing a null reference. This would effectively force the source of an event or data item off the InfoBus it previously joined.

We recommend the use of a simple technique to eliminate the opportunity for such problems. An InfoBus component creates a separate event listener to serve as its proxy for handling events, and registers it as its event listener. When one of the methods to obtain a reference to the source of an event or data item is called, it gets a reference to the proxy listener rather than the parent class. The parent is protected because the proxy Object has nothing of any particular interest that can be introspected.

The InfoBus makes this implementation easier by providing classes that serve as the proxy listeners in reusable form. To use a proxy listener, the parent simply creates an instance of the class, passing a reference to itself to the constructor. The parent is required to implement the corresponding listener interface (i.e. InfoBusDataProducer or InfoBusDataConsumer), because the proxy listeners will call back to methods on these interfaces for the handling of events, but the parent does not register itself as a listener. The sample applications included with the InfoBus distribution archive illustrate the technique, and code can be cut and pasted from the samples into your application.

3.16.1 The InfoBusDataProducerProxy class

InfoBusDataProducerProxy is the proxy listener class used by producer components.

public InfoBusDataProducerProxy( InfoBusDataProducer parent )

The parent parameter is a reference to the Object that will handle InfoBusItemRequestedEvents and PropertyChangeEvents received by the producer proxy listener.

public void dataItemRequested( InfoBusItemRequestedEvent event )

The dataItemRequested method is called by InfoBus or a data controller to deliver an InfoBusItemRequestedEvent to this component. The implementation in the proxy class simply delegates the handling of the event to its parent class.

public void propertyChange( PropertyChangeEvent event )

The propertyChange method is called by InfoBus or a data controller to deliver an PropertyChangeEvent to this component. The implementation in the proxy class simply delegates the handling of the event to its parent class.

3.16.2 The InfoBusDataConsumerProxy class

InfoBusDataConsumerProxy is the proxy listener class used by consumer components.

public InfoBusDataConsumerProxy( InfoBusDataConsumer parent )

The parent parameter is a reference to the Object that will handle InfoBusItemAvailableEvents, InfoBusItemRevokedEvents, and PropertyChangeEvents received by the consumer proxy listener.

public void dataItemAvailable( InfoBusItemAvailableEvent event)

This method is called by InfoBus or a data controller to deliver an InfoBusItemAvailableEvent to this component. The implementation in the proxy class simply delegates the handling of the event to its parent class.

public void dataItemRevoked( InfoBusItemRevokedEvent event)

This method is called by InfoBus or a data controller to deliver an InfoBusItemRevokedEvent to this component. The implementation in the proxy class simply delegates the handling of the event to its parent class.

public void propertyChange( PropertyChangeEvent event )

The propertyChange method is called by InfoBus or a data controller to deliver an PropertyChangeEvent to this component. The implementation in the proxy class simply delegates the handling of the event to its parent class.

4. Data items

4.1 Overview

Data items are any Java Objects passed by reference from a producer to a consumer by way of a request event, and any sub-items when collection interfaces are used. The InfoBus API defines a data item transfer object as an Object for maximum flexibility and compatibility with the JDK Collection classes. The InfoBus API defines several interfaces to add InfoBus-standard functionality to the data items:
  • The DataItem interface provides descriptive and identifying information about the data item itself.
  • The DataItemChangeManager interface manages DataItemChangeListeners from consumers.
  • The DataItemView interface provides methods to manage a view associated with an item.
  • The awt.data-transfer.Transferable interface provides an alternate access mechanism for data in another standard format.
  • A variety of standard access interfaces can be implemented by a data item to provide application-independent access methods for consumers to navigate and retrieve data from the item.
A consumer can examine a data item using the instanceof operator (or catch a cast exception) to discover whether a given interface is available on the item. For example, the consumer can find out whether change notifications are available on a given data item by testing for an instanceof DataItemChangeManager.

4.2 Example: A simple data item

Figure 4-1 - A simple data item offering a Float value with change listener support

Data items can be a single-valued Object wrapper using the ImmediateAccess interface. Figure 4-1 illustrates the "MonetaryDataItem" from the SimpleProducerBean sample application. It implements a data item to identify and describe the data. It implements a DataItemChangeManager to accept listeners from consumers that want change notifications. The Float, which represents a dollar value, is a member data item, which the consumer can access using methods provided by the ImmediateAccess implementation.

A simple data item class definition looks like this:

public class MonetaryDataItem

implements ImmediateAccess, DataItem, DataItemChangeManager

{

protected Float m_value;

// methods for DataItem
// methods for DataItemChangeManager
// methods for ImmediateAccess
};

To access the Float object contained in the MonetaryDataItem instance, the ImmediateAccess.getValueAsObject() method can be called. The reference to the Float is returned, allowing calls to methods on that object, as described in java.lang.Float. The ImmediateAccess interface also defines methods to return a string renderings of the data. The SimpleConsumerBean example uses ImmediateAccess.getPresentationString() to getting a formatted currency string from the producer on the basis of a specified Locale.

For many data items, the data object could be a part of the inheritance hierarchy, in which the class declaration above would extend the data object. This is possible when the data object is not declared as a final class. In such cases, getValueAsObject() simply returns this.

Note that this example does not embrace our recommendation that the data be separate from the access object to allow fast creation of data items, the way the next example does.

4.3 Example: A spreadsheet data item

SimpleProducerBean and SimpleConsumerBean are basic examples of producer and consumer applet/Beans. In real-world examples, data items will often contain more interesting data structures, such as a collection of other data items, using various collection interfaces to provide rich structuring of a complex data set.

Figure 4-2 - InfoBus objects for a spreadsheet producer that has two cells

Figure 4-2 shows InfoBus objects in a spreadsheet producer, which provides access to a collection of cells by way of the ArrayAccess interface. The getItemByCoordinates() method returns an ImmediateAccess item to provide access to a given cell's data. The spreadsheet offers change notifications at both the sheet and cell level. A spreadsheet producer might also implement a Collection to provide access to various ranges of cells (not shown in the picture).

Spreadsheet data structures generally contain a lot of information that are used for internal purposes and would not be provided to consumers, for example a formula used to calculate the value of a cell. They can also be large, and contain many cells and ranges. This has two important implications in implementing the model.

First, applications of this size will generally not carry copies of their data in the various data items they expose, because it is time-consuming and wasteful of resources. Instead, an access interface serves as a proxy for accessing the data from the internal representation of the sheet, and carries a means of getting the data (e.g., a reference to the cell in the internal representation) rather than a copy of the data.

Second, it is generally inefficient to create data items for all cells when the ArrayAccess data item is created. Instead, ImmediateAccess data items for cells should be created on demand and released when no longer needed.

4.4 The DataItem interface

The DataItem interface provides identifying and descriptive information about a data item. Well-behaved InfoBus producers always implement this interface on the top-level data item being exchanged. For multi-level data items, such as a Collection of data items, we recommend the implementation of DataItem for items at every level. This may not always be possible, thus consumers should be prepared to use it if it is there, and degrade gracefully otherwise.

public Object getProperty(String propertyName)

Returns a property or metadata information about the item, as indicated by key. One key is recommended for DataItems at the top-level: the "Name" property must provide the data item name that was used for announcement or request in the rendezvous process. This does not apply to nameless DataItems, ie those below the rendezvous point of a hierarchical data item. Support for other properties is optional; null should be returned for unsupported properties. Property names should not contain the `*' character.

public InfoBusEventListener getSource()

This method returns a reference to the creator of this data item. This method should return a reference to the InfoBusEventListener (usually an InfoBusDataProducer) used for rendezvous of this item as their source.

Data items can also be supplied by a consumer to the producer, for temporary use in changing mutable data items (to provide to the producer a means of accessing the new value), in which case the source of the temporary item is an InfoBusDataConsumer.

null is not a permissable return value from this method.

public void release()

This method allows a producer to know when a consumer has finished using a data item. Consumers are required to call this method when they are about to drop their last reference to an item or anything obtained directly or indirectly from the item, including subitems and transfer data (from Transferable.getTransferData()). Producers may optionally manage resources for data items in their implementation of this method.

Producer and consumer requirements for this method are discussed in "Requirements for releasing data items," later in this chapter. Some special considerations for release() for data items that implement RowsetAccess are discussed in Chapter 5.

4.5 The DataItemView interface

Producers may implement DataItemView to optimize the management of a view of the contents of a particular subset of records. The view represents the window of data that is currently visible to the consumer.

For example, a consumer of an object that implements ScrollableRowsetAccess and DataItemView can paint the cells in a grid view of a subset of its rows. As the view is scrolled, the items in the view change as new rows appear. Without the use of this interface, the view can be populated by changing the current row of a ScrollableRowsetAccess to get values to be displayed for each row in the view, but this can be time-consuming.

The ViewStart property indicates the number of the row in the row set that is seen as the first row of the view. There is no relationship between the current row of the rowset and the ViewStart; it is possible to scroll the view without affecting the current row, or change the current row without scrolling the view.

It is possible for a view to contain fewer rows than specified in getView(). Similarly, when scrolled to the end, the view may end up with fewer rows than were originally requested for viewSize. A similar situation can occur when rows are deleted from a rowset. In these cases, the ArrayAccess obtained from getView() must indicate the number of rows actually in the view from dimension[0] are returned by getDimensions(). Attempts to access items beyond this dimension must cause IndexOutOfBoundsException to be thrown.

This use of this interface is optional: producers can implement it or not as they choose; consumers may use it or not if it is present.

public int getViewStart()

Returns the current value of the ViewStart property.

public void setViewStart(int absoluteRow)

Sets ViewStart to absoluteRow. The absoluteRow should be greater than or equal to zero.

public void scrollView(int relativeAmount)

Changes ViewStart by relativeAmount relative to its current position. relativeAmount is negative to move backwards, or positive to move forward.

public ArrayAccess getView(int viewSize)

Returns a two-dimensional ArrayAccess object that represents a row and column view with viewSize rows. The array must be two-dimensional, corresponding to row and column numbers in the view, respectively. The ArrayAccess should be read-only; an attempt to set a new value must throw UnsupportedOperationException.

Sub-items returned by this ArrayAccess must be ImmediateAccess items that correspond to the current view to provide standard access to the values in each sub-item. If the ViewStart property changes, the values returned by items in the array change so that the view maps to a different range of rows in the row set.

For example, if DataItemView were implemented on a RowsetAccess object, and an ArrayAccess was obtained by calling this method, and ViewStart is 0, getting the item at coordinate [1,2] of the array returns the item at row 2, column 3 in the row set. If the consumer calls scrollView(5), ViewStart changes to 5, and the value of item [1,2] changes to be the value at row 7, column 3 in the row set.

4.6 Using the awt.datatransfer.Transferable interface

The Transferable interface can optionally be implemented on any data item at any level. The Transferable mechanism provides an alternative to the access interfaces for accessing one or more JDK-standard formats for a data item. Using this mechanism it is possible to achieve essentially a dynamic clipboard implementation. The Transferable object exposes the data items DataFlavors, as described in

java.sun.com/products/JDK/1.1/docs/api/Package-java.awt.datatransfer.html

When the producer wishes to share a Transferable implementation that can work for more than one data item, it can carry a reference to the implementation, and delegate Transferable method calls directly to that object.

When implementing Transferable.getTransferDataFlavors(), the returned array must include only those MIME types for data that can be accessed via Transferable.getTransferData().

Note that when Transferable.getTransferData() is used to get data in a particular data flavor, the reference handed back counts as one of the references that must be released prior to calling DataItem.release() at any point above the Transferable object.

4.7 Synchronization and locking

InfoBus access interfaces do not provide an explicit locking API. Producers can lock a given class instance in the implementation of access methods using synchronized methods or blocks as needed.

4.8 Requirements for releasing data items

Some data items require a critical resource to be allocated by the producer. For example, a data access component may allocate a system resource on a database server that must be released when it is no longer needed. Such producers will typically track the consumers that have requested data items associated with the resource, for example by count or by handing out separate instances of the data item access object, and release the resources when the last consumer indicates that it has finished using the resource. The DataItem.release() method is designed to provide an indication to the producer when a given consumer has finished using a data item.

Consumer requirements

Consumers are required to call the DataItem.release() when they are finished using any object obtained directly or indirectly from the data item, including objects returned by Transferable.getTransferData(). After a consumer calls this method, it must not make any further calls on the DataItem or its sub-items, and should drop its reference (for example, if it's member data, set that member reference to null). When release() is called for any DataItem, it means that the consumer is finished with the item at that level and all subitems it may have. When a consumer passes around a data item references among various objects it manages, it must track its use of the references so that it knows when the last reference is dropped.

The consumer may optionally look for sub-items that implement DataItem and release these when they are no longer needed. This is a good idea for large, multi-level collection data items.

Producer requirements

DataItem is required for all top-level (rendezvous) data items, and is optional for all sub-items in a collection hierarchy. The producer is required to provide an implementation of release() for each object that implements DataItem. However, DataItem.release() is only a hint to the producer, and it can use any strategy it chooses for managing associated resources, or provide an empty implementation to ignore the hints.

A producer may decide to manage resources at the top-level data item, or at all levels of the collection hierarchy, or not at all, according to its own requirements. In a multi-level collection hierarchy which contains a DataItem at more than one level, calling DataItem.release() at any level means that that node and all nodes below it are released; calling release() on the top-level DataItem releases the entire data item for the consumer that called it.

When a producer supports the release of resources, it must do so in such a way that when one consumer calls release(), it will not affect other consumers who hold a reference to any part of the same data item. Of course, because a producer manages the lifetime of a data item, it can revoke it at any time it chooses, but ideally it aims to minimize disruption to consumers. In any case, when a producer does release resources associated with a data item, it should always send a DataItemRevokedEvent to listeners of the item, and if it is a top-level item, it should also send an InfoBusItemRevokedEvent by calling InfoBus.fireRevoked().

Example

Consider Figure 4-3. There's a data item called A which was provided to the consumer via a rendezvous. A implements a DataItem and a Collection interface. Collection A has two sub-items, Collection B1 and B2, which each implement DataItem. Collection B1 has one sub-item, an ImmediateAccess C, that implements Transferable but does not implement DataItem. Suppose the consumer has requested references to B1, B2, C, and a data transfer object T obtained from C.getTransferData().B1.release() should be called when all references to C and T have been dropped, and the consumer plans to drop B1 immediately after calling B1.release().

Figure 4-3 - example for describing release() rules

A.release() should be called when all references to B1, B2, C, and T have been dropped, and the consumer will drop A immediately after calling A.release(). Note that calling A.release() implies that all sub-items are also released, so when all references to sub-items have been dropped, B.release() need not be called before calling A.release().

4.9 Access Interfaces

In the first chapter we suggested that data items suitable for exchange on the InfoBus are those that might be useful to more than one consumer and might be available from more than one producer. Another way of considering this is: can the data be represented by one of the standard access interfaces defined here? Although private access methods can be used with items exchanged on the InfoBus, it defeats the purpose of providing a standard means of exchange between arbitrary components.

The InfoBus access interfaces, when implemented on a data item, allow the consumer to access the producer's data in a standard, application-independent fashion, without knowing the details of the internal representation of the data the way the producer sees it. A producer should implement as many standard access interfaces as possible, to support the greatest variety of consumers, and richer function of those consumers. In general, the access interfaces are not mutually exclusive; depending on the nature of the data, it might make sense to implement all of them.

With the access interfaces presented in this specification, together with the JDK Collection classes, most types of data items, with any arbitrary structure, should be representable and accessible in a standard fashion. Though we encourage these as the standard, we also recognize the need for future compatibility, thus Object is the data type specified by get/setDataItem() in the InfoBusItemRequestedEvent. Object is also the data type for objects managed by the JDK Collection classes. See also "The use of JDK Collection interfaces for InfoBus access" in this chapter.

4.10 Mutability

Producers establish a policy of when, if ever, data can be changed by consumers. The policy may vary depending on the consumer's permissions. Most InfoBus access interfaces define methods for changing the data items. If a producer chooses not to support changes to a data item, whether for any consumer or on the basis of examining a particular consumer, it can throw an exception in place of making the change. This style is in keeping with the mutability design for JDK Collections.

When using Java 2 Platform, the producer may call AccessController.checkPermission() to determine permissions during the handling of a method that can change a data item. The producer decides how permissions will be used in this case.

We recommend that producers accept an ImmediateAccess, if present in the argument to setValue-type methods, as the preferred source of a newValue, but also allow for producers to accept other types of Objects that make sense for the application. This applies to all access interfaces defined in this InfoBus Specification as well as the similar methods in interfaces defined for JDK Collections. ImmediateAccess provides the producer with methods needed to determine the new value. It can attempt to parse a string representation, or copy an Object if it is of a suitable type, or use the value from an Object.

The mutable data item must implement all methods that change data items, including collections, in a way that is thread-safe, for example by using a synchronized method or block. If the data item supports DataItemChangeEvents, it must distribute change events due to changes before exiting the synchronized code region. This means a consumer that is changing a data item can rely on seeing the event for the change it initiated before its call to change a data item returns.

4.11 The ImmediateAccess interface

A data item implements this interface to allow data values to be retrieved directly from calls to methods on this interface, returning an immediate rendering of the data as a String or Object. The interface is also convenient for wrapping final Objects, which cannot be passed with additional interfaces via a subclass, as in the example showing a data item housing a Double at the beginning of this chapter.

ImmediateAccess is strongly recommended for data items that are not collections, because they provide common renderings as a String or Object, independent of the type of data the item represents. This makes life easy for data consumers, who can simply use these strings for representing the object to the user, without knowing any more about the nature of the Object.

ImmediateAccess can also be used to supply a user-presentable string identifying the collection as a whole. This may be different from the advertised data item name, which is obtained by a method in the DataItem interface.

public String getValueAsString()

This method returns an unformatted representation of the data item. For example, if the item represents a dollar value, the string may return just the digits. There is no requirement that the returned string be the same as getValueAsObject().getString().

public String getPresentationString(java.util.Locale locale)

This method returns a formatted representation of the data item appropriate for the specified locale. For example, if the item represents a dollar value and the locale indicates the United States, the string may be formatted with a dollar sign, commas, and a decimal point. If locale is null, the consumer is asking for the producer's default locale.

This method may throw UnsupportedOperationException to indicate that it does not support the specified locale.

public Object getValueAsObject()

This method returns a reference to the Object that is "wrapped" in this ImmediateAccess. The type of the Object is implementation dependent. A consumer that accesses the Object directly may interrogate it to discover its type, or may examine the MIME type (if available) for this purpose. A producer may choose not to expose an Object by returning null.

Consumers should not attempt to modify a mutable Object returned from this call, because the producer cannot know when the Object is changed, and thus cannot send data item change notification events. Producers can protect themselves from such consumers by handing out a copy of the Object that holds the current value, so that changing the copy does not affect the value the producer maintains.

public void setValue(Object newValue) throws InvalidDataException

This method sets a newValue for the immediate data in this object. We recommend that all producers accept an ImmediateAccess, if present, as the preferred source of a newValue, but also allow for producers to accept other types of Objects that make sense for the application. A producer should not change the type of Object housed in an ImmediateAccess, only it's value. See also "Mutability" in this chapter.

The producer's implementation must obtain the new value from the argument object before returning, rather than saving a reference to it, because the caller may change its value after the return. In obtaining the newValue from the argument, the provider's implementation may need to make a deep (recursive) copy of newValue to get all values changed by the consumer, for example when newValue is a collection of subitems.

If the item supports change notifications, the producer should notify listeners after the value has changed, but before returning from setValue. Such change notifications look the same as if the producer had changed the value itself. See "Mutability" in this chapter for a discussion of synchronization requirements in changing a value.

UnsupportedOperationException should be thrown when the underlying data source does not support changes from any consumer (e.g., read-only). java.security.AccessExceptions should be thrown when a given consumer does not have permission to change the data. Java.lang.IllegalArgumentException should be thrown if newValue is a type that is not recognized by the producer for representing the new value. InvalidDataException should be thrown if the new value is invalid for this field.

4.12 The ArrayAccess interface

Data items that implement the ArrayAccess interface are collections of data items organized in an n-dimensional array. ArrayAccess objects are bounded in size; that is, their dimensions can be obtained at any time.

Essential to the notion of an array is that you have random access (without significant performance penalty) to any element of the array. Almost all the other forms of data could be modeled as a (degenerate) form of an array, but often the notion of unpenalized access to any element does not hold true.

Methods that specify coordinates throw ArrayIndexOutOfBoundsException when the coordinates are invalid.

public int[] getDimensions()

The number of integers in the returned array indicates the number of dimensions in the object implementing the ArrayAccess, and the value of each integer indicates the number of elements in the ArrayAccess in that dimension - e.g., a return value of {5,6,7} indicates a 5 x 6 x 7 three-dimensional ArrayAccess.

public Object getItemByCoordinates(int[] coordinates)

This method retrieves an individual data item from an ArrayAccess by way of its index. Retrieval of a data item via getItemByCoordinates() should not affect the cursor for other access interfaces on the object that implements ArrayAccess. Indexing in each dimension is zero-based; that is, coordinates[i] range from 0 to (getDimensions()[i] - 1 ), to agree with Java arrays.

null must be returned to indicate an empty cell.

public void setItemByCoordinates(int[] coordinates, Object newValue)
throws InvalidDataException

This method sets a new value for an item at the indicated coordinates in an ArrayAccess. Setting a data item via this method should not affect Iterators for other access interfaces on the object that implements ArrayAccess. Indexing in each dimension is zero-based; that is, coordinates[i] range from 0 to (getDimensions()[i] - 1 ), to agree with Java arrays. We recommend that all producers accept an ImmediateAccess for setting the newvalue. See also "Mutability" in this chapter.

UnsupportedOperationException must be thrown when the underlying data source does not support changes from any consumer. java.security.AccessExceptions must be thrown when a given consumer does not have permission to change the data. Java.lang.IllegalArgumentException must be thrown if newValue is a type that is not recognized by the producer for representing the new value. InvalidDataException must be thrown if the new value is invalid for this field.

public ArrayAccess subdivide( int[] startCoordinates, int[] endCoordinates)

This method returns an ArrayAccess which is a subset of the ArrayAccess on which it was called, with coordinates in the new subset readjusted to start at zero. For example, a data set arranged as rows and columns can be divided into arrays representing individual columns or rows. The endCoordinates must be equal to or greater than the startCoordinates for all dimensions. The method throws an ArrayIndexOutOfBoundsException on an out-of-bounds access.

4.13 The ReshapeableArrayAccess interface

Producers that allow a consumer to change the shape of an array, including changing the dimensions, the number of dimensions, or inserting or deleting all elements in a particular dimension (i.e. "delete column"), implement the ReshapeableArrayAccess interface to indicate their willingness to change the shape and provide methods used for this purpose. ReshapeableArrayAccess extends ArrayAccess to add these methods.

Producers that supply data items with the ReshapeableArrayAccess are required to implement the DataItemChangeManager on those data items, and to fire the DataItemShapeChangeEvent when modifications are made to the array by way of the methods described below.

public void setDimensions(int[] newDimensions) throws IllegalArgumentException

This method can be called to change the dimensions of an existing array. Such changes involve appending or removing one or more new columns, rows, planes, etc from the end(s) of the array . Any new cells that are added in this operation must be empty cells (e.g., calling getItemByCoordinate() on a new cell returns null).

The number of integers in newDimensions indicate the requested number of dimensions in the object implementing the ArrayAccess, and the value of each integer indicates the requested number of elements in the ArrayAccess in that dimension.

All producers that implement this interface must be able to change existing dimensions. Producers may or may not support a change to the number of dimensions; those that do not support a change in number of dimensions throw the UnsupportedOperationException for such attempts.

IllegalArgumentException must be thrown if any of the specified newDimensions are less than one (zero for any dimension would result in an array with no cells).

For example, given a 2x2 array, passing newDimensions as [2,3] adds a new row. Calling this method with [2,2,3] either adds two new 2x2 planes to form a three-dimensional array of 2x2x3, or if the producer does not support changes to the number of dimensions, UnsupportedOperationException is thrown.

public void insert(int dimension, int position, int count)
throws IllegalArgumentException

This method is called to insert additional cells, columns, rows, planes, etc, in a specified dimension. dimension specifies which entry will be affected in the array of dimensions for this array (as returned by getDimensions()).

When adding cells in a given dimension, the extents of other dimensions are unchanged. For example, in a two-dimensional array, when inserting a new row of cells, the number of cells added is the extent of the column dimension.

position is a zero-based cell offset in the specified dimension. position can range from zero up to the maximum extent for the specified dimension. When position is zero up to the extent for the indicated dimension, cells are inserted before the indicated cell. When position is equal to the extent for the indicated dimension, cells are appended at the end of the array.

count specifies the number of cells to be inserted in the specified dimension; it must be greater than zero. The total number of cells inserted with the operation is count times the extents of each dimension other than the specified dimension. For example, when count is one for a 3x3 array, a new row or column of three cells is inserted. See the examples below for more information.

IllegalArgumentException must be thrown if dimension is greater than the count of entries from the dimensions returned by getDimensions(). It must be thrown if position is less than zero or greater than the count of cells in the specified dimension. It must be thrown if count is not greater than zero.

Finally, for the special case of an n-dimensional array, where n>1 and one or more of the dimensions other than the one specified by dimension has a zero extent (thus, the array has no cells), it is not possible to insert new cells, and IllegalArgumentException must be thrown. In this case the setDimensions() method can be used to change the dimensions. Note that an array with a zero extent can be the result of calling the delete() method when position=0 and count is set to the maximum extent for the given dimension.

The required behavior is easiest to understand when considering simple pictoral representations. In the following diagrams, the white squares represent cells of an ArrayAccess that already exist, and may be empty or not. The gray cells represent new, empty cells added as a result of the indicated operation. Note that the use of the terms columns, rows, and planes to refer to particular dimensions is arbitrary, and is only used for describing the diagrams for these examples.

Example 1: Inserting cells in a single-dimension array. Before insertion, getDimensions() returns [3]. After calling insert(0,1,2) to insert two cells before cell 1, new cells are added as shown, and calling getDimensions() returns [5].

Example 2: Inserting columns in a two-dimension array. Before insertion, getDimensions() returns [3,2]. After calling insert(0,1,2) to insert two columns before column 1, new cells are added as shown, and calling getDimensions() returns [5,2].

Example 3: Appending columns in a two-dimension array. Before insertion, getDimensions() returns [3,2]. After calling insert(0,3,2) to append two columns (note that the position here is equal to the count of cells in dimension 0, meaning "append"), new cells are added as shown, and calling getDimensions() returns [5,2].

Example 4: Inserting rows in a two-dimension array. Before insertion, getDimensions() returns [3,2]. After calling insert(1,1,2) to insert two rows before row 1, new cells are added as shown, and calling getDimensions() returns [3,4].

Example 5: Inserting columns into three-dimension array. Before insertion, getDimensions() returns [3,2,3]. After calling insert(0,1,2) to insert two columns before column 1, new cells are added as shown in all three planes, and calling getDimensions() returns [5,2,3].

Example 6: Inserting rows into three-dimension array. Before insertion, getDimensions() returns [3,2,3]. After calling insert(1,1,2) to insert two rows before row 1, new cells are added as shown in all three planes, and calling getDimensions() returns [3,4,3].

Example 7: Inserting planes into three-dimension array. Before insertion, getDimensions() returns [3,2,3]. After calling insert(2,1,2) to insert two planes before plane 1, new cells are added as shown in two new planes, and calling getDimensions() returns [3,2,5].

public void delete(int dimension, int position, int count)
throws IllegalArgumentException

This method is called to delete cells, columns, rows, planes, etc, in a specified dimension. dimension specifies which entry will be affected in the array of dimensions for this array (as returned by getDimensions()).

position is a zero-based cell offset in the specified dimension. When position is zero up to the extent for the indicated dimension, deletions start at the indicated cell.

count specifies the number of deletions in the specified dimension; it must be greater than zero, and less than the remaining cells of the indicated dimension starting with the indicated position.

IllegalArgumentException must be thrown if dimension is greater than the count of entries from the dimensions returned by getDimensions(). It must be thrown if position is less than zero or greater than or equal to the count of cells in the specified dimension starting with the indicated position. It is thrown if count is not greater than zero, or if position plus count is greater than the current extent in the indicated dimension.

Some examples of the required behavior:

Example 1: Deleting cells in a one-dimension array. Initially, getDimensions() returns [5]. After calling delete(0,1,2) to delete two cells starting with cell 1, cells are removed as shown, and calling getDimensions() returns [3].

Example 2: Deleting rows in a two-dimension array. Initially, getDimensions() returns [3,4]. After calling delete(1,1,2) to delete two rows starting with row 1, cells are removed as shown, and calling getDimensions() returns [3,2].

Example 3: Deleting planes in a three-dimension array. Initially, getDimensions() returns [3,4,5]. After calling delete(2,1,2) to delete two planes starting with plane 1, cells are removed as shown, and calling getDimensions() returns [3,4,3].

Example 4: Deleting entire contents of a two-dimension array. Initially, getDimensions() returns [3,2]. After calling delete(1,0,2) to delete two rows starting with row 0 (i.e., all rows), cells are removed as shown, and calling getDimensions() returns [0,0]. Calling delete(0,0,3) to delete three columns starting with column 0 (i.e., all columns) has the same result. In both cases, empty cells can be added only by calling setDimensions() with an array where both extents are non-zero.

4.14 Using JDK Collection interfaces for InfoBus access

In addition to the InfoBus data access interfaces described in this chapter and in Chapter 5, the interfaces defined for JDK Collections are also allowed for a standard means of sharing collections of data and collection hierarchies. Collection, Map, List, and Set interfaces are all suitable container contracts.

Because these interfaces are not defined in JDK 1.1, an interim release of the JDK Collections package is available from JavaSoft; its package name will be com.sun.java.util.collections.

In Java 2 Platform, the implementations provided in java.util can be used if convenient, or the interfaces can be used with a custom implementation, for example, to provide standard access for an existing private data structure).

We recommend that implementations of the methods to set new values use InfoBus access interfaces as the source of the new value (or container of values). See also "Mutability" in this chapter.

JDK Collections are described at http://www.javasoft.com/products/jdk/preview/docs/guide/collections/.

4.15 ArrayAccess and JDK Collections

When ArrayAccess and any of the JDK Collection access interfaces are implemented on the same data item, there may or may not be a relationship between the order of accessing elements using an Iterator or ListIterator and the indices of an ArrayAccess. When using the standard implementations of the collections, it may not be convenient to provide indexed access in an efficient manner.

When using the JDK Collection interfaces as a public contract for a private implementation along with ArrayAccess, we recommend that the right-most integer in the dimensions array be the index that changes most frequently when an object is iterated. For example, an ArrayAccess that returns {5, 4, 3} as its dimension array is a 5 x 4 x 3 array, and succussive calls to Iterator.next() would return the following elements from ArrayAccess:

0, 0, 0

0, 0, 1

0, 0, 2

0, 1, 0

0, 1, 1

etc.

4.16 Tree data items

InfoBus does not define a specific access interface for implementing a tree. Trees should be implemented by using one of the JDK Collection interfaces recursively, e.g. creating a Collection that contains Collection objects, and so on.

4.17 Requirements for InfoBus-compliant data items

The task of creating an InfoBus-compliant data item includes making a decision on which of the interfaces to implement. While the InfoBus API requires only that a data item be an Object, there are additional requirements for an InfoBus-compliant data item. Note that the `top-level' item refers to the one handed out via the rendezvous mechanism, which may have sub-items. Data items that are members of a collection data item are referred to here as "sub-items."
  • The spec requires that top-level data items implement DataItem. We recommend implementing DataItem for sub-items whenever possible.
  • DataItemChangeManager is recommended for all data items where it makes sense, including sub-items of multi-level data items. When present, the manager is required to manage listeners and send notifications to them for all changes.
  • An InfoBus-compliant data item is required to provide at least one of the standard access interfaces for top-level data items. We recommend the use of these interfaces for all sub-items. Standard access interfaces include those defined in this spec, and those defined by the JDK Collections specification.
  • We recommend that methods used to set a value in a mutable data item accept an ImmediateAccess, if present, as the source of the new value; other Objects can be accepted at the discretion of the producer.

5. Database access interfaces

5.1 Overview: database access for the InfoBus

The relational access model

In many cases including Relational Database Management Systems, data is organized into (or can be returned as) tables containing rows and columns. Each row has the same number of columns, and each column is homogenous - within a column, the data is of a particular datatype, or null. A table may have no rows. The data is usually managed by a server program which controls all access to the data.

To retrieve data from such a source, the client composes a query (typically in a dialect of SQL), submits it to the database server, and receives a result set of rows, or rowset, in return. It is then possible to determine the "shape" of the rowset (the number of columns returned and their names and datatypes). There may be no data rows in the rowset.

To send data to such a source, or modify the data, INSERT, UPDATE and DELETE operations are supported. These return a success indicator and the number of rows affected (this may be zero), but not a result set. Other operations are also usually supported, including such things as creating and deleting tables, granting and revoking access, and storage management. These operations return a success indicator but not a result set or number of rows affected.

Database access and the InfoBus

While tables in a database and the rowsets returned from retrieval queries could be modeled as InfoBus ArrayAccess data items, this is not a natural match for the following reasons:

  • The number of rows and columns is not known ahead of time and can be expensive to determine, so ArrayAccess.getDimensions() cannot always be supported;
  • A very large number of rows may be returned
  • The column names and datatypes may not be known ahead of time and it may be necessary to discover this at runtime.
To solve these problems we provide the RowsetAccess interfaces. This family of interfaces can be used for constructing a data item in addition to or instead of other access interfaces defined in the previous chapter.

When the data is provided by a remote server, a data access component (DAC) can be constructed as a producer that provides RowsetAccess data items. The DAC serves as a translator between the remote source and the local consumers of the data. Figure 5-1 illustrates the use of a DAC for serving data to local consumers.

Figure 5-1: Using a data access component as a producer of RowsetAccess data items

5.2 The RowsetAccess model compared to other access interfaces

The RowsetAccess interfaces represent a different model from the access interfaces in Chapter 4, in that the contents of a RowsetAccess item change as the cursor is moved. This difference reflects the orientation of an external, potentially huge data store. Note that the data is not "in" the data access applet or bean, but in another data store, usually on another machine. The interfaces in chapter 4 are more oriented toward internal data, i.e. data which is "in" an applet or bean running in the local environment such that the total size is known and all the data is immediately available.

Also, the RowsetAccess interfaces extend the use of the DataItemChangeEvent in two ways. First, if there are any change listeners on a RowsetAccess item, a DataItemChangedEvent is emitted when the row cursor changes. Second, data items are used to represent column values, and as the row cursor changes, these data items are modified by the data producer and change notifications sent to any listeners. While this is a standard InfoBus mechanism, in this case it is the data producer itself which is changing the values of the items representing columns.

Even with these differences, it might makes sense to implement both RowsetAccess and ArrayAccess for some data items. For example, if a query results in a set of a hundred rows, the data access component (producer) may choose to make it available via both interfaces.

5.3 DataBase Cursor and RowsetAccess design

To process a retrieval query, a database server may do extensive work on behalf of the client, using available indexes wisely, constructing temporary tables when appropriate, obtaining and releasing physical locks on data and index pages, etc. The server typically maintains bookkeeping structures as it returns the result rows, and the current row is presented to the client via a "cursor." Servers free up resources as soon as possible in order to serve more clients more efficiently, so generally only one cursor is supported per result set. While some servers support backward scrolling cursors, only forward scrolling cursors are guaranteed.

In support of the database notion of a cursor, RowsetAccess implements a slightly different model for data items compared to those described in Chapter 4. Whereas it looks like a collection of records (rows), when the consumer obtains a row of information, the row actually contains the information for the record at the current cursor position. When the cursor changes, the contents of a row also changes. A change notification is available that tells the holder of a row item when its contents changed because of cursor movement. Also, when consumer changes the cursor, holders of an item of the current row all see their contents change. Finally, it is not possible to watch for value changes on an arbitrary row, only on the current row.

5.4 The RowsetAccess interface

Data items that implement the RowsetAccess interface are collections of rows obtained from a data source, usually a relational database server. The RowsetAccess interface contains methods to discover the number and type of columns, to get the next row, to obtain column values, and to insert, update and delete rows.

Initially the rowset is positioned before the first row, and it is necessary to call nextRow() to obtain each row.

Metadata methods

These methods return information about the rowset rather than the data itself.

public int getColumnCount()

Returns the number of columns in the rowset.

public String getColumnName(int columnIndex)
throws IndexOutOfBoundsException

Columns are numbered from one. Given the one-based columnIndex, returns the name of the column if available, else null. For example, calculated columns may have no name. See java.sql.ResultSetMetaData.

public int getColumnDatatypeNumber(int columnIndex)
throws IndexOutOfBoundsException

Columns are numbered from one. Given the one-based columnIndex, returns the column's SQL type using the encoding in java.sql.Types. See java.sql.ResultSetMetaData. For producer specific datatypes, this should be java.sql.Types.OTHER.

public String getColumnDatatypeName(int columnIndex)
throws IndexOutOfBoundsException

Given the one-based columnIndex, returns the column's data source specific type name. See java.sql.ResultSetMetaData. For producer specific datatypes, this should be the package and subpackage qualified name of the Java class used to represent the datatype, such as "com.yourorg.data.SpecialDataType".

Cursor movement

public boolean next() throws SQLException, RowsetValidationException

Advances the row cursor to the next row. Returns true if there is a next row, false otherwise. It is valid for a rowset to contain zero rows, so the first call to nextRow() may return false. If modification of the data is supported, moving the cursor may submit a pending change to the database.

When a rowset's cursor is moved, if the rowset has any DataItemChangeListeners, the data producer calls the listener's rowsetCursorMoved() method with a DataItemChangeEvent object. For more information, see Chapter 6, Monitoring changes to data items.

Note that only one data consumer should call nextRow(); if two or more consumers each get the same data item implementing RowsetAccess and both use nextRow(), each can miss some of the data.

public int getHighWaterMark()

Returns the total number of rows known to the data producer. The data producer should not throw an exception for this method.

In the simplest case, the data producer merely counts the rows as it fetches them. In more sophisticated cases, the data producer may be able to obain information from a middle tier which fetches rows in chunks.

public boolean hasMoreRows()

Returns a conservative indication of whether more rows exist in the set. Specifically, a returned value of false indicates that the last row has been accessed. A returned value of true indicates that further rows may exist.

Simple data providers may return true when actually on the last row and then return false after detecting they have fetched the last row. Data providers for sophisticated backends may be 100% accurate. Sophisticated consumers can avoid an extraneous attempt to retrieve non-existent rows in the case that false has been returned.

Data retrieval methods

InfoBus data access components must use the standard mapping between JDBC types and Java types (see JDBC specification, Section 8 - Mapping SQL Data Types into Java. The JDBC spec can be found at http://java.sun.com/products/jdbc/index.html). For a data item obtained as a column value, when obtained as an Object (for example, by calling ImmediateAccess.getValueAsObject()), the Object must have the same actual type as the Java type corresponding to the column's JDBC SQL type.

public Object getColumnItem(int columnIndex)
throws IndexOutOfBoundsException, SQLException

Given the one-based columnIndex, returns a data item which can be used to obtain the current value of the specified column. This is usually an ImmediateAccess item. The value changes as nextRow() is called, that is the data item tracks the current row, and if the column item implements DataItemChangeManager, DataItemChangeEvents are generated.

This method throws IndexOutOfBoundsException if the column index is invalid.

public Object getColumnItem(String columnName)
throws ColumnNotFoundException, DuplicateColumnException, SQLException

Given the columnName, returns a data item which can be used to obtain the current value of the column. This is usually an ImmediateAccess item.

As for getColumnItem(int columnIndex) above, the value changes as nextRow() is called; that is, the data item tracks the current row, and if the column item implements DataItemChangeManager, DataItemChangeEvents are generated.

This method is useful instead of the column number version above when many columns are returned and the order of the columns changes over time, but the names of the columns do not change.

This method throws an exception if the specified column is not present in the RowsetAccess object, or if more than one column with a matching name is found.

We define both flavors of getColumnItem() to return a data item which tracks the value of the specified column in the current row. By definition, this succeeds if the column name or number is valid, but throws an exception as described above if the column number or name is invalid.

Since the data item returned is (usually) an ImmediateAccess item, the data consumer must still call getValueAsObject(), getValueAsString(), or getPresentationString() to obtain the value. We specify that if no row is available, getValueAsObject(), getValueAsString(), and getPresentationString() should throw an exception.

If the data item returned is not an ImmediateAccess item, the data consumer must still invoke other methods to obtain values in the column. In relational databases, a column value is normally a scalar, but in principle RowsetAccess could be used to return non-scalar column data from other types of data sources.

Thus one need only obtain a data item for each desired column once. This is more efficient even for the simple use pattern.

Data modification methods

These methods insert, update, and delete rows. If the data item does not support a particular operation, the owner can throw UnsupportedOperationException (a runtime exception and thus not listed explicitly in the throws clause of these methods.) The owner may also throw java.security.AccessControlException if the caller does not have permission to change the data, or InvalidDataException if the new data is not valid.

Note that column values may also be modified via ImmediateAccess.setValue() if a data item has been obtained for the column.

After a row is changed, cursor movement may cause the row to be submitted to the underlying data store, and this may cause an exception. However, not all data providers submit changes on cursor change. The flush() method may be used to explicitly submit a changed row to the database.

public void newRow() throws SQLException, RowsetValidationException

Creates a new, empty row and sets the row cursor to this row. Since this changes the row cursor, this may propagate a changed row to the back end, and this may throw an exception.

public void setColumnValue(int columnIndex, Object object)
throws SQLException, RowsetValidationException, IndexOutOfBoundsException

Sets the value of the specified columnIndex in the current row to the supplied value. columnIndex is the one-based column number. This is used both to update existing rows and to supply values for new rows. We recommend that all producers accept an ImmediateAccess, if present, as the preferred source of a newValue, but also allow for producers to accept other types of Objects that make sense for the application.

public void setColumnValue(String columnName, Object object)
throws SQLException, RowsetValidationException, ColumnNameNotFoundException, DuplicateColumnException

Sets the value of the specified column in the current row to the supplied value. columnName must identify exactly one column. We recommend that all producers accept an ImmediateAccess, as the preferred source of a newValue. See also "Mutability" in Chapter 4.

public void deleteRow() throws SQLException, RowsetValidationException

Deletes the current row.

public void flush() throws SQLException, RowsetValidationException

Explicitly submits changes in the rowset to the underlying data store.

public void lockRow() throws SQLException, RowsetValidationException

Requests a row level lock on the current row, if supported by the backend and the data producer. The method does nothing if it is not supported.

Normally each row change is an implicit trancaction, and the lock is released by moving to a different row. If an explicit transaction has been started by way of a call to DbAccess.beginTransaction(), the locked is released during the processing of DbAccess.commitTransaction() or DbAccess.rollbackTransaction().

Determining the mutability of the data source

Data repositories support different combinations of retrieval, insert, update and delete. Some are read only, some allow all operations, and some allow other combinations such as read and insert but not delete or update.

The following methods allow the data consumer to determine which operations may be attempted. Note that a particular operation may fail due for other reasons such as access control, integrity constraints, or network connection problems.

public boolean canInsert()

Returns true if inserting new rows is allowed, false otherwise.

public boolean canUpdate()

Returns true if modifying the items in all columns in the existing rows is allowed, false otherwise.

public boolean canUpdate(String columnName)
throws ColumnNotFoundException, DuplicateColumnException

public boolean canUpdate(int columnNumber) throws IndexOutOfBoundsException

Returns true if modifying the items in the specified column is allowed, false otherwise.

public boolean canDelete()

Returns true if deleting rows is allowed, false otherwise.

Special methods

public DbAccess getDb()

This method returns a DbAccess item representing the database associated with the Rowset. This returns null if the DataProducer does not support the DbAccess interface. For more information, see the DbAccess interface below.

5.5 The ScrollableRowsetAccess interface

This interface extends RowsetAccess, and represents the case in which the data provider can support moving the row cursor backwards and creating multiple cursors.

public ScrollableRowsetAccess newCursor()

Returns a new ScrollableRowsetAccess having the same underlying data but an independent cursor. The new cursor is positioned before the first row. The object returned is a separate data item from the one on which newCursor() was called. The new data item has no name (i.e., if it implements DataItem, getProperty("Name") should return null).

public void setBufferSize(int size)

Asks the data provider to keep the specified number of rows immediately available. The specified size is a hint for performance and does not throw an exception if not supported.

public int getBufferSize()

Gets the buffer size in effect. If setBufferSize is not supported, this will be 1.

Cursor movement

In addition to the methods listed here, the next() method defined for RowsetAccess is also available.

public boolean previous() throws SQLException, RowsetValidationException

Moves the row cursor to the previous row. Returns true if there is a previous row, otherwise false.

public boolean first() throws SQLException, RowsetValidationException

Moves the row cursor to the first row. Returns true if there is a first row, false if the rowset is empty.

public boolean last() throws SQLException, RowsetValidationException

Moves the row cursor to the last row. Returns true if there is a last row, false if the rowset is empty.

public boolean relative(int numRows) throws SQLException, RowsetValidationException

Moves the row cursor forward the specified number of rows (or back if numRows is negative.) Returns true if the specified row exists, false otherwise. This can position the cursor before the first row or after the last row.

public int getRow()

Gets the row number of the current row.

public int getRowCount()

Returns the total number of rows in the rowset. Some data producers will not support this operation without fetching all the rows, and should throw UnsupportedOperationException.

public boolean absolute(int rowIndex) throws SQLException, RowsetValidationException

Moves the row cursor to the specified rowIndex. Returns true if the specified row exists, false otherwise.

5.6 The DbAccess interface

In some cases, the data consumer will wish to control the lifetime of a data item representing a rowset. This can be important if the retrieval query ties up significant resources on the database server, or large numbers of rows are involved, or both. In this scenario, only the consumer knows how long the data item is needed. For example, if a query returns a large number of rows (say one million), if there is only one consumer, and if the consumer is only interested in the first few rows (say one screenful), the data item and the resources on the database server should be released as soon as the consumer reads the first screenful of rows.

Also, in some cases it is more convenient for the data consumer to construct the query and control when it is executed and whether the result is made available to other InfoBus aware components.

Connect and disconnect methods

The Connect and Disconnect methods intentionally mirror their counterparts in java.sql.DriverManager and java.sql.Driver. They are intended for cases in which components other than the data producer need to control the time of connection and disconnection, since a connection to a database can be an expensive resource.

A producer can implement the DataItem interface on RowsetAccess items and DbAccess items to provide the release() method. By supporting release(), the producers may choose, if appropriate, to implicitly disconnect when the last dependency on resources goes away.

public void connect() throws SQLException

Attempts to establish a connection to the given database URL. Any required connection arguments such as user ID and password must be defined in the producer. For example, these might be supplied to the producer outside the DbAccess interface via HTML <PARAM>s, JavaBean properties, or incoming InfoBus data items.

public void connect(String url, String username, String password) throws SQLException

Attempts to establish a connection to the given database url using the supplied username and password.

public void connect(String url, Properties info) throws SQLException

Attempts to establish a connection to the given database url using the connection arguments in info. Typically, "user" and "password" properties are required.

public void disconnect() throws SQLException

Unconditionally disconnects from the current database. The producer should announce that all data items have been revoked prior to disconnecting, by firing an InfoBusItemRevokedEvent via the InfoBus and by firing a DataItemRevokedEvent on each DataItemChangeManager. Further use of the DbAccess object, except to connect, is undefined. Further use of RowsetAccess/ScrollableRowsetAccess objects associated with the DbAccess object is undefined.

public java.sql.DriverPropertyInfo[] getPropertyInfo(String url, Properties info)

This method allows a data consumer to discover what connection arguments the data producer requires to connect to a database. The database is specified by url, and a proposed list of connection arguments is specified by info (this may initially be empty.)

The resulting array of DriverPropertyInfo objects may be empty if no connection properties are required. Otherwise it is a list of properties, some of which may be required. See java.sql.Driver.getPropertyInfo. In complex cases it may be necessary to call getPropertyInfo() multiple times - the possible or required connection arguments may depend on previous choices.

Query methods

public Object executeRetrieval(String retrieval, String dataItemName,
String options) throws SQLException

This method executes the specified retrieval query and returns the result as an Object. retrieval specifies the retrieval query, which is typically a SQL SELECT or a stored procedure which returns a result. If dataItemName is not null, the data provider should make the resulting RowsetAccess item available under the specified dataItemName.

options provides special instructions to the data provider. This may be null, or a space delimited list of option strings. The producer is not required to honor these requests, but if it does it should use the specified syntax. The producer is not required to honor these requests. When using the functions listed here, the producer should use the strings indicated for those functions. A producer can add other functions as needed.

The following option strings are predefined:

  • Option string meaning
  • "ArrayAccess" asks the producer to return an object which implements the ArrayAccess interface.
  • "Map" asks the producer to return an object which implements the Map interface
  • "RowsetAccess" asks the producer to return an object which implements the RowsetAccess interface.
  • "ScrollableRowsetAccess"
  • asks the producer to return an object which implements the ScrollableRowsetAccess interface.
  • "PreFetch=n" asks the producer to pre-fetch the specified number of rows, where n represents the number. 0 means none, -1 means all.
  • "RowLimit=n" asks the producer to fetch no more than the specified number of rows, where n represents the number. 0 means none (for cases where only the resulting column names and datatypes are desired.), -1 means all.


The method returns an item implementing RowsetAccess if the operation succeeded. If the operation fails, an SQLException is thrown.

public int executeCommand(String command, String dataItemName) throws SQLException

This method executes the specified non-retrieval query and returns the count of rows affected, or -1 if this is not applicable. command specifies a non-retrieval query, such as SQL INSERT, UPDATE, DELETE, or a stored procedure which does not return a result. If dataItemName is not null, it instructs the data provider to make the count of affected rows available as an ImmediateAccess data item. The method returns the number of rows affected. This can be 0 for INSERT, UPDATE, DELETE or equivalent, or -1 if this is not applicable (for commands which do not manipulate rows). If the operation failed, a SQLException is thrown (see java.sql.SQLException).

Transaction methods

By default, changes are implicitly committed when sent to the underlying data store.

Many database servers and intelligent middle tiers support grouping of modifications into transactions such that the group of changes is atomic (either all of them take effect or none of them take effect.) Such transactions are at the database level, since changes to multiple tables are allowed. Because the capabilities of databases vary, some of these methods are optional and may have no effect, as noted.

public void beginTransaction()

Do not commit changes when they are sent to the underlying data store. Begin explicit commit mode. The producer should throw UnsupportedOperationException if it does not support this method.

public void commitTransaction() throws SQLException, RowsetValidationException

Performs any database integrity and consistency checks on changes sent to the database since the last beginTransaction. If all checks pass, make the changes permanent in the database. Multiple tables may have been modified using multiple rowsets; all such changes are applied. Following the commit, resume implicit commit mode.

public void rollbackTransaction() throws SQLException, RowsetValidationException

Undo all changes sent to the database since beginTransaction. This may affect multiple rowsets. Resume implicit commit mode.

public void validate() throws SQLException, RowsetValidationException

If supported, performs explicit validation of all changes to the database since the last beginTransaction(), without committing them. Multiple tables may have been modified using multiple rowsets; all such changes are validated. Does nothing (and does not throw an exception) if not supported by the data producer.

public void flush() throws SQLException, RowsetValidationException

If supported, sends all changes made through any rowset to the database. Column, row, cross row, and some cross table integrity and consistency checks may be applied. Does nothing if not supported by the data producer.

5.7 The RowsetValidate interface

Producers may implement the RowsetValidate interface to provide a means of validating the contents of a Rowset data item. This interface is optional: producers can implement it or not as they choose; consumers may use it or not if it is present.

public void validateCurrentRow() throws RowsetValidationException

Explicitly validates data in the current row only.

Typically, the producer performs checks that can be done locally without involving underlying data store. For example, this method could check that the value in a column is one of the allowed values or is in a range of values.

public void validateRowset() throws RowsetValidationException

This method validates data in the current rowset, taken as a set of rows.

Typically, the producer performs checks that can be done locally without involving underlying data store. The checks may involve more than one row. For example, this method could check that the sum of one particular column in the current set of rows does not exceed a particular value.

5.8 Releasing database resources

Chapter 4 discusses the use of DataItem.release() to give producers a hint about when it can release a critical resource. When this method is called for database items, the producer must have a policy about how it will behave for items that have been modified but require commitTransaction to be called to apply the changes to the database.

We recommend that a producer treat release() as having an implied rollbackTransaction method call, so that changes are not applied except when explicitly committed by a consumer. If the producer decides to adopt a policy to commit instead, it must be clearly documented so that application designers can plan for this.

6. Monitoring changes to data items

After a consumer acquires a data item from a producer, it can begin to access the data by way of the various access interfaces discussed in the previous two chapters. A consumer may need to monitor changes to the data item, for example to cue it for updating its display. Change notifications are sent by the producer to registered consumers using an event-based scheme. This scheme is similar to but logically separate from the InfoBus events used for rendezvous about data items in order to reduce traffic on the InfoBus.

Four classes and interfaces are defined for the InfoBus mechanism for monitoring data item changes:

  • DataItemChangeManager - implemented by the producer on data items for which it is able to notify consumers of changes.
  • DataItemChangeSupport - an implementation of DataItemChangeManager that defines methods to manage listeners and fire events that can be used in applications.
  • DataItemChangeListener - implemented by a consumer then registered with the producer to receive notifications of changes.
  • DataItemChangeEvent - a base class for change events which are sent by the producer to registered consumers via their DataItemChangeListener objects.
In Figure 4-2 we saw a spreadsheet data producer that provided data items for a sheet collection of two cells, along with the relationship between the exposed data items and the internal data representation. In Figure 6-1 below, the same example is illustrated, this time showing the relationship between the producer's data items and the change listeners registered by a consumer application. The consumer is interested in changes occurring to the sheet as a whole, perhaps so it knows when to repaint a graph, as well as changes to one cell in particular, which may be displayed separately.

Figure 6-1 - Change managers and listeners in a producer and consumer

In a slightly more complex example, change listeners may be registered at various parts of a data item hierarchy by different consumers. To the producer, it looks no different; it does not distinguish which change listeners are provided by which consumer. Change events are "multi-cast" to those listeners that have attached themselves to the data item in no defined order as per section 6.6 of the Java Beans 1.0 specification.

Data producers should be tolerant of exceptions in listeners, and in general should ignore any exceptions thrown by a listener. In particular, an exception thrown by one listener should not prevent other listeners from receiving the event.

6.1 The DataItemChangeManager interface

This interface allows a data item to provide notifications to a consumer when the item has changed, by managing DataItemChangeListeners registered by the consumer for this purpose. The DataItemChangeManager interface is required for data items that implement the ReshapeableArrayAccess interface, and is recommended for all other data items. We recommend that data items provide change listener support at all levels of a collection hierarchy where it is possible, to offer maximum flexibility to data consumers. This allows the producer to choose the granularity of notifications it wants. In the example above, the consumer can look for changes on any individual cell, or on any in the spreadsheet as a whole.

Data items that implement DataItemChangeManager must support registration and deregistration of event listeners as per section 6.5.1 of the JavaBeans 1.0 specification. Specifically, changes to the listener list may take place during notification of all listeners. Accordingly, the listener list should be copied at the beginning of a change notification, and the copy of the list used for the duration of the notification.

When data changes (whether by the producer for its own reasons, or on behalf of a change from any consumer, or in the producer's data source), the producer notifies registered listeners, as described in "Distribution and handling of change events".

public void addDataItemChangeListener(DataItemChangeListener listener)

public void removeDataItemChangeListener(DataItemChangeListener listener)

These methods form the standard JavaBeans design pattern for an object that emits a DataItemChangeEvent. A data consumer interested in continuing updates on a data item will call addDataItemChangeListener() to express that interest, and removeDataItemChangeListener() when it no longer needs the notifications.

6.2 The DataItemChangeManagerSupport class

This class implements DataItemChangeManager, and can be used by a producer for any data item. You can use an instance of this class as a member field of your data item and delegate various work to it. Aside from providing the methods to manage listeners, it also has methods for firing each kind of event.

Constructor

public DataItemChangeSupport( Object source )

The constructor requires references to the data item object that implements the DataItemChangeManager interface. When an instance of this class is a member field for a data item, the source is a reference to the outer class that contains the instance of this class.

Listener management

public void addDataItemChangeListener(DataItemChangeListener listener)

public void removeDataItemChangeListener(DataItemChangeListener listener)

These methods add and remove change listeners as requested by a consumer or other InfoBus component.

public void removeAllListeners()

This method can be used by the producer to remove all listeners that may still be associated with the change manager. It should be called only after announcing that the item is being revoked both by way of InfoBus.fireItemRevoked and DataItemChangeSupport.fireItemRevoked.

Event-firing methods

Each method below creates an appropriate change event and sends it to all the listeners at that level only. Events should also be distributed to other levels according to the rules specified in "Distribution and handling of change events," later in this chapter.

public void fireItemValueChanged(Object changedItem,
InfoBusPropertyMap propertyMap)

This method should be called when an item, usually an ImmediateAccess, changes value. The caller indicates the changedItem as the one whose value changed. Producers that wish to supply additional information about the change may do so by supplying a propertyMap; producers that do not offer this information should supply null for this parameter.

public void fireItemAdded(Object changedItem, Object changedCollection,
InfoBusPropertyMap propertyMap)

This method should be called when one or more new items are being added to a collection. The caller indicates the changedItem as the one being added, and changedCollection as the collection that gained an item. changedItem can be null when more than one item is added in the same operation. Producers that wish to supply additional information about the change may do so by supplying a propertyMap; producers that do not offer this information should supply null for this parameter.

public void fireItemDeleted(Object changedItem, Object changedCollection,
InfoBusPropertyMap propertyMap)

This method should be called when one or more items are being removed from a collection. The caller indicates the changedItem as the one being removed, and changedCollection as the collection that lost an item. changedItem can be null when more than one item is removed in the same operation. Producers that wish to supply additional information about the change may do so by supplying a propertyMap; producers that do not offer this information should supply null for this parameter.

public void fireItemRevoked(Object changedItem, InfoBusPropertyMap propertyMap)

This method should be called when an item or collection is no longer available, such as when the data source is going offline. The caller indicates the changedItem as the item or collection that is being revoked. Unlike the other events, this event is sent to the data item passed during rendezvous, and to all sub-items in a collection hierarchy. Producers that wish to supply additional information about the change may do so by supplying a propertyMap; producers that do not offer this information should supply null for this parameter.

public void fireRowsetCursorMoved(Object changedItem,
InfoBusPropertyMap propertyMap)

This method must only be called for a RowsetAccess object when the rowset's cursor has moved to a different row. The caller indicates the rowset whose cursor changed. Producers that wish to supply additional information about the change may do so by supplying a propertyMap; producers that do not offer this information should supply null for this parameter.

public void fireItemShapeChanged(Object changedItem,
InfoBusPropertyMap propertyMap)

This method must be called when a data item changes shape. For example, a DataItemShapeChangedEvent should be fired when any of the extents of a ReshapeableArrayAccess, as reported by getDimensions(), change, for example as a result of calling setDimensions(), insert(), or delete(). This method must only be called for data items that implement the ReshapeableArrayAccess interface.

The caller specifies the object that is changing dimensions in changedItem, which is always a reference to a ReshapeableArrayAccess object. Producers that wish to supply additional information about the change may do so by supplying a propertyMap; producers that do not offer this information should supply null for this parameter.

6.4 The DataItemShapeChangeListener interface

This interface extends the DataItemChangeListener interface to add a new change event delivery method.

public void dataItemShapeChanged(DataItemShapeChangedEvent event)

Indicates a change in the shape for a data item. For example, a ReshapeableArrayAccess has been changed such that one or more of its dimensions has changed. A reference to the data item that changed can be obtained from the event.

6.5 The DataItemChangeListenerSupport class

This class implements DataItemChangeListener and DataItemShapeChangeListener, and can be used by a consumer as a base class for implementing a change listener class. All methods in this class have empty-body implementations. To use this class, the consumer should subclass it and override methods for handling events of interest.

public void dataItemValueChanged(DataItemValueChangedEvent event)

Default handler for the DataItemValueChangedEvent, which simply ignores the event. If the event is of interest, the consumer should override this method with one that handles the event.

public void dataItemAdded(DataItemAddedEvent event)

Default handler for the DataItemValueAddedEvent, which simply ignores the event. If the event is of interest, the consumer should override this method with one that handles the event.

public void dataItemDeleted(DataItemDeletedEvent event)

Default handler for the DataItemValueDeletedEvent, which simply ignores the event. If the event is of interest, the consumer should override this method with one that handles the event.

public void dataItemRevoked(DataItemRevokedEvent event)

Default handler for the DataItemRevokedEvent, which simply ignores the event. If the event is of interest, the consumer should override this method with one that handles the event.

public void rowsetCursorMoved(RowsetCursorMovedEvent event)

Default handler for the RowsetCursorMovedEvent, which simply ignores the event. If the event is of interest, the consumer should override this method with one that handles the event. This method will only be called for data items that implement the RowsetAccess interface.

public void dataItemShapeChanged(DataItemShapeChangedEvent event)

Default handler for the DataItemShapeChangedEvent, which simply ignores the event. If the event is of interest, the consumer should override this method with one that handles the event. This method will only be called for data items that implement the ReshapeableArrayAccess interface.

6.6 The DataItemChangeEvent class and event subclasses

DataItemChangeEvent is the base class of all other events described in this chapter. A data item fires a DataItemChangeEvent to all registered DataItemChangeListeners to inidicate that a change has occurred. The nature of the change is indicated by the name of each subclass. The easiest way to fire change events is use DataItemChangeSupport, which has a fire method for each type of event.

The listener handles change events by implementing DataItemChangeListener.dataItemChange(). It uses the instanceof operator to distinguish the various change events, and handles or ignores them as required.

The event includes a reference to the data item whose data changed and a reference to the item that manages the listener to which the notification is sent. Note that because the source of the change can only be set in the constructor, a separate event instance must be created for each change manager implementation in a multi-level collection hierarchy. This is intentional, for security reasons.

Figure 6-2: Class Hierarchy for DataItemChangeEvents

DataItemChangeEvent base class

DataItemChangeEvent(Object source, Object changedItem,
InfoBusPropertyMap propertyMap)

DataItemChangeEvent(Object source, Object changedItem, java.util.Map propertyMap)

Constructs a DataItemChangeEvent, indicating the item that manages a listener (source), the data item that changed, and an Map object that can be used for looking up the values for properties in getProperty(). propertyMap is optional, and null can be used when getProperty() is not supported.

The DataItemChangeEvent constructor is package access so that only the events defined by the InfoBus spec can be sent.

The first form of the constructor, which accepts an InfoBusPropertyMap for the propertyMap parameter, will be deprecated in InfoBus 2.0. The second form of the constructor, which accepts a java.util.Map for the propertyMap parameter, is not present in InfoBus 1.1; this method will be added for InfoBus 2.0.

public Object getSource()

This method returns a reference to the source data item to which the change event listener is registered. Note that this method is defined in java.awt.Event, rather than DataItemChangeEvent, which extends it. When a DataItemChangeSupport class is used to fire events, the source is the same as the reference passed to its constructor.

public Object getChangedItem()

This method returns a reference to the data item that changed.

For collection size change and item value change events, this can be the same as the reference returned by getSource(), meaning the item that changed is also the one that had the registered change listener. When it is not null and is different from getSource(), it refers to a sub-item of the one that had the change listener. Finally, it can be null, meaning that more than one sub-item has changed in the same operation (for example, delete row).

For the revoked change event, getChangedItem() returns a reference to the top-level rendezvous item that is being revoked. The recipient of a revoked change event might not recognize the top-level item under some circumstances - for example, if a lower-level item was published in two overlapping collections. In these cases, the recipient is still obliged to treat the data item identified by getSource() as a revoked item.

For a rowset cursor moved event, getChangedItem() returns a reference to the RowsetAccess data item whose cursor was moved.

public Object getProperty(String propertyName)

Returns a property or metadata information about the change event. For example, properties can provide information about the nature of sub-items that changed when getChangedItem() returns null (which indicates that more than one sub-item changed).

Support for properties is optional; if the DataItemChangeEvent constructor received null as the map reference, getProperty() returns null. If a reference to an implementation of InfoBusPropertyMap was supplied in the change event constructor, this method calls InfoBusPropertyMap.get() with the specified key and returns the result. null is the conventional return value when the specified key is not supported as a property name.

DataItemValueChangedEvent, DataItemRevokedEvent, DataItemShapeChangedEvent and RowsetCursorMovedEvent classes

These events extend DataItemChangeEvent, overriding the constructor with a public constructor, but add no other methods or data. They also have a public constructor so they can be created using any implementation of DataItemChangeManager. Except for having a public constructor, the API is the same as for DataItemChangeEvent.

DataItemAddedEvent and DataItemDeletedEvent classes

These classes extend DataItemChangeEvent to modify the constructor and add a method that indicates the collection associated with the added or removed item, as well as a public constructor method.

In addition to the methods described below, the getSource() and getChangedItem() methods from DataItemChangeEvent are also available in these events.

public DataItemAddedEvent(Object source, Object changedItem,
Object changedCollection, InfoBusPropertyMap propertyMap)

public DataItemDeletedEvent(Object source, Object changedItem,
Object changedCollection, InfoBusPropertyMap propertyMap)

public DataItemAddedEvent(Object source, Object changedItem,
Object changedCollection, java.util.Map propertyMap)

public DataItemDeletedEvent(Object source, Object changedItem,
Object changedCollection, java.util.Map propertyMap)

Constructs an event, indicating the source as the data item that sent the event, the item that was added or removed, the collection that changed, and an Map object that can be used for looking up the values for properties in getProperty(). propertyMap is optional, and null can be used when getProperty() is not supported.

The first form of the constructors, which accept an InfoBusPropertyMap for the propertyMap parameter, will be deprecated in InfoBus 2.0. The second form of the constructors, which accept a java.util.Map for the propertyMap parameter, are not present in InfoBus 1.1; these methods will be added for InfoBus 2.0.

public Object getChangedCollection()

This method returns a reference to the collection data item that lost or gained sub-items. For DataItemDeletedEvent, can return null when the item deleted was a singleton item (i.e., not a member of a collection).

6.7 Distribution and handling of DataItemChangeEvents

This section describes the event classes defined for InfoBus data item change events, when they should be fired, and how consumers should respond to them.

The event classes are subclasses of DataItemChangeEvent. An event specifies a source data item, which is the item containing the DataItemChangeManager, and the data item that changed. If the reference to the item that changed is null, it means that more than one item was changed in the operation, for example by deleting a column of cells or filling a range of cells. Most events are empty subclasses of DataItemChangeEvent. DataItemAddedEvent and DataItemDeletedEvent modify the parent class to offer a reference to the collection that gained or lost items.

Most data items offered at the rendezvous are collections of other data items. A complex data item can have several levels to the collection hierarchy. When data items change, change events are distributed up or down the collection hierarchy, depending on the type of change that occurred, as listed below. When a change occurs, the producer must fire an event to all registered change listeners according to the rules described in this section, in any order that is convenient to the producer. When the rules indicate that a change notification should be sent to a given data item's listener, it means that the event should be sent if the DataItemChangeManager is implemented and has registered listeners.

The producer indicates the levels of the hierarchy for which it is willing to provide change notification service by implementing the DataItemChangeManager interface at these levels. Although implementations of DataItemChangeManager are not required, for maximum flexibility for consumer applications, we recommend an implementation of the interface at all levels where it makes sense. DataItemChangeSupport implements DataItemChangeManager, and provides methods to fire each type of change event when listeners are present.

Consumers can register a change listener at any level where a DataItemChangeManager is present. Listeners determine the nature of a data item change on the basis of the class type of the event they receive. The listener can use instanceof or attempt a cast for this purpose. Events can be handled or ignored according to the needs of the consumer, except for DataItemDeletedEvent and DataItemRevokedEvent, which require the consumer to release references and cease making calls to the data item that changed.

In this section we refer to the "rendezvous item" as the data item provided to a consumer that requests the event, which is often a collection of other data items. Data items that are members of a collection data item are referred to as "sub-items." "Parent items" refer to any container item in the hierarchy (ArrayAccess, RowsetAccess, or one of the JDK Collections interfaces) which is the parent of a given sub-item.

DataItemValueChangedEvent

An item is said to change value when calling a method to get its value returns a different result than before it changed. This can result when the underlying data itself has changed, such as when a method is called to change the value. Some items are treated as having a current value, for example the current row of a RowsetAccess item; the value is also said to change when the rowset cursor moves to a record that has a different value for a given column.

When an item changes value, a change notification event should be sent for that item, then for its parent collection, and so on up to and including the rendezvous item. Note that an item can belong to more than one collection, and the collections could have a common ancestor; in this case care must be taken to avoid a redundant item value change notification to the common ancestor. Items that can change value are those that have an immediate value, and are generally not collections. getChangedItem() returns a reference to the item that changed, or null to indicate more than one item changed in the same operation.

DataItemAddedEvent

When one or more items are added to a collection item, DataItemAddedEvent should be sent for each added item and for the parents of the items up to and including the rendezvous point. getChangedItem() returns a reference to the item that was added, or null to indicate more than one item was added in the same operation. getChangedCollection() returns a reference to the collection that gained item(s).

DataItemDeletedEvent

When one or more items are permanently deleted from a collection, an event should be sent for each item and for the parents of the items up to and including the rendezvous point. When a container data item is being deleted, sub-items should fire DataItemDeletedEvent with the same rules. This event can also be fired with a singleton data item (i.e., one that is not a sub-item of a container item) is being deleted. getChangedItem() returns a reference to the item, or null to indicate more than one items were added in the same operation. getChangedCollection() returns a reference to the collection that lost the item(s) or null if it was a singleton item.

Operations on a ReshapeableArrayAccess data item that reduce the number of cells in the array also result in firing DataItemDeletedEvent for those cells that are not empty. For example, calling setDimensions() when one of the new dimensions is smaller than the corresponding existing dimension will cause the removal of cells; calling the delete() method will generally result in the removal of cells as well. For cells being removed that are not empty (i.e. calling getItemByCoordinate() would return a non-null reference to a data item), the DataItemChangeEvent should be fired for each, indicating the reference to the data item that is being deleted; alternatively, one DataItemChangeEvent can be fired with null as the item that is being deleted, meaning that more than one non-empty cell is being removed. These events must be fired before firing the DataItemShapeChangeEvent.

The consumer must cease making calls on this item, release any reference to it, and call DataItem.release() on the item.

DataItemRevokedEvent

Sent when an item is being revoked by its producer, i.e. it is no longer available from the producer, for example because the producer's data source is going away. The event should be sent to all listeners for the item and, if it is a container item, to all listeners of any sub-items in the containment hierarchy. This event can also be fired with a singleton data item (i.e., one that is not a sub-item of a container item) is being revoked. This event differs from DataItemDeletedEvent in that it the item may be available in the future.

The consumer must cease making calls on this item, release any reference to it, and call DataItem.release() on the item.

DataItemShapeChangedEvent

Indicates that the one or more of the dimensions of a ReshapeableArrayAccess data item has changed. The event must be sent to listeners of the ReshapeableArrayAccess item whose shape has changed, with changedItem referring to the ReshapeableArrayAccess.

Note that when changes to the shape of this data item results in the removal of one or more non-empty cells, the DataItemDeletedEvent must be fired first, indicating the removal of the cells, before sending DataItemShapeChangedEvent. See the subsection "DataItemDeletedEvent" above for more details.

RowsetCursorMovedEvent

Indicates that the cursor for a RowsetAccess data item has moved to a different row. The event should be sent to listeners of the RowsetAccess whose cursor changed.

6.8 Examples of event propagation

Consider Figure 6-3, which is an example of a multi-level data item shown with a few sub-items. Note that `Subset' contains some of the same items as `All'. `Top-level' is the rendezvous point.

Figure 6-3: A data item hierarchy example with an item in more than one collection in the hierarchy

The following list looks at various changes that are possible in this hierarchy and the way events should be distributed to notify listeners. When we say notification should occur at a given level, it means that it should occur of the item at that level implements DataItemChangeManager and has at least one listener, and that all listeners at that level receive the event. Whenever an event is sent to a listener of some data item, the source is a reference to the data item that manages the listener list (is a DataItemChangeManager).

  • If item `3' changes value, fire a DataItemValueChangedEvent specifying `3' as the item that changed on listeners of `3', `Subset', `All', and `Top-level'. The listeners of `Top-level' should be notified exactly once.
  • If item `3' is deleted, fire a DataItemDeletedEvent specifying `3' as the item that changed and `Subset' as the collection that changed on listeners of `3', `Subset', and `Top-level'. Next, fire the event specifying `3' as the item that changed and `All' as the collection that changed on listeners of `3', `All', and `Top-level'. If instead `3' is removed from `Subset' but still remains in `All', only the first set of notifications are sent, because `All' didn't change.
  • If items `3' and `4' are being deleted in one operation, fire a DataItemDeletedEvent specifying each item as the item that changed on the listeners of each item, then fire the event specifying null as the item that changed on listeners of `All', `Subset', and `Top-level'. Instead of sending null, notifications could be sent for each of items `3' and `4' to the parent collections.
  • If item `2' is added to the `Subset' collection, fire DataItemAddedEvent specifying `2' as the item that changed and `Subset' as the collection that changed on listeners of `2', `Subset' and `Top-level'.
  • If `Top-level' is being revoked, the producer calls InfoBus.fireRevokedEvent() for the item, indicating `Top-level' as the one that changed, then it fires DataItemRevokedEvent on listeners of all sub-items, each indicating the sub-item as the one that changed. This continues until all sub-items of `Top-level' at all levels are notified.
  • Suppose `All' is a ReshapeableArrayAccess. If the delete() or setDimensions() methods are called such that existing, non-empty cells will be removed, one DataItemDeletedEvent must be fired for each non-empty cell, indicating a reference to the cell that was deleted and the ReshapeableArrayAccess item that contained it, followed by a DataItemShapeChangeEvent sent to listeners of the ReshapeableArrayAccess. Alternatively, for convenience, it is permissible to fire one DataItemDeletedEvent with null as the reference to the cell being deleted (meaning more than one non-empty cell was deleted), followed by a DataItemShapeChangeEvent sent to listeners of the ReshapeableArrayAccess. In both cases, the rules for propogating the DataItemDeletedEvent are the same as those described for that event, above. The ReshapeableArrayAccess event must be propogated upward to the parent of the array that changed, and its parent, up to and including the top-level node of the data item.
  • For a ReshapeableArrayAccess whose dimensions change such that empty cells are being added, the DataItemShapeChangedEvent should be sent to listeners of the array, to its parent, up to and including the top-level node of the data item.
  • For a ReshapeableArrayAccess where more than one dimension is changing as a result of setDimensions(), it is possible that cells are being removed from one dimension while being added in another. In this case, only one ReshapeableArrayAccess is sent per call to setDimensions() to each level of the data item hierarchy from the array up to the top-level node of the data item.
  • Suppose `All' is a RowsetAccess. When its cursor changes, fire RowsetCursorChangedEvent on listeners of `All'. Then, for each column item that would return a different value as a result of the new cursor position, fire DataItemValueChanged on listeners of the columns whose value changed relative to the previous row.

6.9 The temporary InfoBusPropertyMap interface

InfoBusPropertyMap is a temporary interface designed to provide a mechanism for use with InfoBus 1.1 components that wish to supply properties on DataItemChangeEvents. To use it, the producer implements the interface and provides a reference to the implementation class in the change event constructor.

When implementing this interface for InfoBus 1.1, an implementation of sun.com.java.util.collections.Map, such as HashMap may be used, delegating the get() method to the class.

In InfoBus 2.0, this class and its method will be deprecated. Constructors that use it will also be deprecated, and new constructors will be introduced that specify java.util.Map instead. Support for applications compiled with InfoBus 1.1 will be present by removing the get() method from InfoBusPropertyMap and adding a clause to have it extend java.util.Map.

public abstract Object get(Object key)

Returns the Object to which the specified key is mapped. Returns null if the map contains no mapping for this key OR it maps to null. key must be a String, otherwise ClassCastException should be thrown. Property names should not contain the `*' character.

7. Data controllers

7.1 Overview

A data controller is an object that implements the InfoBusDataController interface and participates in the distribution of InfoBusEvents to consumers and producers on an InfoBus. Generally, the data controller is a pluggable module that gets added to an InfoBus in order to optimize some aspect of the communication on the bus.

The InfoBus supports multiple data controllers on one bus. When consumers on an InfoBus make requests for data or when producers make requests that Available or Revoked notices be sent, the InfoBus passes the request on to its registered data controllers by polling each in turn. Any controller polled can indicate to the InfoBus that the request should not be processed further by returning true from the method used to pass in the request. In this case the InfoBus will not poll remaining controllers, and returns the results, if any, to the requester of the action. If no data controller indicates an event is processed, or if no controllers are installed, the event is handled by the InfoBus default controller. The order in which the data controllers receive a request can be partially determined by a controller priority that is specified when the controllers are added to the bus. The default controller always has the lowest priority so that it is always last.

A data controller is not directly involved with data item method calls, nor is it involved with data item change events. This interaction is directly between producers and consumers, and the InfoBus does not intervene. However, a data controller could intervene on data item methods by keeping a producer's item and supplying a proxy in its place to the consumer.

7.2 Data controller examples

The InfoBusDataController interface is very powerful and allows a controller to provide a range of functions that are not included in the basic services already supplied by an InfoBus. The following are some of the possible optimizations that a custom controller might perform. A single controller might implement one or many of the functions here (or others we haven't thought of).

The simple priority router. The data controller identifies a subset of the total producer and consumer population, perhaps by inspecting the package to which each belongs, or by detecting the presence of an identifying interface. Messages which originate in this subset are routed first within the subset. For example, a findDataItem() sent by a consumer in the subset will first query producers in the subset.

The late-binding controller. The data controller maintains tables of data item names along with the producers who announced them and/or the consumers that requested them. When a new findDataItem() is issued, the controller first (or perhaps only) queries producers in the table already associated with that name; likewise for a fireRevoked(). Variants on this theme might send the request to all producers if the name is not tabled.

Wildcard support. Similar to late-binding, the controller keeps tables of item names and participants, but supports wildcard use in matching requests to previously announced items.

The voting controller. The data controller receives a findDataItem() request, but queries multiple producers even after a response is received, then singles out the best answer for its return result. This concept is probably strongest when a subset of voting-aware beans has been isolated. A variant would use a custom interface on producers which could attach a priority or certainty to the results, allowing a method of identifying the best response.

VM bridge. A data controller can be written to propagate InfoBus events, data items, and change events to a partner controller in a different VM, using RMI or other network transport. Such a bridge could be used to provide access to remote processes through the InfoBus.

InfoBus traffic monitor. The controller is set to MONITOR_PRIORITY so that it sees events ahead of other controllers. It monitors InfoBus activity and displays information or writes it to a log for debugging purposes.

7.3 The DefaultController

Each InfoBus instance always contains a DefaultController which provides standard one-to-all distribution, where a consumer's request is sent to all producers and a producer's announcements are sent to all consumers. In the absence of any custom controllers, the presence of the DefaultController insures a basic, unoptimized level of operation. The DefaultController has the lowest possible priority and is therefore always the last controller on the request distribution list. If none of the data controllers ahead of the DefaultController indicate that the request has been handled, the DefaultController will get the request and process it.

7.4 InfoBus methods supporting data controllers

Adding and removing data controllers

The following methods, defined in the InfoBus class, allow a data controller to be added to or removed from an InfoBus.

public synchronized void addDataController(InfoBusDataController controller,
int priority) throws InfoBusMembershipException

This method registers the indicated controller with the InfoBus, causing it to be added to the list of registered controllers by the indicated priority.

The priority parameter denotes roughly where in the list of controllers the new controller should be placed, and is described in detail in the next section. Once added, a data controller will remain at that priority level until removed. Any unique data controller object may only appear once in the list of controllers on a single InfoBus.

Calling the addDataController() method with a controller that is already present on the bus will cause an InfoBusMembershipException to be thrown. Calling this method on a stale InfoBus instance will cause StaleInfoBusException to be thrown.

public synchronized void removeDataController(InfoBusDataController controller)

The remove method will remove the specified controller from the InfoBus. Calling removeDataController() with a controller that is not currently listed on the InfoBus has no effect. An InfoBus instance will not remove itself from the virtual machine's set of active controllers if there are any data controllers (or InfoBusMembers, producers, or consumers) still registered on the bus. Therefore, applications which have registered a controller with one of their InfoBuses should remove the controller as they exit, just as they remove their member, producer, and consumer objects.

Firing events from data controllers

Creation and delivery of InfoBusEvents is handled exclusively by the InfoBus class. In order to permit an added data controller to optimize the distribution of events on a bus, the InfoBus provides a set of target-specific event delivery methods, which are described in Chapter 3; see "The InfoBus class: firing events."

The target-specific event firing methods on the InfoBus are versions of the findDataItem() and fireItemAvailable/Revoked() methods that specify a single target or a list of specific targets to which the method should be applied. A data controller may call any combination of these methods when processing a request, but should avoid sending multiple events to the same target for efficiency's sake.

The data controller must call only the target-specific versions of the methods on the InfoBus. The methods that do not specify a target or targets are for the exclusive use of producers and consumers, and are handled by calling the data controllers. Calling them from a data controller will cause a loop where the request is again distributed to the data controllers for processing.

7.5 Composition of a data controller

A data controller must implement the InfoBusDataController interface to register with an InfoBus. There are no additional requirements on data controllers in terms of other InfoBus interfaces. That is, a data controller is free to implement InfoBusMember, InfoBusDataProducer, InfoBusDataConsumer, or any combination of these interfaces, but is not required to implement any of them.

For example, one type of controller might be an independent bean which gets and joins an InfoBus of its own accord, in which case implementing InfoBusMember is appropriate (it is a requirement of joining).

Another type of controller is one that is instantiated and controlled by other objects in a larger application -- perhaps a primary producer or consumer in that application. In this case, the controller may only implement the InfoBusDataController interface; since another object is obtaining and setting the InfoBus in this controller, the controller need not even be an InfoBusMember. Again, it is critical in either case that the controller be properly removed from the bus when other participants are being removed and terminated, so that the InfoBus itself can be freed for garbage collection.

7.6 Data controller priority levels

The priority specified when adding a controller is used to determine the insertion order in a linked list of established controllers. This list is traversed from the beginning to the end when processing events, so the priority also determines the order in which a controller will be given a chance to handle events. Because a controller can indicate that an event has been handled, and should not be passed on to other controllers for handling, a lower-priority controller may not see all of the events that a higher-priority controller handles.

The InfoBus class declares six priority levels, with higher integers indicating higher priority, and higher priority controllers receiving requests before lower priority ones. The order of delivery among controllers having the same priority is unspecified. The InfoBus defines the following constants for priority values:

  • InfoBus.MONITOR_PRIORITY
  • InfoBus.VERY_HIGH_PRIORITY
  • InfoBus.HIGH_PRIORITY
  • InfoBus.MEDIUM_PRIORITY
  • InfoBus.LOW_PRIORITY
  • InfoBus.VERY_LOW_PRIORITY
  • InfoBus.DEFAULT_CONTROLLER_PRIORITY (reserved for javax.infobus.DefaultController)
The MONITOR_PRIORITY is reserved for data controller objects that need to be aware of all requests that arrive at the InfoBus, and is therefore the highest available priority. However, data controllers that assert MONITOR_PRIORITY are expected to be monitor processes, and not actively participate in event distribution. To enforce this concept, values returned by data controllers having MONITOR_PRIORITY are ignored, and the requests proceed to the data controllers with non-monitor status regardless of whether any such monitors exist.

If a priority is specified during addDataController() that is higher than VERY_HIGH_PRIORITY but not equal to MONITOR_PRIORITY, the object will be treated as having VERY_HIGH_PRIORITY because of the special restriction on MONITOR level controllers.

The DEFAULT_CONTROLLER_PRIORITY is the lowest possible priority, but is reserved for the javax.infobus.DefaultController that is always present in an InfoBus, to insure that DefaultController always handles a request that has not been completed by a previous controller. VERY_LOW_PRIORITY is therefore the lowest generally-available priority level. If a controller is added with a priority value lower than VERY_LOW_PRIORITY, it will have that value adjusted to VERY_LOW_PRIORITY.

7.7 Maintaining producer and consumer lists in a data controller

Each data controller on the InfoBus maintains its own lists of producers and consumers that it wishes to serve, which may include all or a subset of the members on the bus. When the InfoBus passes a request to a data controller, the controller decides whether the request applies to the members on its private lists. If so, it calls one of the target-specific event firing methods on InfoBus to initiate delivery of the appropriate event to the members of its choosing. Conversely, if the request does not apply to the members handled by this data controller , the controller simply returns.

The InfoBus provides a copy of its master lists when a data controller first joins the bus, and then updates its data controllers on changes to the master lists. Because data controllers may only concern themselves with a subset of all participants, some additions to the InfoBus may not be reflected in the local list held by a data controller. However, when an InfoBus producer or consumer indicates that it is leaving the bus, all data controllers which included that participant locally are obliged to remove it from their distribution lists.

Data controllers that handle only a subset of producers and consumers may make such a determination when an add method is called. Note, though, that there is no mechanism for a data controller to re-request the entire master list from its InfoBus - if a controller is not interested in including a new participant on its private list immediately but might in the future, it is the responsibility of the controller to remember that participant.

public void setConsumerList(Vector consumers)

public void setProducerList(Vector producers)

These two data controller methods are called on by the InfoBus to which the controller has been added at the time the controller is joining the bus. This is done in order that the data controller can discover what producers and consumers were on the bus already when it joined.

public void addDataConsumer(InfoBusDataConsumer consumer)

public void addDataProducer(InfoBusDataProducer producer)

public void removeDataConsumer(InfoBusDataConsumer consumer)

public void removeDataProducer(InfoBusDataProducer producer)

These four methods are called by the InfoBus on each registered data controller when the InfoBus methods of the same names have been called to add a producer or consumer to the bus. This allows the controllers to make the appropriate adjustments to their lists.

Data controllers may receive add or remove notifications while in the process of handling a request; for example, delivery of an InfoBusItemRevokedEvent to one consumer may cause that consumer and possibly others to remove themselves from the bus. Data controllers must take care not to accidentally repeat delivery or, worse, skip delivery of events to some participants in such situations.

7.8 Data controller event distribution

"The InfoBus class: firing events" in Chapter 3 defines a set of methods in the InfoBus class that fire events for use by producer and consumer components. The events fired by consumers and producers are generally handled by the creation and distribution of appropriate events; for example, when a producer calls fireItemRevoked it expects an InfoBusItemRevokedEvent to be created and sent to the consumers on the bus.

When an InfoBus receives a request from a consumer or producer, it passes the request to its highest priority data controller. The data controller, in turn, decides whether the request applies to the members on its private lists. If so, it calls the target-specific event firing methods on InfoBus to initiate delivery of the appropriate event to the members of its choosing, possibly collecting returned results. Conversely, if the request does not apply to the members handled by this data controller, the controller simply returns.

The methods used to pass the requests to the data controller have a boolean return value which indicates whether the InfoBus should stop or continue processing the request. A return value of true indicates that all processing of this request is complete and that no further data controllers should be consulted; false indicates that the processing should continue with the next available controller.

public boolean fireItemAvailable(String dataItemName, DataFlavor[] flavors,
InfoBusDataProducer producer)

This method is called by an InfoBus to pass a producer's request for an ItemAvailable broadcast. A data controller can distribute an InfoBusItemAvailableEvent to any of its consumers by calling the target-specific versions of fireItemAvailable() on the InfoBus. The value of the source parameter from the calling of the data controller's method should be copied to all target-specific calls, to preserve the identity of the original requester.

The return value indicates whether processing is complete: if true, no other data controllers are called regarding this request.

public boolean fireItemRevoked(String dataItemName, InfoBusDataProducer producer)

This method is called by an InfoBus to pass a producer's request for an ItemRevoked broadcast. A data controller can distribute an InfoBusItemRevokedEvent to any of its consumers by calling the target-specific versions of fireItemRevoked() on the InfoBus. The value of the source parameter from the calling of the data controller's method should be copied to all target-specific calls, to preserve the identity of the original requester.

The return value indicates whether processing is complete: if true, no other data controllers are called regarding this request.

public boolean findDataItem(String dataItemName, DataFlavor[] flavors, InfoBusDataConsumer consumer, Vector foundItem)

This method is called by an InfoBus to pass a consumer's request for the named data item. A data controller uses the InfoBus's target-specific versions of findDataItem() to query any of its producers. The value of the consumer parameter from the calling of the data controller's method should be copied to all target-specific calls, to preserve the identity of the original requester.

The foundItem Vector is passed by the InfoBus as a location for storing a response if one is found. If foundItem is not empty when the call completes, the element at 0 in the Vector is taken as the result and passed by the InfoBus back to the consumer. In this case, the boolean return value is ignored and no other controllers receive the request. If the foundItem Vector is empty after the method completes, the return value indicates whether processing is complete: if true, no other data controllers are called regarding this request, and null is passed to the requesting consumer.

public boolean findMultipleDataItems(String dataItemName, DataFlavor[] flavors,
InfoBusDataConsumer consumer, Vector foundItems)

This method is called by an InfoBus to pass a consumer's request for the named data item. A data controller uses the InfoBus's target-specific versions of findDataItem() to query any or all producers it is managing. The value of the consumer parameter from the calling of the data controller's method should be copied to all target-specific calls, to preserve the identity of the original requester.

The foundItem Vector is passed by the InfoBus as a location for storing responses if found. If foundItem is not empty when the call completes, the elements in the Vector are concatenated by the InfoBus with results from other controllers polled (with elimination of duplicate occurrences of an object). The return value indicates whether processing is complete: if true, no other data controllers are called regarding this request.

Although a consumer's findMultipleDataItems() request is sent to data controllers, it should only be handled in special cases. The desired behavior of a findMultipleDataItems() is that each producer on the bus be queried exactly once for the named data, and the collection of all responses is returned in foundItems. This behavior is exactly that performed by the DefaultController, and therefore custom data controllers should usually simply defer to the DefaultController for handling the find-multiple case, by returning by returning false and leaving foundItems empty.

In situations where a custom controller decides to handle findMultipleDataItems(), there are some special considerations.

  • The single-target version of findDataItem() should be used to query each producer being managed in turn - the Vector version will stop on the first response and is therefore unsuitable for gathering multiple response data.
  • Results returned by the data controllers are concatenated by the InfoBus. The InfoBus will remove redundant responses by eliminating duplicate objects from the concatenated array; however, producers that are queried more than once may return different response objects (based, for example, on the security clearances on inquiring classes, which will include the controllers themselves).
In short, the two safest ways to handle a findMultipleDataItems() within a data controller are to either do nothing (rely on the DefaultController) or, conversely, to query all producers on the bus and then return true to stop further processing. Firing an event to a component then returning false to allow handling by other controllers will always result in an event being fired more than once to the same component, and should be avoided.

7.9 Example of event distribution process

Figure 7-1 illustrates the handling of a consumer's request event in a system with four established controllers and five producers. In the diagram, the controllers were added as follows:
  • The event monitor controller was added as MONITOR_PRIORITY. As the highest priority controller, it sees all events. Because it is passive and does not send events, it does not maintain lists of producers or consumers.
  • Controllers 1 and 2 were added at HIGH_PRIORITY and MEDIUM_PRIORITY respectively. For this example these controllers only manage requests to producers and so do not have lists of consumers, although they could, if they wanted, manage announcements to consumers as well. Controller 1 decided that it would manage producers V and W. Controller 2 decided that it would manage producers X, Y, and Z.
  • The default controller was created with DEFAULT_CONTROLLER_PRIORITY so that it is always the last controller to handle events. It is created by the InfoBus instance, and has package-level access to the InfoBus's producer list, so it does not keep its own copy. It implements the default rules (all producers see all requests, all consumers see all available / revoke announcements) when none of the other controllers indicate that an event has been handled. Because no other controller handles available and revoke announcements, the default controller handles these events.
Also, the Vector used for the producer list has entries for each of the producers V through Z, but the lines are not drawn in order to keep the diagram readable.

Figure 7-1: Event distribution example illustrating multiple controllers

Suppose Consumer A requests an item called "Sales." It does this by calling the InfoBus method findDataItem(). The InfoBus handles the request by calling each controller in order of priority.

The InfoBus calls findDataItem() on each controller in turn until one says the event has been handled. The monitor controller logs the event. Controller 1 asks producers V and W by calling findDataItem() with its Vector as the list of targets. In this example neither V nor W can supply a "Sales" item, so Controller 1 puts nothing in the foundItem Vector and returns false. The InfoBus next calls Controller 2, which calls producer X. X is able to supply the item, so Controller 2 stores the item reference in the foundItem Vector, does not need to call its other producers, and returns true. The default controller is not called in this case.

As another example, suppose producer V announces the availability of "Sales Forecast." Each controller is invited in turn to handle the event. Because controllers 1 and 2 only handle data item requests, the default controller handles the announcement by distributing it to both consumers.

8. Policy helper

The InfoBusPolicyHelper interface provides a means of centralizing security-related decisions. The decisions made in this class implement the permission and security mechanism used for all consumers, producers, and data controllers.

The InfoBus class holds a single object of type InfoBusPolicyHelper as a static variable. Whenever an InfoBus object is about to perform an activity (such as registering an InfoBusConsumer or distributing an InfoBusItemAvailableEvent), it first calls a method on that static variable to ensure the policy helper permits it. If the policy helper does not approve of the action, it throws a runtime exception which goes uncaught in the InfoBus.

This design strategy is optimized for use of the Java 2 Platform Security Architecture - e.g., AccessController.checkPermission() is called within the policy helper and an AccessControlException thrown if the action is not permitted. However, the specification of InfoBusPolicyHelper is general enough to allow implementations which do not rely on Java 2 Platform mechanisms.

The other activity delegated to the InfoBusPolicyHelper is the creation of default InfoBus names - when an InfoBusMember calls InfoBus.get(Object), the InfoBus in turn calls the policy helper's generateDefaultName() method.

Security and default naming functions are encapsulated in InfoBusPolicyHelper to provide flexibility in making these decisions. The javax.infobus package includes a default implementation of the interface in the javax.infobus.DefaultPolicy class, but specification of a different policy helper class can be made by setting a system property called javax.infobus.InfoBusPolicy.

8.1 The InfoBusPolicyHelper interface

The InfoBusPolicyHelper interface encapsulates several security decisions and default InfoBus name generation in one interface. The InfoBus class holds a single object of this type as a static variable, and all InfoBus instances perform a call to one of its security methods before performing an action. The implementation of InfoBusPolicyHelper considers the action being requested on behalf of a caller and may throw a runtime exception if it disapproves. This consideration may include examining the call stack and deciding on the basis of the classes it finds whether to grant permission.

It is not necessary for the policy helper to pass judgement on every method provided in the interface: a very relaxed policy helper may implement all security checks as no-ops (empty methods), while a very strict policy helper may introduce an arbitrarily complex set of checks and permissions before approving any action.

The static variable holding the InfoBusPolicyHelper in use is initialized when an InfoBus static method is called or when an InfoBus constructor is called, whichever occurs first. Once instantiated the policy helper static variable is immutable: no means of changing the policy helper is available short of restarting the JVM.

public String generateDefaultName(Object object)

The implementer of InfoBusPolicyHelper is responsible for determining the default InfoBus naming strategy in use. The InfoBus limits the parameter of the get() method to an object of type java.awt.Component or java.beans.BeansContext. From the object parameter, the policy helper must create a String that denotes the default InfoBus name for the object.

A default name policy must generate names that allow objects in a shared space - for example, on a single web page or within a single BeanContext - to communicate without having prior knowledge of what InfoBus name to specify. Ideally, the default name policy should generate names in which objects in other spaces (i.e. a different web page) get a different bus, so that InfoBuses do not get overpopulated.

public void canGet(String busName)

public void canJoin(InfoBus infobus, InfoBusMember member)

public void canRegister(InfoBus infobus, InfoBusMember member)

public void canPropertyChange(InfoBus infobus,
java.beans.PropertyChangeEvent event)

public void canAddDataProducer(InfoBus infobus, InfoBusDataProducer producer)

public void canAddDataConsumer(InfoBus infobus, InfoBusDataConsumer consumer)

public void canAddDataController(InfoBus infobus, InfoBusDataController controller, int priority)

public void canFireItemAvailable(InfoBus infobus, String dataItemName, InfoBusDataProducer producer)

public void canFireItemRevoked(InfoBus infobus, String dataItemName, InfoBusDataProducer producer)

public void canRequestItem (InfoBus infobus, String dataItemName, InfoBusDataConsumer consumer)

The security methods on InfoBusPolicyHelper are named to reflect the methods from which an InfoBus object will call them, and usually include as parameters a reference to the calling InfoBus and all parameters that the InfoBus method has been provided. In general, the policy helper method canXYZ is called by an InfoBus before performing the activity in method xYZ. For example, the implementation of the InfoBus method addDataProducer (InfoBusDataProducer producer) calls the InfoBusPolicyHelper method canAddDataProducer(InfoBus, InfoBusDataProducer) - with parameters set to ( this, producer ) - before permitting the producer's registration. The exception to this naming pattern is InfoBusPolicyHelper.canRequestItem(), which is called by the InfoBus methods findDataItem() and findMultipleDataItems().

The parameters provided to the InfoBusPolicyHelper method generally include all those provided to the parent InfoBus method to allow maximum flexibility in determining whether an action is permissible. Note that an InfoBusPolicyHelper implementation based on Java 2 Platform security mechanisms may find one or more parameters unnecessary.

These security methods are called from InfoBus before performing an action. InfoBus methods that check to see if the InfoBus object is stale will perform that check and throw an Exception if it is stale before calling the policy helper security method (because the exception prevents the action, and a replacement bus may permit the action where the stale one may not have).

The InfoBusPolicyHelper implementation in use may consider the requested action and throw a RuntimeException if it disapproves; if the action is permitted, the security method simply returns. This technique lends itself to use of the Java 2 Platform security mechanisms: the policy helper implementation can introduce a set of Permissions corresponding to InfoBus activity that it may want to restrict. When the security method is called, the policy helper can formulate the appropriate permission and call AccessController.checkPermission() to see if the Permission is granted in the current java.policy. If it is not, the AccessController throws an AccessControlException. Both the policy helper and the InfoBus then propagate the AccessControlException, which should generally propagate to the system level to indicate the unpermitted activity.

8.2 The DefaultPolicy class

This class implements the InfoBusPolicyHelper interface and is the policy helper put into effect if the javax.infobus.InfoBusPolicy system property is nonexistent or unreadable. For the InfoBus 1.1 release, which supports JDK 1.1, the DefaultPolicy class:
  • generates the default InfoBus name based on DOCBASE from an AppletContext.
  • performs no security checks, because Java 2 Platform is required for all security mechanisms. All of the methods defined for the InfoBusPolicyHelper interface have empty implementations for the DefaultPolicy class in InfoBus 1.1, and will not throw an exception if called. This simulates the behavior of passing a permission check in InfoBus 2.0.
InfoBus 2.0 will include a new implementation of this class that uses the Java 2 Platform security features to do its work.

9. Exceptions

9.1 InfoBusMembershipException

This Exception is thrown by the InfoBus core code when it will not allow an action related to membership with the bus. For example, this exception is thrown when InfoBusMemberSupport.joinInfoBus() is called on a class which is already a member of a bus. It is also thrown when InfoBusBeanSupport.setInfoBusName() is called, but a PropertyVetoException is thrown by the resulting setInfoBus() call to change the bus membership.

9.2 DuplicateColumnException

This exception is thrown in RowsetAccess methods when a duplicate column is found.

9.3 ColumnNotFoundException

This exception is thrown in RowsetAccess methods when a specified column cannot be found.

9.4 InvalidDataException

InvalidDataException extends java.lang.Exception, and is thrown by any InfoBus method which modifies data. The producer may throw the exception when data cannot be accepted as specified, such as an invalid date.

9.5 RowsetValidationException

RowsetValidationException extends InvalidDataException to provide more information about the Rowset on which the exception occurred. A RowsetValidationException may be thrown by any RowsetAccess (or sub-interface) method which modifies data.

public RowsetValidationException(String message, RowsetAccess rowset, InfoBusPropertyMap map)

The constructor for RowsetValidationException specifies a message string, a reference to the rowset on which validation failed, and an optional reference to an implementation of InfoBusPropertyMap for supporting properties on this exception. When properties are not supported, null should be specified for map.

public RowsetAccess getRowset()

This method returns a reference to the RowsetAccess object on which the validation problem was detected.

public Object getProperty(String propertyName)

Returns a property or metadata information about the validation exception. Support for properties is optional; null should be returned for unsupported properties.

Support for properties is optional. If no property map was specified on the constructor, getProperty() returns null. Otherwise, this method calls InfoBusPropertyMap.get() with the specified key and returns the result. null is the conventional return value when the specified key is not supported as a property name.

9.6 UnsupportedOperationException

This runtime exception is temporarily defined in javax.infobus for the InfoBus 1.1 release for use with JDK 1.1. Applications built with Java 2 Platform should use java.lang.UnsupportedOperationException instead. This method may be thrown for optional methods that are not supported, such as methods that modify data when the producer provides read-only access.

9.7 StaleInfoBusException

This RuntimeException is thrown when certain operations are attempted on an InfoBus instance which has become "stale." See Chapter 2 for an explanation of how stale InfoBus instances occur, and how to avoid them.

10. Guidelines for well-behaved InfoBus components

This section offers guidelines for developers creating InfoBus components that will make them generally useful with other components.

10.1 Membership

For maximum flexibility, applets should accept a bus name as a parameter in the HTML tags, and use it if found. If none is found, it should use the default InfoBus. InfoBus members should be prepared to have their "InfoBus" property changed by an outside class (such as a container). The InfoBusMemberSupport class provides this support.

10.2 Data items

Specifying names

It should be possible to name data items by way of applet parameters, the UI, or both.

Rendezvous

All items announced as available should have a matching revoked announcement. In addition to sending an InfoBusRevokedEvent, the revoked change event should be sent to data item change listeners.

Interfaces to be implemented for data items

Though not required by the API, an InfoBus-compliant data item should always implement the DataItem interface and support all methods defined there by returning non-null objects as required. The data item name should be the same as the one specified in the available or request event. We recommend that sub-items of a collection data items also implement this interface.

Data producers should provide as many standard access interfaces as makes sense to support a wide variety of consumers. The standard access interfaces include those defined in chapters 4 and 5 of this specification, along with the collection interfaces defined by Java 2 Platform.

Data consumers should always call DataItem.release() when they are dropping their last reference to a data item they have requested, and should never issue any other calls on the data item after calling release().

Lists of fields and contents

A common form of exchange is the fieldname-value pair. This form is consistent with the notion of passing bags of properties. It is anticipated that this kind of table of values will be a common exchange medium. Note that a list of fields and their contents is an appropriate way to model (with careful implementation) a single row of a database table.

As such, it is an appropriate medium for applications like forms fill-out, or data browsing. In addition to the passing of repeating tuples of data, the field/value pair is also a good way to pass general parameter information from producer to consumer. Field/value lists should be modeled as a Map interface (from the JDK Collections).

Streams of data

InputStreams are considered single-valued items and could be presented in an ImmediateAccess object. However, the intent of the InfoBus is to impose structure on the data, in an effort to make the data comprehensible to the widest audience of consumers. In this sense, offering only an InputStream where more significant formatting is possible is a shortcut with potentially expensive future consequences. In general, streams of data should be handled via the Transferable mechanism as per the AWT clipboard specifications.

Of course, some types of InputStreams, like multimedia streams that are "played" by the recipient, will not lend themselves to such formatting.

Appendix A: Changes in the InfoBus 1.2 Specification

This section lists changes that have been made in the specification, comparing this version to the previous version (InfoBus 1.1.1). The changes are organized as "Major Changes" (those that add or deprecate interfaces or classes, deprecate interfaces, or otherwise imply changes in the way things should work) and "Minor Changes" (clarifications, corrections to typos, corrections to the API where the spec was wrong but the code and JavaDoc were right, and so on).

The essential change for InfoBus 1.2 is the addition of a new access interface, called ReshapeableArrayAccess, that extends ArrayAccess to add an API for allowing the consumer to insert and delete rows or columns, and change dimensions on the array at will. There are a number of supporting changes, mainly in announcing changes that are possible with the new interface.

Major Changes in the InfoBus 1.2 Specification

  • In section 4.12, "The ArrayAccess interface", the method getItemByCoordinate() now specifies that null is returned to indicate an empty cell.
  • In section 3.6, "DataFlavors and describing data items", the MIME string constants have been redefined as a bug fix so that applications that use DataFlavors with the defined MIME strings, and are built with JDK 1.1.x and InfoBus 1.2 will also work on Java Platform 2. The MIME strings introduced in the earlier spec were missing the "infobus.javax." qualification before the interface name, and that caused applications to fail when used on the Java 2 Platform.
  • Section 4.13, "The ReshapeableArrayAccess interface", describes a new access interface that was introduced in this release.
  • Section 6.1, "The DataItemChangeManager interface", adds a requirement that data items that implement ReshapeableArrayAccess must also implement DataItemChangeManager, as consumers of this interface typically cannot function well, without the DataItemShapeChangedEvent notification.
  • Section 6.2 was renamed to "The DataItemChangeManagerSupport class". The old class (DataItemChangeSupport) has been deprecated, and is replaced by the new class (DataItemChangeManagerSupport). The extra word, "Manager", was added to the class name to distinguish it from another new class, DataItemChangeListenerSupport. DataItemChangeManagerSupport has one new method (to fire DataItemShapeChangedEvents to consumers of a ReshapeableArrayAccess item) compared to the older class it replaces.
  • Section 6.4, "The DataItemShapeChangeListener interface", describes a new interface that consumers of the ReshapeableArrayAccess will use instead of DataItemChangeListener.
  • Section 6.5, "The DataItemChangeListenerSupport class", describes a new class that makes it easier to implement data item change listeners (of either type).
  • Section 6.6, "The DataItemChangeEvent class and event subclasses", has a subsection "DataItemValueChangedEvent, DataItemRevokedEvent and RowsetCursorMovedEvent classes" is renamed to "DataItemValueChangedEvent, DataItemRevokedEvent, DataItemShapeChangedEvent and RowsetCursorMovedEvent classes", since it also describes the new change event. Figure 6-2 is also revised to include the new event.
  • Section 6.7, "Distribution and handling of DataItemChangeEvents", has a new paragraph describing how the DataItemDeletedEvent is used for data items that implement the ReshapeableArrayAccess interface, as well as a description of how the new DataItemShapeChangedEvent is used for those data items.
  • Section 6.8, "Examples of event propagation", adds examples that apply to a ReshapeableArrayAccess data item.
  • A few classes, interfaces, and methods are deprecated. These are described in Appendix B. Appendix B will accumulate all deprecated classes, interfaces, and methods in future releases.

Minor Changes in the InfoBus 1.2 Specification

The following minor changes have been made in this version of the specification, mainly fixes to spec wording problems. These do not represent any changes to the functioning of InfoBus:
  • Some sections are renumbered because of new sections inserted before them.
  • In several places, references to InfoBus 1.2 have been changed to InfoBus 2.0. The planned release that uses Java 2 Platform security features, BeanContext, and the like, has been renamed from InfoBus 1.2 to InfoBus 2.0. Additional releases of InfoBus 1.x for JDK 1.1.x will be numbered 1.3 and so on.
  • In section 2.8, "The InfoBus class", the incorrect reference to "InfoBusDefaultPolicies" has been corrected to "DefaultPolicy."
  • In section 3.2, "Event listeners," the spec used to state that methods to add event listeners were called "in" the start() method. This has been corrected to "during the execution of" the start() method. The same correction was applied for removing event listeners during the execution of the stop() method. The changes reflects the fact that in the sample application, the event listeners are added and removed in the propertyChange() method of the PropetyChangeListener interface.
  • Sections 4.2 and 4.3 used to refer to the now-obsolete clock and timesource sample apps. They has been reworked to describe the newer SimpleProducerBean and MonetaryDataItem example.
  • In section 4.11, "The ImmediateAccess interface", the API for ImmediateAccess.setValue() incorrectly specified Object as the return type. This has been corrected to void, to agree with the code and JavaDoc.
  • In section 5.4, "The RowsetAccess interface", a wording error was fixed.
  • In section 6.2, "The DataItemChangeManagerSupport class", the descriptions for methods that fire events now include a description of the propertyMap parameter. This description was missing in the last spec.
  • In section 6.4, "The DataItemChangeEvent class and event subclasses", a wording error was fixed.
  • In section 6.5, "Distribution and handling of DataItemChangeEvents", for the subsection called DataItemValueChangedEvent, the obsolete TimeSource sample app was used as an example. It has been reworked to remove that reference.
  • In section 8.1, "The InfoBusPolicyHelper interface", the text ran over the bottom of the page. The layout has been fixed.

Appendix B - Deprecated Interfaces, Classes and Methods

This appendix lists various interfaces, classes, and/or methods that used to be a part of earlier InfoBus specifications and are still present in the InfoBus.jar file to support existing applications, but are marked deprecated because they are not the preferred means of coding InfoBus applications from this release onward.

B.1 DataItemChangeSupport replaced by DataItemChangeManagerSupport

The DataItemChangeSupport class provided an implementation of the DataItemChangeManager interface for InfoBus 1.1.1 and earlier. In InfoBus 1.2 and onward, the preferred support class for DataItemChangeManager is now called DataItemChangeManagerSupport. The older DataItemChangeSupport class is deprecated. The newer DataItemChangeManagerSupport contains all of the methods from DataItemChangeSupport, and adds a new method for firing the new DataItemShapeChangedEvent.

The reason for renaming the class is that we have also introduced a new support class for DataItemChangeListener, called DataItemChangeListenerSupport, and we thought it would be confusing if the support class for the change manager did not have the word "Manager" in it to distinguish it from the "Listener" support class.

*As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.

* * * END OF DOCUMENT * * *