Let InfoBus Plug Your Beans Together
Equip your JavaBeans to exchange data
without knowing beans about each other
 
by Mark Colan and Christopher J. Karle

The InfoBus is a public specification of dynamic data-sharing technology that enables developers to equip their JavaBean components to communicate with other JavaBean components. InfoBus was jointly designed by Lotus Development Corporation and Sun Microsystems' JavaSoft division; the final release 1.1 specification, the release candidate for InfoBus 1.1, and other information about InfoBus can be found at http://www.javasoft.com/beans/infobus. InfoBus 1.1, which supports applications designed for either JDK 1.1 and 1.2, will be released soon; watch the web page for more news.

   
InfoBus interfaces allow application designers to create "data flows" between cooperating components. In contrast to an event/response model where the semantics of an interaction depend upon understanding an applet-specific event and responding to that event with applet-specific callbacks, the InfoBus interfaces have very few events and an invariant set of method calls for all applets. The semantics of the data flow are based on interpreting the contents of the data that flows across the InfoBus interfaces as opposed to responding to names of parameters from events or names of callback parameters.

Components that make up an InfoBus application can be classified in three types:

Data producers: Components that respond to requests for data from consumers.Data consumers: Components interested in hearing about any new data sets that enters the environment. Data controllers: The InfoBus traffic cop. A data controller is an optional component that regulates or redirects the flow of events between data producers and consumers. A data controller is not required for the application described here.


Fig2
 
The second applet is a data access component (DAC). It looks for a data item (the string representation of an employee ID from the input applet), passes that string to a database via JDBC, and receives a result set, which is then published on the InfoBus. The DAC runs the query when it first receives an employee ID and each time the ID changes. When each query is complete, a change notification is sent regarding the result-set data item. The DAC in this example is both a data consumer (in that it looks for a query-string item from the employee ID input applet on the InfoBus) and a data producer (in that it publishes to the InfoBus a data item containing the results of the query).
The spreadsheet in this example acts as a data consumer: It looks for information published by the DAC and displays it on the screen, as shown in Figure 3. The application designer did not have to write the spreadsheet, of course: It is a commercially available Java applet designed specifically to talk to the other applications via the InfoBus. The sheet responds to change events fromt he DAC to display the results of each query.
From the point of view of the InfoBus architecture, the application architecture looks a bit different. Note that the InfoBus is similar to a hardware bus; the three applets are all plugged into the same bus, as peers shown in Figure 2. In principle, all applets can see the events generated by all other applets; it is up to the application designer to control which data items will be produced or consumed by each applet. InfoBus-connected applets must all live in the same Java Virtual Machine, but that doesn't mean InfoBus can't participate in distributed applications. In our example, the DAC has a connection to a remote data source and uses JDBC to query it.
 
Although our simple example shows one producer for each consumer and vice versa, multiple consumers can receive and user data published by a producer, and a consumer can easily obtain data from multiple producers. In fact, a request for data can elicit multiple responses. Multiple conversations can occur simultaneously without requiring anything special fromt eh participating components.

Overview of the InfoBus Process for Data Exchange
The InfoBus supports a stylized protocol for data exchange between InfoBus components consisting of the following major elements:


Membership.
Any Java class can join the InfoBus if it implements the InfoBusMember interface. The membership process connects applets to an InfoBus instnace in preparation for data exchange.


Rendezvous. An InfoBus application supplies an object that implements InfoBusDataProducer or InfoBusDataConsumer interfaces (or both) to listen for events appropriate to a component's role as a producer or consumer. A producer can also announce the availability of information to all listeners. When a producer gets a request event for information it can supply, it creates an instance of a data item and provides it to the requesting consumer. The consumer can request a particular item by name or ask for a data item in reaction to a producer's announcement of its availability.

Data access.InfoBus specifies a number of standard interfaces to provide direct data transfer between a producer and consumer. The interfaces include ImmediateAccess, which provides an InfoBus wrapper for a simple data item; ArrayAccess, which provides access functions for an array with arbitrary dimensions; and RowsetAccess, which provides a row and column interface to support database solutions. In addition to these, InfoBus applications can also use the standard Java collection interfaces defined in JDK 1.2 (see http://java.sun.com/products/jdk/1.2/docs/guide/collections/) for structured data exchange.

Change notification. When a consumer has received a data item from a producer, it can request notifications of all changes to the data by registering a DataItemChangeListener on the data item. As the producer detects changes, either because of changes in the external data source or because of modifications made by consumers, it will announce the changes to all listeners.

Let's consider how each of these plays a role in the employee ID input applet.

Implementing InfoBusMember to Get on the Bus
For our sample application, our first need is to connect our components to the InfoBus. Our main application class, SimpleDataProducer, implements the required InfoBusMember interface. The methods required by InfoBusMember are designed to support an InfoBus property and listeners for changes to the property. An InfoBusMemberSupport class is available that encapsulates the required functionality, and the SimpleDataProducer uses -- this by creating an instance of InfoBusMemberSupport and delegating all methods to that class.

The following code indicates that EmployeeIDInput implements InfoBusMember (as well as other interfaces, explained later) in this manner. It declares member data that holds a reference to the InfoBusMemberSupport instance, and it defines each method required for InfoBusMember, delegating the work to the support instance. In the code snippet, we show only one method implementation; the implementation of the other methods is similar.

public class EmployeeIDInput extends 
Applet implements InfoBusMember, 
InfoBusDataProducer, ActionListener
{

   // use an InfoBusMemberSupport to hold          
   // our InfoBus
   private InfoBusMemberSupport    
m_IBHolder;

   // put the String to be published in a          
   // SimpleDataItem
   private SimpleDataItem      
m_data;

   // the name of the InfoBus to which we
   // connect
   private String              
m_busName = null;

   // the name we use to publish our data
   private String            
m_dataName;

   // following used to synchronize 
   // Available and Revoke events
   private Object            
m_AvailRevokeInterlock = new Object();

   // Delegate all calls to our
   // InfoBusMemberSupport 
   // object, m_IBHolder

   public InfoBus getInfoBus()
   {
      return m_IBHolder.getInfoBus();
   }

   // other InfoBusMember calls are 
   // delegated in the same fashion.
  

The InfoBusMemberSupport instance must be created before any calls are delegated to it, of course. In this example we do this work in the applet's init() method, after calling Applet.init():

public void init()
   {
      super.init();

      // the "this" in the following
       // constructor tells the
      // support to use this object in the
      // source field of all events 
      // it issues.
   m_IBHolder = new InfoBusMemberSupport 
( this );
  

init(\040) also sets up a property change listener so we know when our own InfoBus property has changed. This is important for creating reusable components; a Java Bean will typically have its InfoBus set by its Bean container.

m_IBHolder.addInfoBusPropertyListener ( 
this );
  

The "default InfoBus" can be used for communication between applets on the same browser page. A default InfoBus is one whose name is created from the DOCBASE provided by applets. Alternatively, an applet or Bean can have the name of the InfoBus it should join specified externally. It is good practice to allow the application designer to specify the name of the InfoBus to use. In the code below, the supplied name is used if an InfoBusName parameter is found, otherwise the default InfoBus is used.

m_busName = getParameter("InfoBusName"); /
/ leave null if not there
  

Similarly, a data-item name should be configurable. Data-item names are used in the rendezvous; in our example the producer will offer the employee ID input as a named data item. The code below gets the data-item name and saves it for later.

m_dataName = getParameter("DataItemName");

      if ( m_dataName == null )
      {
         m_dataName = "EmployeeID";
      }
  

Notice that InfoBusMemberSupport also provides a convenient, high-level method that can be called to join either a named or default InfoBus. The start() method is a good place to call it. InfoBusMemberSupport.leaveInfoBus() can be called in the stop() method. joinInfoBus can throw an exception; these statements are inside a try...catch block in the source file.

if ( m_busName != null )
   {    // gets a named bus using the busName string
      m_IBHolder.joinInfoBus ( m_busName );
   }
      else
   {    //gets default bus for this applet
       m_IBHolder.joinInfoBus( this );
   }
  

In the InfoBus model, the rendezvous is asynchronous. Data producers announce the availability of new data as it becomes ready at, say, completion of a URL read or completion of a calculation. Data consumers solicit data from producers as they require that data (at applet initialization, redraw, and so on.)

EmployeeIDInput listens for requests for data and announces the availability of data by implementing an InfoBusDataProducer listener object and adding it to the list of listeners on the InfoBus it joined. For simplicity, our example shows this interface implemented on the main applet class; in a real-world application, we recommend that the listener object be separate from the main class to prevent unwanted introspection, since the listener reference can be obtained by any other InfoBus component on the same bus.

InfoBusDataProducer defines only one method, which is called each time a consumer on the same bus requests data by name. Our producer compares the requested data name to the name received from a parameter and sets the data item if they match.

Infobus Interface Definitions
InfoBusMember is an interface implemented by a Java class in order to become a member of the InfoBus. Its primary purpose is to manage the InfoBus property, which keeps a reference to the InfoBus instance the member has joined. InfoBusMember also allows an external entity (for example, a bean container) to set the InfoBus it wants a member to talk to, so that a Bean Builder can control the communication between its Beans.

InfoBusDataProducer is an event listener implemented by data producers to receive request events from consumers.

InfoBusDataConsumer is an event listener implemented by data consumers to receive events indicating the availability and revocation of items of fered by producers.

DataItem is an interface that provides methods used for learning about a data item. Consumers can discover MIME-type information, get a reference to the producer's event listener, or ask for the value associated with a property string.

DataItemChangeManager is the interface that provides methods used by a consumer to add or remove a change listener. A producer implements DataItemChangeManager on all data items for which it is willing to provide change notification to listeners.

DataItemChangeListener A consumer implements this interface and registers the object with the producer's DataItemChangeManager to be informed of changes that occur on a data item.

ImmediateAccess is implemented by a DataItem that needs to retrieve data values directly from calls to methods on this interface. You can get an immediate rendering of the data as a String or Object. A method can be used to change the value or throw an exception for read-only items.

ArrayAccess is implemented by data items that are collections of DataItems organized in an n-dimensional array. The ArrayAccess interface includes methods to determine dimensions, obtain individual elements of the data set, iterate over all elements in the set, and subdivide oneArrayAccess object into two (e.g., to divide a data set of 2 columns and 5 rows into two data sets of 1 column and 5 rows).

RowsetAccess is an interface for data items that are collections of rows obtained from a data source such as a relational database server. The RowsetAccess interface contains methods to discover the number of columns as well as their names and data types, to get the next row, to obtain column values, and to insert, update and delete rows.

DbAccess is a database interface that allows the data consumer to is sue retrieval and non-retrieval queries and to optionally have the result announced as a DataItem.

InfoBus applications can exchange information using any of the access interfaces described below. The JDK 1.2 Collection interfaces can also be used for accessing structured information in a standard, application-independent fashion. These interfaces are described in http://java.sun.com/products/jdk/1.2/docs/guide/collections/.

public void dataItemRequested 
            ( InfoBusItemRequestedEvent ibe )
   {
      if ( m_data == null )
      {      // user hasn't clicked Search
            // yet, so item isn't available.
            return;
      }
      String s = ibe.getDataItemName();
      if ( ( null != s  ) && s.equals( 
m_dataName ) )
      {
         // THREADSAFETY: make our activity
         // on a positive match thread safe

         synchronized (m_data) 
         // NEVER USE OUR INFOBUS AS THE 
         // LOCK OBJECT
         {
            ibe.setDataItem( m_data );
         }
            }
      }
  

InfoBusDataProducer listens for request events. The listener can be registered when start() is called, after we join the InfoBus.

if (null !=  
      {
         try
         {
         // Add event listening when the
         // applet is started
               m_InfoBusHolder.getInfoBus().
               addDataProducer(this);
         }
         catch ( InfoBusMembershipException e )
         { /* handle exception */ }
      }
  

The data item is created and announced the first time the button is pushed. For the second and each successive time the button is pushed, we just set the new value.

private void searchClicked ()
   {
      if ( m_data == null )
      { //first click, so create data item
         //and announce
        m_data = new SimpleDataItem( 
textField1.getText(), this );
         // THREADSAFETY: create a lock to
         // prevent a REVOKE from being sent
         // before the AVAILABLE has
         // completed
         synchronized ( m_AvailRevoke
            Interlock )
         {
            InfoBus ib = 
               m_IBHolder.getInfoBus();
               ib.fireItemAvailable( 
               m_dataName,this );
         }
      }
      else
      {
            m_data.setValue( textField1.
            getText() );
      }
   }
  

We revoke the data item when stop() is called and remove our listener from the InfoBus.

// revoke our data item
   synchronized ( m_AvailRevokeInterlock )
   {
      InfoBus ib = m_IBHolder.getInfoBus();
      ib.fireItemRevoked( m_dataName, 
         this );
   }

   // tell the IBMSupport to leave its bus
   try
   {
      m_IBHolder.leaveInfoBus();
   }
   catch ( PropertyVetoException pve )
   {
      // do nothing - our InfoBus property
      // is managed by external bean
   }
   catch ( InfoBusMembershipException ibme )
   {
      // do nothing - simply means we 
      // already had null for IB
   }
  

Data exchange. Different data producers manage different types of data; consumers may wish to receive this data in simple or complex ways. To accommodate the needs of both producers and consumers, the InfoBus defines a determinate number of data access interfaces.

EmployeeIDInput uses a separate class, SimpleDataItem, to house the data it publishes. This class is a general-purpose implementation of ImmediateAccess; we'll look at the implementation of the methods required for our application.

Data controllers are potentially quite complex. They might keep track of data producers that have offered particular data items, and of data consumers that have requested data items, in order to provide late binding of producer to consumer. ImmediateAccess defines three ways of getting the data. getValueAsString() returns a simple string rendering of the data. getPresentationString() is similar, but can provide a rendering appropriate for a specified locale. For example, if ImmediateAccess is being used to house a monetary value, getValueAsString() might return "1000.00" whereas getPresentationString(), given a locale for the United States, might return "$1,000.00." The other access method is one that provides a reference to the Object held by ImmediateAccess.

For our purposes, the following simple implementations will suffice:

public String getValueAsString()
   {
      return m_value != null ?    
         m_value.toString() : new String("");
   }

   // Note that getPresentationString() is
   // identical to getValueAsString()
   // for this simple-minded example.

   public Object getValueAsObject()
   {
      return m_value;
   }
  

The DAC provides a row and column interface to the information returned by the query. This can be done using the ArrayAccess interface, or the database-specific RowsetAccess interface. A really flexible DAC might implement both interfaces to support the widest possible audience of consumers, some of which may understand only one or the other.

The DataItem interface is used to provide descriptive information for a data item. The methods include getTransferrable(), which provides, among other things, access to a MIME type for the item; getProperty(), which allows a producer to return an Object in response to a property name it recognizes; and getProducer(), which returns a reference to the event listener of the producer for identification purposes.

SimpleDataItem returns null for the first two and a reference to the EmployeeIDInput instance that supplies the item. Notice that our example is vulnerable to unwanted introspection, in that EmployeeIDInput has several other interfaces. A security-conscious application could implement InfoBusDataProducer in a class with no other functions to avoid giving out such information to nosy consumers.

Data Item Change Notification. The EmployeeIDInput class allows the user to enter an employee ID, then click Search to find data for that employee. The first time Search is clicked, the data item is announced, as we saw earlier. Every succeeding time we send a change notification. The DAC does a query when it gets the item and for each change in value to the data item.

Producers can easily implement the DataItemChangeManager interface to support change notification to its listeners using the DataItemChangeSupport class. Our approach recalls what we did with InfoBusMemberSupport: create an instance of the class and delegate all calls from the interface to the matching calls in the support class.

DataItemChangeSupport also provides methods that can be used to fire change methods of each type. EmployeeIDInput calls SimpleDataItem.setValue when the user clicks Search to set a new employee ID for look up. SimpleDataItem.setValue sets the new value, then calls a method to fire an event indicating the change in value to all registered listeners:

m_DICS.fireItemValueChanged ( this, null );

Putting It All Together
The last piece of our example is the HTML code that places our InfoBus components and controls the way they talk to each other (). In this example HTML code, notice that all three applications have parameters that specify the name of an InfoBus. Because all three use the same name, they will see each other on the InfoBus.

The first applet is the EmployeeIDInput example examined in this article. Its data item name has been specified as EmployeeIDNumber.

Remember that the second applet, the data access component, is both a consumer of a lookup string and the producer of a table of data returned by a query. A parameter called queryName specifies the data item name for the lookup string with a value of EmployeeIDNumber. Because the value is the same as the parameter EmployeeIDInput produces, they will exchange this data. The data item name for the query result data is specified by the parameter publishName, whose value is specified as EmployeeIDData.

Finally, the sheet component specifies a data item name with its DataName parameter value of EmployeeIDData. Again, since this matches the data item name produced by the DAC, the sheet will be able to display the data from the query.

How Can the Infobus Help Me?
InfoBus 1.1 Release Candidate is now available from www.javasoft.com/beans/infobus. Few changes, if any, will be made between this and the final release. The InfoBus 1.1 release will be a standard extension to the JDK. This technology allows InfoBus-compliant components to be quickly and easily assembled into larger composite applications built of reusable components. For example, Lotus eSuite uses the InfoBus for seamless application integration allowing programmers to write custom extensions using eSuite and other off-the-shelf Java applications.

Editor's note: This article was written before the InfoBus spec was frozen. API modifications may require minor changes to the code samples. The version of the code available at http://www.java-pro.com should reflect the most current version of the specification.


Mark Colan is the lead architect for the Java InfoBus project and editor of the InfoBus specification; Chris Karle is a developer on the InfoBus project. Both are employed by Lotus Development Corp. Comments and questions on InfoBus can be sent to infobus-comments@java.sun.com This article originally appeared in Java-Pro magazine, Feb/March 1998.