Using SOM for Tool Integration

From EDM2
Jump to: navigation, search

By Christina Lau

Abstract

IBM's System Object Model (SOM) is an object-oriented programming interface for building binary class libraries. When changes are made to a SOM class, client programs that use the SOM class will not need to be recompiled. The SOM Toolkit provides a set of frameworks for building object oriented applications. These include Distributed SOM, the Persistence Framework, the Replication Framework, and the Emitter Framework. In this paper, we discuss our experience using SOM to handle the control aspects of tool integration.

Introduction

It is generally believed that software developed using object-oriented techniques is easier to use, maintain, and extend. SOM complements existing programming languages by providing a language-neutral model for developing object-oriented applications[1]. To use SOM, a developer defines the class interfaces in a language called Interface Definition Language (IDL). The developer then implements the methods in any of the languages that are supported by the SOM compiler. Finally, the classes are compiled into a dynamic link library (DLL) to be distributed to clients. Clients can use these classes directly or can override existing methods to develop their own applications. As long as the class interfaces remain unchanged, a new DLL can be redistributed to clients without requiring them to compile their applications. This full backward binary compatibility of SOM is best demonstrated in the building of the OS/2 Workplace Shell[4].

As the computer industry is moving toward distributed client-server systems, tools and applications have to interoperate in a heterogeneous environment. Existing integration techniques such as Dynamic Data Exchange[5] do not extend well into such distributed environments. The Object Request Broker (ORB), as defined by the Object Management Group (OMG), provides mechanisms by which objects transparently make requests and receive responses. The ORB thus provides interoperability between applications on different machines[2].

Distributed SOM (DSOM), which is built on top of SOM, was developed to comply with the OMG's specification of the ORB[1]. DSOM makes the location of an object transparent to clients. This means that a client program will always use the same invocation to access an object regardless of whether it is local or remote. Therefore a tool can use DSOM to interact with another tool without knowing where the other tool is located.

In this paper, we will look at how to use the infrastructure in SOM to build and integrate tools. We will then look at how these SOM-based tools can be easily used to compose new tools. Tool Composition refers to the ability to build new tools by using existing tools. Finally, we will look at the notification aspects of tool communication.

Defining a Tool as Classes in SOM

The first step to use SOM is to define a class interface. Suppose we want to define an edit tool called Lpex in SOM. We begin by defining the set of operations that are supported by the tool. The result is then represented in an IDL:

#include <somobj.idl>
interface FileObject;
interface Lpex:SOMObject
{
   void etart(in FileObject fileObj);

   // This method starts the Lpez
   // editor on the file object
   void save(in FileObject fileobj);
   // This method saves the file object
   void goToLine(in FileObject fileObj);
   // This method positions the cursor
   // to a specified line
 };

The SOM compiler can then be run to produce binding files and an implementation template for the Lpex class. Code can then be added to the implementation template to implement the tool operations. For example, the start method implementation might contain code to display an edit screen.

Notice that each method takes a FileObject as an input parameter. The FileObject is another SOM object that is used to store file-related information. One of the attributes is a file name. We will look at FileObject in more detail later on.

Tool Invocation

A client can use offset resolution to invoke the methods in the Lpex class. For example, another tool program that is written in C++ can use the following to invoke a method in the Lpex class.

#include <FileObject.zh>
#include <Lpex.xh>
int main(int argc, char *argv[])
{
  Environment *env;
  FileObject *file;
  Lpex *lpez;

  env = somGetGlobalEnvironment();

  // create an instance of FileObject
  // set file name to test.c
  file = new FileObject;
  file->_set_fileName(env,"test.c");

  // create an instance of Lpex
  lpex = new Lpez;
  // invoke start method
  lpex->start(env, file);
  // invoke goToLine method

  lpex->goToLine(env,file);

  ...

  // Free the instances
  delete file;
  delete 1pez;
}

Offset resolution is the fastest way to invoke a method but is also the most restrictive. The client must know the name of the class and the name of the method at compile time. The client must also have the bindings (.xh files) for the tool IDL. Therefore, this scheme is most suitable for tightly coupled tools. An example of tightly coupled tools is an edit, compile and debug tool set. In the next section, we will show how to use metaclasses and dispatch function resolution to achieve more flexibility for less tightly coupled tools.

Using Metaclasses

The methods we defined above for the Lpex class are instance methods. These are methods that can be performed by an instance of the Lpex class. Every class in SOM is also associated with a class, which is referred to as metaclass. SOMClass is the root class for all SOM metaclasses and is also the metaclass for the above Lpex class.

The metaclass of a class provides the class methods for the class. Class methods are used to perform global operations on a class. For example, class methods can be provided to create instances, delete instances and keep track of information about the instances of the class. Since soMClass is the root class for all metaclasses, it provides generic class methods to create new instances. For a detailed description of soMClass, see the SOM Toolkit Reference Manuai[6].

We will create a new metaclass for our Lpex class. The new IDL becomes:

#include <somobj.idl>
interface FileObject;
interface MetaLpext; // forward declaration

interface Lpez:SOMObject
{
   void start(in FileObject fileObj);
   // This method starts the Lpex editor on
   // the file object
   void save(in FileObject fileobj),
   // This method saves the file object
   void goToLine(in FileObject fileObj),
   If This method positions the cursor to
   // a specified line
   #ifdef _SOMIDL_
   implementation
   {
     metaclass = MetaLpex;
     dllname = "lpex.dll";
   } ;
   endif;
};

interface MetaLpex;
{
   Lpex createLpex(in FileObject fileObj);
   // This method creates and returns a new
   // instance of the Lpex class
};

The Lpex class now contains a class method createLpex that is used to create an instance of an Lpex class. The class relationships are shown in Figure 1. In the next section,we will show how a class method facilitates the use of runtime method resolution.

SOM-Tool-Figure1.png

FIGURE 1. Parent class and Metaclass relationships

Using Dispatch to Invoke an Object's Method

In this section, we show how a program that does not have knowledge about the Lpex class at compile time can use the runtime method resolution in SOM to invoke an Lpex instance method. To invoke an instance method, we have to first obtain an instance pointer to the Lpex class. This can be done by calling the class method createLpex. However, in order to call createLpex, we must have a pointer to the class object.

SOM provides a somFindClass method that allows one to dynamically create a class object. Using the pointer to the class object, we can then use somDispatch to invoke the class method createLpex to create a new Lpex instance . This is shown in the following code segment:

// creates a class object for Lpex
// and stores the pointer in classPtr

classPtr = somFindClass {
          SOMClassMgrObject,
          somxdFromString("Lpex"),0,0);

// invokes createLpex class method to
// create an Lpex instance
1pexPtr = eomDispatch(claeePtr,
          somldFromstring("createLpex"), 0);

The instance pointer 1pexPtr can then be used to invoke any of the Lpex instance methods. The following invokes the start method:

 somDispatch(lpexPtr,
       somldPromString("start"), "test.c");

This approach provides maximum flexibility for tools to interact with each other. A tool can determine at runtime what other tools it needs to invoke in order to accomplish its task. For example, the class name Lpex and the method names createLpex and start can all be input from the caller. The tool logic can then use the dispatching mechanism to invoke the requested tool.

Using DSOM

Perhaps the biggest advantage of wrapping a tool as SOM objects is to prepare the tool for remote access. The DLL "Ipex.dll" that implements the class Lpex can be used unchanged in a distributed environment. This is because DSOM provides a generic server that includes code for server activation, loading the Lpex DLL, object creation, and data marshalling. By registering the Lpex class with the generic server program, the Lpex program is ready to be accessed by clients from different machines.

For a client to send requests to a remote tool, the interface for the operations remains the same. The only differences lie in the creation and deletion of the tool instance. In the Lpex example, the local Lpex creation statement:

lpex = new Lpex ;

should be replaced by a call to find the Lpex server. If the server is found, a server proxy object is returned. The server proxy can then be used to create an Lpex instance. This is shown in the code segment below:

server = somdFindAnyServerByClass(
           SOMD_ObjectMgr,
           "Lpex") ;
lpex = somdCreateObj(server, "Lpex") ;

The Lpex proxy that is returned from somdCreateObj can be used by the client to invoke operations on the remote Lpex. To destroy the Lpex proxy, somdDestroyObject should be used instead of somFree.

Tool Composition

We will now look at how to build new tools based on existing tools. Tool Composition has four components:

  • Data Definition
  • Tool Definition
  • Method to Implementations Mapping
  • Runtime Generation.

Data Definition

This is used to define object types and the operations that operate on an object type. Object types are used to represent the user's world at the interface level. For example, a FileObject can be used to represent file information and a PrintObject can be used to represent printer information. Each object type can have its own data and can perform a set of actions. The same action might be supported by different object types. The behavior of the action is determined by the providing object. This is known as polymorphism.

To make use of SOM for tool composition, we translate each object type into an IDL. For example, the IDL for a FileObject can be as follows:

#include <somobj.idl>
interface FileObject:SOMObject;
{
    attribute string fileName;

    void edit();
    // invokes editor on file
    void checkIn();
    // checks file into library
    void checkOut();
    // checks file out from library
};

An object type can also be defined using existing object types. This is known as inheritance. A derived type can override the methods in its parent's class. For example, a DocObject can be derived from a FileObject and overrides the edit method.

At this stage, we have not yet specified the implementations for each method. We simply provide an interface for a user to interact with objects. Later on, we will show how to provide the implementations for the different methods.

Tool Definition

This is used to describe a tool, the data type it works with, and its entry points. The Lpex class example above is an example of a tool definition. For the purpose of illustration, we will define another tool Cmvc. Cmvc is a configuration management tool. Its IDL is shown below:

#include <eomobj.idl>
interface Cmvc:SOMObject;
{
   void checkIn(in FileObject fileObj);
   // This method checks a file into Cmvc

   void checkOut(in FileObject fileObj);
   // This method checks a file out
   // from Cmvc
};

Method to Implementations Mapping

This is used to define different implementations for a method on an object type. In effect, this defines how an object behaves when a request is received. For example, the edit method on a FileObject and a DocObject might behave differently as shown in Table l.

Table 1. Different Implementation for edit

Object Type Method Implementations
FileObject edit Lpex->start
DocObject edit Framemaker->start

Using this technique, an organization can start configuring tools based on its needs. To complete the implementation mappings for a FileObject, an organization might map the checkIn and checkOut methods to the check in and check out implementations in Cmvc:

Table 2. Simple Mappings for FileObject

Object Type Method Implementations
FileObject checkIn Cmvc->checkin
FileObject checkOut Cmvc->checkOut

Over time, the organization might want to perform the following additional functions on check in and check out:

  1. A file can be checked in if the complexity level is below a certain number. A Metric tool with the IDL below is to be used for complexity analysis.
interface Metric: SOMObject;
{
  long analyse(in FileObject fileObj);
  // Return ok if the complexity of the
  // file is below 10
};
  1. After a file is checked out, invoke the Lpex editor on the file.

This organization will therefore redefine its mappings for the checkln and checkOut method:

Table 3. Complex mappings for FileObject

Object Type Method Implementations
FileObject checkIn Metric->analyse

Cmvc->checkIn

FileObject checkOut Cmvc->checkOut

Lpex->start

Clearly, it is important that such a migration is transparent to the users. Any program such as graphical user interfaces (GUIs) that were written to use the checkln and checkOut methods should not need to be recompiled or relinked. In the next section, we will see how a new runtime can be generated to replace the old runtime to make such a migration completely transparent.

Runtime Generation

Based on the Method to Implementations mapping, the implementations for each object type can be generated. For example, the C++ implementations for the FileObject in the initial configurations shown in Table 1 and Table 2 will look like the following:

#include <Cmvc.xh>
#include <Lpex.xh>
SOM_Scope void SOMLINK checkln (
   Fileobject * somSelf, Environment *ev)
{
  Cmvc *citric ;

  // create a new instance
  cmvc = new Cmvc;

  // invoke cmvc checkln method
  cmvc->checkln(somself, ev);
}

SOM_Scope void SOMLINK checkOut (
    FileObject *eomself, Environment *ev)
{
  Cmvc *citric ;

  // create a new instance
  cmvc = new Cmvc;

  // invoke Cmvc checkout method
  cmvc->checkOut(somSelf, ev);
}

SOM_Scope void SOMLINK edit (
    FileObject *somSelf, Environment *ev)
{
  Lpex *lpex ;

  // create a new instance
  lpex = new Lpex ;

  // invoke Lpex start method
  lpex->start(somself, ev) ;
}

A DLL can be built for the FileObject and distributed to clients. When a new configuration is defined, one can regenerate a new implementation for the FileObject. The new implementation is highlighted in italics below:

#include <Cmvc.xh>
#include <Lpex.xh>
#include <Metric.xh>
SOM_Scope void SOMLINK checkln (
    Fileobject *somSelf, Environment *ev )
{
  Cmvc *cmvc;
  Metric *metric;

  // create new instances
  cmvc = new Cmvc;

  metric = new Metric;

 // check complexity before check in
 // file
 if ( metric->analyee(oomSelf, ev) )
 {
     // invoke cmvc checkln method
     cmvc->checkln(somself, ev) ;
 }
}

SOM_Scope void SOMLINK checkout (
  FileObject *somself, Environment *ev )
{ 
  Cmvc *cmvc;
  Lpex *lpex;

  come = new Cmvc;
  lpox = new Lpex;

  //invoke Cmvc checkout method
  cmvc->checkout(somSelf, ev);

 // invoke Lpex start method
 lpex->atart(eomself, ev);
 
}

SOM_Scope void SOMLINK edit (
  FileObject *somSelf, Environment *ev)
{
    Lpex *lpex ;

    // create a new instance
    lpex = new Lpex;

    // invoke Lpex start method
    lpex->start(somSelf, ev);
}

Any client programs that were developed to use the DLL will execute the new configurations when the DLL is replaced.

Notification Mechanism

In addition to requesting services from another tool, a client might want to notify other interested parties on the completion of an event. The SDE WorkBench/6000 implements such a facility using the Broadcast Message Server (BMS)[7]. A tool registers with the BMS the messages that it is interested in. The registration is typically in the form <message-type, tool-class, command, action> . The messagetype field indicates whether the message is a Request, a Failure or a Notify message. The tool-class field indicates what tool class the tool belongs to. The command field indicates the message that the tool is interested in. The action field indicates the function to execute when the message is received.

When a message is sent by a tool, the BMS will broadcast the message so that any tools that have registered an interest in this message will be notified. An example is shown in Figure 2.

SOM-Tool-Figure2.png

FIGURE 2. A message broadcast example

In this example, the Cmvc tool and the Browser tool are interested in a fileModify event from the Edit class. They accomplished their interest by registering with the BMS in steps 1 and 2. When the Edit tool sends out a fileModify message in step 3, the BMS notifies the list of interested parties (step 4).

The BMS routes messages based on pattern matching. As a result, it forces tools to communicate using a predefined message pattern. This can be inadequate in situations where non-character based information must be passed. For example, to pass an object reference, the object handle will have to be encoded into a character representation and then decoded on the receiving end.

To alleviate some of these limitations, we propose to use the dispatch function in SOM to handle routing. The dispatch function supports a variable argument list and any parameter types can be passed.

Using Objects for Dispatch

We propose a new registration and notification mechanism that is based on objects and composition. As we saw earlier, tool composition allows users to easily reconfigure their environment to include the set of tools that are needed for their function. By the same token, tool composition can also be used to configure the set of tools that need to be notified on a particular event.

Previously, we described Data Definition as the component that is used to define object types that represent the user's world at the interface level. Each operation on an object type can in turn map to different implementations. Using this mechanism, we can view each event that a tool is interested in as an operation on an object type. We further view the implementations for an operation as the list of callback functions that will service the event.

Consider the Fileobject we discussed in Section 8.1. We can use the Fileobject to handle registration and notification by adding a fileModify method. The Fileobject IDL becomes:

#include <somobj.idl>
interface Fileobject:SOMObject;
{
  attribute string fileName;
 
  void edit();
  // invokes editor on file
  void checkIn();
  // checks file into library
  void checkout();
  // checks file out from library
  void fileModify();
  // notifies interested parties
};

Assuming we want the same notification events as in Figure 2, where the Cmvc tool and the Browser tool are the tools that are interested in a fileModify event, we can map the implementations for the fileModify method to the callback methods in the Cmvc and the Browser tool.

Table 4. Mappings for fileModlfy

Object Type Method Implementations
FileObject fileModify cmvc->fileChange-Callback

browser->fileChangeCallback

A new runtime can be re-generated for the Fileobject with the following implementation for the fileModify method.

 SOM Scope void SOMLINR fileModif y
 (Fileobject *somSelf, Environment *ev )
 {
   Cmvc *cmvc;
   Browser *browser;

   // creates a new Cmvc instance
   cmvc = new Cmvc;

   // creates a new Browser instance
   browser = new Browser;

   // invokes Cmvc callback
   cmvc->fileChangeCallback(somSelf, ev);

   // invokes Browser callbac k
   browser->fileChangeCallback(soBelf,ev);
 }

We also discussed how to wrap a tool using IDL. For example, the Lpex editor is wrapped in an Lpex IDL where a Fileobject is specified as an input parameter to the Lpex methods. Consequently, if the Lpex editor wants to send out a fileModify message, it can simply invoke the fileModify method on the input Fileobject as shown in the code segment below. The implementation for fileModify then invokes the interested parties.

 // logic to detect if file is modified
       ...
 // invoke fileModify on input fileobj
       fileObj->fileModify(ev);

This example demonstrates the simplicity of using objects to handle routing. To avoid oversimplification, let it be noted that the implementation code shown in the fileModify method above executes the callbacks in a sequential manner. If a concurrent model is required, then separate processes should be created. Also, to allow for remote tool execution, one might want to replace the local cmvc and browser instance creation with a call to find the corresponding servers and use the server proxies to create the cmvc and browser instances.

Data Change Notification

Another notification scheme involves objects with different views . The Event Notification Mechanism Invention Disclosure in [8] described this in the context of GUI objects. Typically, an object can be displayed graphically in many ways. When a change is made to one view, the other views will need to be updated to reflect the change. A Smalltalk implementation for such notification mechanism is proposed in the disclosure.

In this section, we propose an alternate approach that is based on the SOM Replication Framework. The Replication Framework allows an object to be replicated across address space. When an update is made to one replica, the framework will propagate the update to all the other copies. The updates are communicated without the use of secondary storage and are serialized by the framework.

Using Replicated Objects

We begin by defining objects that will need to be updated in different views as replicated objects. In this way, we can rely on the Replication Framework to update all copies whenever a change is made to one copy. However, the framework will not automatically refresh all the different views of the object because the framework has no knowledge of the views. To automatically update all the views, we will implement a view notification scheme using a "register and notify" object.

Overview of Implementation

The overall implementation is as follows:

  • A RegAndNotify class is created. It has two methods: register and notify. The register method uses some internal data to store registration information. The notify method performs dispatching based on the registration information.
  • An instance of RegAndNotify can be created

by a replica. The replica stores this object pointer and returns it when requested by a client. For clarity, we will call this pointer regPtr.

  • If a view is interested in receiving notification when the object is updated, it should use the regPtr to register its interest in receiving notification. The registration information should include the method to invoke when an update occurs.
  • When an update occurs, the replica will use the regPtr to notify the interested views.
An Example

Consider the following example. A Product class is used to model product information. Product information can be displayed in two views: a table view and a graph view. The objects chips, biscuits, and candies are instances of the Product class. The IDL for Product is shown below. Figure 3 depicts the overall class hierarchies.

SOM-Tool-Figure3.png

FIGURE 3. Class hierarchies for Product example

#include <replicbl.idl>
interface RegAndNotify ;
interface Products SOMRReplicbl
{
   attribute string productName;
   attribute long numberSold;
   attribute RegAndNotifyregPtrt;
   
   void initReplica(in string prodName);
   // This method sets the replica name and
   // initialize replica for operation
   // logging
    
   void updateNum(in long salesCount);
   // This method locks the replica and
   // updates numberSold. it then notifies
   // the interested views using regPtr
   
   void createRegPtr();
   // This method creates an instance of
   // RegAndNotify and stores
   // the returned pointer inregPtr
   
   RegAndNotify getRegPtr();
   // This method returns the regPtr for
   // this replica
}

Suppose a manager is interested in the sales of chips and wants to monitor the sales information in a table form. The manager will set up his notification scheme as follows:

 // Creates a new product instance and
 // initialize it for replication
 prodPtr = new Product;
 prodPtr->initReplica(env, "chips");

 //Creates an instance of RegAndNotify and
 // obtains the pointer
 prodPtr->createaegPtr(env);
 regPtr = prodPtr->getRegPtr(env);

 // Creates a TableView object and register
 // its display method with the replica
 tblPtr = new TableView;
 regPtr->register(env, tblPtr, "display");

Suppose Storel and Store2 both sell chips. They will separately create a chips object as follows:

 // Creates a new product instance and
 // initialize it for replication
 chipPtr = new Product;
 chipPtr->initReplica(env, "chips") ;

Thus two additional replicas of the chips object exist in the system.

At various time intervals, Store1 and Store2 will update the number of chips packages sold:

chipPtr->updateNum(env, numberSold);

The updateNum method locks the replica and updates the numberSold attribute . It then releases the lock and propagates the update to other replicas by calling somrReleasePropagateOperation. In addition, it invokes the notify method on regPtr to notify all views that have registered an interest in the event.

Since the manager has registered an interest in the event, the display method in the TableView object will be invoked . Figure 4 summarizes the whole scenario.

SOM-Tool-Figure4.png

FIGURE 4. Summary of the chip sales scenario

Summary

Object-oriented techniques provide an abstraction where the interface is separated from implementations. As we saw in this paper, a tool can be thought of as an object having operations that map to tool functions. By wrapping a tool using IDL, the tool can interact with other tools in a heterogeneous environment. In addition, each operation in a tool can be used as a building block for new tools or configurations. The techniques discussed here can be further refined to handle more complicated tool communication scenarios. The result is a very flexible and extensible framework for building and integrating tools or applications for information systems.

References

  1. SOM Toolkit User's Guide, Release 2.0 Beta, May 1993.
  2. The Common Object Requester Broker Architecture and Specification, OMG Document 91.12.1, 1992.
  3. William Harrison, Harold Ossher, Mansour Kavianpour, eXtended Tool Integration Services ()TIS) Architecture Document, IBM Confidential Document, 1992.
  4. OS/2 Version 2.0 Volume 4: Application Development, IBM Document GG24-3774-00, 1992.
  5. OS/2 Programming Guide Volume II, Version 2.0, 1992.
  6. SOM Toolkit Reference Manual, May 1993.
  7. IBM AIX SDE Integrator/6000 Programmer's Guide, First Edition, May, 1992.
  8. Daniel Kehn, "Event Notification Mechanism", IBM Confidential, Invention Disclosure, December, 1992.

About the Author

Christina Lau is a staff development analyst in the Application Development Technology Center,IBM Canada Laboratory, Toronto. Her email addresses are torolab6(clau) or clau@torolab6.vnet.ibm.com on INTERNET.