Implementing IBM Director extensions

This section introduces the key components and classes for creating a basic IBM Director extension. We will describe the building blocks used by most extensions, the classes used to implement them, and essential features. You will be guided through the process of using these components and classes for creating an extension. The FileExplorer sample shows how each of these topics are used. This section is organized towards using the FileExplorer sample.

Subtopics

Related sample code

Managed objects

IBM Director is an object-oriented systems management framework. With IBM Director, you can find out information about distributed systems, invoke programs on those systems, and take actions upon them. Each of the systems or objects being managed is represented within IBM Director as a managed object. The most common managed object is a computer or server. Using IBM Director, you might invoke an application on a selected server. The application is a task and the selected server is the managed object.

The following classes are commonly used when dealing with managed objects.

TWGManagedObject
encapsulates common attributes such as the object's name and id. Each IBM Director managed object is assigned a unique id that is persistent across restarts of IBM Director.
TWGNativeManagedObject
A machine that is running the IBM Director Agent is a special type of managed object called a native managed object. The presence of the IBM Director Agent on a system enables functions not available with other managed objects.
fFilters IBM Director uses the concept of groups to allow users to organize the systems they manage into workable sets. Groups are collections of managed objects. They can be created manually using IBM Director Console tools or programmatically by IBM Director extension programs. Tasks applied to a group are performed on all of the members of the group.

The left panel of the IBM Director Console depicts groups. These are categorizations of managed objects that make it easier to work with distributed systems. For example, systems might be grouped by their OS type.

A filter is a set of selection criteria for managed systems. Groups are populated by applying filter criteria to the whole set of managed objects.

A dynamic filter defines the criteria used to query the IBM Director database tables and determine the managed objects that meet the criteria. The criteria can be combined using Boolean operations such as AND (all true) and OR (any true). A group is populated by the set of managed objects determined by these constraints and operations. For example, one group might be the set of managed objects that have Pentium III processors and have a particular device chipset. Your extension can create a new group using the TWGFilter class in the SDK.

Tasks

There are two definitions of tasks. The most visible is that on the IBM Director Console. On the IBM Director Console, tasks are operations that can be performed. They may show up in the right pane of the main console or in various pop-up menus. They may be invoked upon a specific managed object, a group of managed objects, or without reference to a managed object.

In the context of the SDK, a task is a container of data. A task is a set of code that you write using the TWGTask class and install on the IBM Director Server. Tasks are organized into subtasks which are individual units of work. Every task has at least one subtask. Every TWGTask has a properties file associated with it that configures the task to either run all the time or run only when needed. In the properties file, you also list the SubTasks your Task can handle. The SubTasks are the "tasks" that appear in the right pane of the IBM Director Console. When you use the IBM Director Console to drag a task from the right pane onto an object in the middle pane, IBM Director makes sure that the appropriate TWGTask is loaded, and it invokes the Task's subtaskActivate() method. subtaskActivate() receives a parameter which is a TWGTaskActivation object. The TWGTaskActivation object contains the SubTask and the list of managed objects the SubTask is to operate with.

Task functions are performed by code that resides on the console, the server, and the agent. These distributed components can work alone or in combination depending on how the task is structured and what function is being performed.

The IBM Director's task infrastructure handles the coordination of distributed processing among the console, server, and agent.

The base IBM Director product contains many tasks that perform common systems management functions. However, you can implement your own tasks to provide other management functions.

Console tasks

Tasks may be interactive or non-interactive. Interactive tasks display a GUI panel. The user might use the GUI to provide parameters to the agent program or to configure how the server task runs. A non-interactive task, runs without user input. A non-interactive task can be run immediately or can be scheduled to run later.

If a task has a console component, this is specified in the properties file for the task. The "GUI" parameter in the properties file indicates the class to load for the task's console component. In the FileExplorer properties file this line is


   GUI = class:com.BobCo.FileExplorer.BobCoFileExplorerGUI

The console component of the task is created by extending the TWGTaskFrame. This class ensures that the console component of your task interacts correctly with the rest of the IBM Director Console.

In the FileExplorer sample, you will see that BobCoFileExplorerGUI extends TWGTaskFrame. When the BobCoFileExplorerGUI class is loaded, the resulting GUI panel will have colors that match the rest of IBM Director and will automatically have a status bar at the bottom of the panel. Most of the work of actually creating the new console panel is done in the buildView() method of TWGTaskFrame. This is where JPanels and other Java visual objects are created.

Server tasks

Code running on the IBM Director Server can be implemented either in Java or in native C++ code, however, you are strongly encouraged to use only Java.

Which JVM to run in?

One of the fundamental decisions you'll have to make for your server task is whether it will run in the same Java Virtual Machine (JVM) as IBM Director or in its own virtual machine. Running in the same JVM is significantly more efficient in terms of system resources used. In some special cases where the new task uses large amounts of memory and runs to completion (does not remain resident), it might be appropriate to run in a separate JVM.

The FileExplorer sample implements a task that runs in the same JVM as the IBM Director Server. The code examples that follow will depict that case. There is also the FileExplorerJVM sample that runs in its own JVM.

The class that implements your server task is specified by the "Server" parameter in the properties file. This line has other parameters which are described under Task Properties and in the FileExplorer sample properties file. In the FileExplorer properties file this line is the following.


   Server = class:com.BobCo.FileExplorer.BobCoFileExplorerServer | auto-load:true | sameJVM:true

Recall that on the IBM Director Console, the right pane contains a list of operations a user can perform. Each operation is implemented as a subtask. A single server task can implement multiple subtasks. The FileExplorer sample has just one subtask and its name is specified in the properties file with this line.


   Subtask.0.ID = ExploreFiles
Running in the same JVM
If your server task is going to run in the same JVM as the IBM Director Server, then it must implement the TWGTaskServer interface. Tasks that run in the same JVM interact directly with the IBM Director's task-related objects. In the line above, you can see that the FileExplorer is designed to run in the same JVM as IBM Director.

The process of starting your task on the IBM Director Server is called task activation. When your task is started, its TWGTaskServer implementation is passed a reference to a TWGTaskActivation object. The TWGTaskActivation object contains a reference to the TWGSubtask being activated. Your server task might implement several subtasks, so the TWGTaskActivation object lets you know which one was started. The TWGTaskActivation object also contains a reference to a TWGLocalTaskClients object that contains the TWGLocalManagedObject objects being targeted by the user.

A simple way to think of this is the following:

Your subtask can query the state of the specified managed object(s). If the object is currently online, your subtask can perform its function.

Running in a different JVM
If your server task is going to run in a different JVM from the IBM Director Server, there are a number of differences for your implementation. See Running in a Separate JVM for details.

Inter-Process Communications (IPC)

IBM Director provides services that allow components to communicate with each other. These services support multiple lower level protocols whose details are hidden from the communicating components. By using these services, a server task can communicate with a console task, an agent task, with other IBM Director components, or with other IBM Director extensions.

A service requester initiates the communication and a service provider responds to it. Typically, the service requester is a console or server task, and the service provider is an agent; however, other combinations can be implemented as well. A service requester and a service provider communicate by sending messages through IBM Director's inter-process communication (IPC) facility. The service requester and provider can reside on the same machine or on different machines across a network.

IPC communications are implemented with ServiceNodes and Commands. Each end point of IPC communications is called a ServiceNode. Commands are message packages that are sent between ServiceNodes.

The ServiceNode class must be used for communication between:

The basic concepts for all IPC communications are:

A service requester and service provider each create their own service node to send and receive commands. Commands are sent, received, and replied to using methods of the ServiceNode class.

In this section, examples of service node communication are in the context of a server task requesting a service from an agent. Keep in mind that a server task might need to make requests of another server task. A given task can act as both a service requester and a service provider.

The following table summarizes the process flow for synchronous communications between a server requester and service provider. Asynchronous communication is similar and is described below.


 
Service Requester
(console/server)
 
Service Provider
(server/agent)
Create ServiceNodeFactory *    
Create ServiceNode   Create ServiceNode
Create Command 
  • Fill in data parameters.
  • Set destination address.
 
Wait for commands
   ServiceNode.ProcessCommands()
Send Command
   ServiceNode.SendCommand()
------>
 
CommandComplete() 
  • Retrieve return code. 
  • Process any returned data. 
  <------ 
ServiceNode.CommandReceived() 
  • Process command 
  • Retrieve command code and parameters. 
  • Set output parameters and return code. 

*Note: A service node factory needs to be created only if your service requester is a server task running in its own JVM.

Service requester actions

In this example, the service requester is an IBM Director Server component and the service provider is an IBM Director Agent.

Creating a ServiceNodeFactory

In Java, service nodes access the local IBM Director transport services. In the server component of the task, a service node factory must be created before a ServiceNode can be instantiated. If your server task is running in the same JVM as the IBM Director Server, then the service node factory will already have been created. However, if your server task is running in its own JVM, then you must create a service node factory. The ServiceNode.SetServiceNodeFactory() method specifies which factory object to use when creating new service nodes. To create a service node factory, use the ServiceNodeLocalImplFactory class.

Here is how this is done.


   // Set ServiceNode support to use local IPC implementation
   ServiceNodeLocalImplFactory snlif = new ServiceNodeLocalImplFactory();
   ServiceNode.SetServiceNodeFactory(snlif);

IBM Director services must be active on the system before you can create a service node factory. The service node factory should be set only once in any given JVM. Therefore, the following rules apply.

To complete the initialization process, a ServiceNode must be created and communicated to the IBM Director Server. The
TWGRemoteTaskManager class manages task related communications for the IBM Director Server and the task running in a separate JVM. The following code from the FileExplorerJVM sample shows how this is done.

   BobCoFileExplorerJVMServer me = new BobCoFileExplorerJVMServer();
   ServiceNode sn = new ServiceNode( serviceNodeName );
   TWGRemoteTaskManager.setServiceNode( sn, RTMcommandCode, me );

Creating a ServiceNode

Service nodes are implemented using the ServiceNode class. Typically, the service requester component sends commands to the service provider, but does not receive commands.

The FileExplorer sample uses the following constructor to instantiate a service node.


   public static final String serviceNodeName = "BobCoFileExplorer";
   ServiceNode sn = new ServiceNode(serviceNodeName);

The serviceNodeName must be a unique service node name on the local system.

Building a command

The Command class is used to encapsulate the data transferred between service nodes. You must specify two pieces of information to send a command.

The syntax for instantiating a Command is:


  Command(long cmdCode);
     where cmdCode is a unique command code used by the recipient to distinguish commands. 

The destination address for the command is set using the SetDestinationAddress() method. The syntax for this method is:


   cmd.SetDestinationAddress(address); 

The following example shows how to specify a destination address when sending a command to an IBM Director Agent. When sending commands to your task's console, to your task's server, or from your task's server to another IBM Director Server, you need only specify a service node name as the destination address. The service node name for the console and server must be unique. Service nodes that are created on the IBM Director Console or the IBM Director Server can be viewed as having the same logical address. IBM Director knows which protocol and which address to use when routing commands to these non-agent components. In the example below, the destination address would be only "BobSN".

Example:

 
  cmd.SetDestinationAddress("TCPIP::127.0.0.1::BobSN");

Below is the FileExplorer code to set the destination address. In the FileExplorer sample, these steps are implemented across several methods. They are combined here to illustrate more clearly how a destination address is created.

Information about each TWGNativeManagedObject is maintained by the IBM Director Server. A task can create a shadow of that information. This allows the task to work with some attributes of the native managed object while not duplicating the entire managed object in the task. Changes to the managed object are reflected back to the shadow object.


   public  static final String clientAddressSuffix = "::BobCoB";
   private static TWGNativeAddressEntryShadow nativeAddrShadow = new TWGNativeAddressEntryShadow();
   public  static final int getClientLocalFixedDrives = 0xB0BC0001;

   BobCoTaskActivation(TWGRemoteTaskActivation act) {

     // Command cmd was instantiated elsewhere with command code.
     // Command cmd = new Command(getClientLocalFixedDrives);

     Enumeration e = act.getClients().enumerateClients();
     TWGRemoteManagedObject  rmo = (TWGRemoteManagedObject)e.nextElement();
     ... 
     byte[] addrEntry = rmo.getAddressEntryRecord();
     nativeAddrShadow.initAddressEntry(addrEntry, 0, addrEntry.length);

     String ipcPath = nativeAddrShadow.getIPCPath();
     cmd.SetDestinationAddress(ipcPath+clientAddressSuffix);
   }
Specifying input parameters on commands
The command code uniquely identifies a command, but you can also send data with the command. This is done by adding input parameters to the command prior to sending it. The Command method is AddInputParm(). Commands can have any number of input parameters

The simplest syntax of the AddInputParm() method is:


   void AddInputParm(byte[] data);
Example:

   String msg = "Hello world";
   byte[] msgBuf  =  new byte[ IntelByteBuffer.GetUTF8(msg) ];
   cmd.AddInputParm(msgBuf);

Note: When communicating with C++ service nodes, the IntelByteBuffer class must be used for command input and output parameters. IntelByteBuffer is a class for creating or interpreting Intel byte ordered arrays of C data types. The IntelByteBuffer class "flattens" Java objects so they can be passed as byte arrays.

If the sending and receiving service nodes are both implemented in Java, then using the IntelByteBuffer class is not required. Instead, you can use Java's built-in serialization ("flattening") mechanism to add a Java object to a command. The Command class method is AddObjectAsInputParm(). The only requirement to use this method is that the object you add as a parameter must implement the java.io.Serializable interface. This makes it very convenient for sending Java objects between service nodes.

Example:


   public Command buildMyCommand( long cmdCode, String parameter )
   {
     Command cmd = new Command( cmdCode );
     cmd.AddObjectAsInputParm( parameter );
     return cmd;
   }

Sending the command

Commands are sent using methods of the ServiceNode class. Commands can be sent synchronously or asynchronously. Both approaches route the command to the service provider's CommandReceived() method and invoke the server requester's CommandComplete() method when the reply is returned. CommandReceived() is a method of the ServiceNode class. CommandComplete() is a method of the Command class. Asynchronous processing is the preferred approach if a large amount of processing is required.

The ServiceNode SendCommand() method sends a command synchronously. It executes in the caller's thread, and therefore blocks until the command's CommandComplete() method returns and any Command Complete Listeners have returned. We'll talk about CommandCompleteListeners a bit later.

The ServiceNode SendAsynchCommand() method sends the command and returns immediately, allowing the calling thread to continue. Just as with the synchronous case, the command's CommandComplete() method is called after the service provider sends a reply to the command.

Both SendCommand() and SendAsynchCommand() take a reference to a Command object as a parameter. References to the Command object are dropped when the CommandComplete() method completes. Garbage collection can then occur on the Command object. In Java, Commands should not be reused. A new Command should be instantiated each time one is sent.

In most cases, SendAsynchCommand() is the recommended method because the sending process can continue to do work while the command is being processed.

Example of synchronous send:

 
   sn.SendCommand(cmd);
     where sn is a ServiceNode and cmd is a Command.

Example of asynchronous send:


   sn.SendAsynchCommand(cmd);

The following table summarizes the process flow for asynchronous communication between a service requester and service provider.


 
Service Requester
(console/server)
 
Service Provider
(server/agent)
Create ServiceNodeFactory *    
Create ServiceNode   Create ServiceNode
Create Command 
  • Fill in data parameters.
  • Set destination address.
 
Wait for commands
   ServiceNode.ProcessCommands()
Send Command Asynchronously
   ServiceNode.SendAsynchCommand()
   Continue other processing.
------>
 
CommandComplete() 
  • Retrieve return code. 
  • Process any returned data. 
  <------ 
ServiceNode.CommandReceived() 
  • Process command 
  • Retrieve command code and parameters. 
  • Set output parameters and return code. 

*Note: A service node factory needs to be created only if your service requester is a server task running in its own JVM.

Processing the results

To be notified when a command has completed, you create a class that extends the Command class, and implement the CommandComplete() method. This method is invoked when the command reply is returned.

When the CommandComplete() method is invoked, a return code, set by the service provider in its ServiceNode ProcessCommands() method, is made available. The return code can be retrieved with the following method:


   long ReturnCode();

The service provider can set any return code it desires; however, the Command class defines return codes for common errors. Some of these predefined return codes are the following.


   CMDRET_SEND_FAILED
   CMDRET_SECURE_FAIL
   CMDRET_SEND_TIMEOUT
   CMDRET_SERVICEFAILED
These predefined return codes are set by IBM Director, and your extension should never set them explicitly. Your extension can set its own return codes using different values.

The service provider can also attach parameters to the reply. The service requester can determine how many data parameters have been returned with the following Command method.


   int NumOutputParms();

Individual output parameters can be retrieved with the OutputParm() method of the Command class.

Example of a CommandComplete() method:


   public void CommandComplete() {
   // Zero return code means success.
   if (ReturnCode() == 0) {
     System.out.println("Command succeeded.\n");
     byte [] buffer;
     for (int i=0; i < NumOutputParms(); i++){
       buffer = OutputParm(i);
       System.out.println("OutputParm(" + i + ") = " + buffer + "\n");	
     }
     else {
       System.out.println("Command failed with ReturnCode = " + Long.toHexString(ReturnCode()) + "\n");
     }
   }

When sending a command synchronously, the CommandComplete() method is not essential because the ServiceNode SendCommand() method returns to the next line of code only when the command is complete. The service requester can process the command reply in-line following the SendCommand() call. However, when sending a command asynchronously, the CommandComplete() method is essential. This is because the command will not be complete when the SendAsynchCommand() call returns. The CommandComplete() method is the callback method IBM Director invokes when the command reply is received.

CommandCompleteListeners

In addition to the Command object itself handling the command completion, you can register a CommandCompleteListener object to be invoked when the command completes. To do this, you implement the CommandCompleteListener interface.

The CommandCompleteListener interface defines a single method that is invoked when a command completes.


   void CommandComplete( Command );

To register your CommandCompleteListener class to handle the completion of a command, you call the following Command method.


   cmd.setCommandCompleteListener( CommandCompleteListener ccl );

Here is a summary of the steps:

The FileExplorer sample implements the CommandCompleteListener interface as shown below.


   public class GetClientDirectoryContentsCommand
     extends Command
     implements BobCoFileExplorerConstants, CommandCompleteListener
   {
     BobCoTaskActivation    act;
     Command                consoleCmd;
     /**
       * Constructor
       * @param act    the BobCoTaskActivation that this command is being  issued under
       * @param consoleCmd  the ConsoleRequestDirectoryContentsCommand received
       *                    from the console that is causing this command to be
       *                    sent to the client.  Input parameters are taken from
       *                    this received Command, and output parameters received
       *                    from the client are sent back as output parameters in
       *                    the consoleCmd.
       */
       GetClientDirectoryContentsCommand(BobCoTaskActivation act, Command consoleCmd) {
         consoleCmd.PostponeReply();
         setCommandCompleteListener(this);
         ...
       }

     /**
       * Command completion callback.  This listener method is called when a Command instance
       * registered to this listener is completed.
       * @param cmd - Command instance of command which has completed
       */
       public void CommandComplete(Command cmd) {
         long rc = cmd.ReturnCode();
         if (rc == RETURN_OKAY) {
           for (int i=0; i < cmd.NumOutputParms(); i++) {
           ...
           }
         }
         ... 
       }
   }

Service provider actions

This section describes the programming steps required of a service provider to handle and respond to a command.

Creating a ServiceNode

Just like the service requester component, the service provider also needs to create a ServiceNode as its end point for communications.

Often, the agent will contain a class that extends the ServiceNode class. The following examples show how a ServiceNode is created for an agent. The first example shows how a ServiceNode is created using Java. The second example is from the FileExplorer sample and uses C++.

Using Java to create an agent service node

   public class MySubagent extends ServiceNode
   {
     private static final String BOBCO_SVCNODE_NAME = "BobCoB";
     public MySubagent()
       throws BadServiceNodeImplException, ServiceNodeException
     {
       super(BOBCO_SVCNODE_NAME);
     }
   }
Using C++ to create an agent service Node

   #define BOBCO_SVCNODE_NAME  "BobCoB"
   BobCoSvcNode *svcnode = NULL;

   class BobCoSvcNode : public ServiceNode
   {
     public:
     /* Constructor : just create service node with needed name */
     BobCoSvcNode() : ServiceNode(BOBCO_SVCNODE_NAME, FALSE) {};
     ...
   }

   int main()
   {
     /* Register to prevent unload on user logoff */
     ServiceNode::RegisterAsServiceBase();
     /* Create the service node for the process */
     svcnode = new BobCoSvcNode;
     /* If error creating service node, exit with error */
     if(!svcnode->Create()) {
       return 1;
     }
     /* Once service node is created, start processing commands */
     svcnode->ProcessCommands();
     /* Clean up service node */
     delete svcnode;
     svcnode = NULL;
     return 0;
   }

Note that the C++ agent code contains a main() function. Tasks implemented in C++ must be implemented to run as a separate executable. Therefore, they need a main() function. Additionally, C++ agents need to be configured using the SDK's twgsvcee.exe utility. With this utility you specify the name of your executable as well as other options for how to invoke the program. See Registering an Agent Service for more information on this utility. Agents implemented in Java do not need a main() method nor be configured with the twgsvcee utility.

Waiting for and processing commands

Service providers are typically command-driven. They initialize and wait for commands from service requesters. To enter the wait-for-commands loop, the service provider invokes the ProcessCommands() method of the ServiceNode class.

In the Java example, IBM Director will call the service provider's ProcessCommands() method on a new JVM thread. IBM Director obtains the name of the class that extends the ServiceNode class and implements the ProcessCommands() method from an agent properties file. For IBM Director Agents, this file is named class.Subagent, where class is the Java class that extends ServiceNode. For more information on setting up IBM Director Agents, see Extending the Java-based Agent.

The service node in the previous C++ example processes commands in the caller's thread. For C++ service providers, IBM Director invokes the service provider's main() function which then must call the ProcessCommands() method itself.

To enable command processing in the service provider, the ServiceNode class is extended and the CommandReceived() method is implemented. Commands received from other processes are routed to the service provider's ServiceNode.CommandReceived() method.

The incoming command is passed in as a parameter. After processing a command, CommandReceived() sets a return code using the SetReturnCode() method of the Command class.

The CommandReceived() method returns TRUE if the service node should continue processing and returns FALSE if the service node should be closed.

Here is an example, using the FileExplorer agent's CommandReceived() method.


   class BobCoSvcNode : public ServiceNode {

     /****************************************************************************/
     /* Process any received commands, and route to needed handlers              */
     /****************************************************************************/
     BOOL BobCoSvcNode::CommandReceived(Command &cmd) {
       char msg[100];
       sprintf(msg, "Command received: %08x", cmd.CommandCode());
       debug(msg);
       switch(cmd.CommandCode()) {
         case BOBCO_GET_LOCAL_FIXED_DRIVES:
           debug("Calling HandleGetDrives()");
           HandleGetDrives(cmd);
           break;
         case BOBCO_GET_DIRECTORY_CONTENTS:
           debug("Calling HandleGetDir()");
           HandleGetDir(cmd);
           break;
         default:
           return(FALSE); // This will cause the service node to close,
                          //  thus returning from the ProcessCommands call in main(), 
                          //  which will end the agent service.
       }
       return TRUE;
     }

   }
Handling Command parameters
The Command class has several methods for working with input and output parameters. This section describes some of the more frequently used methods. Refer to cmd.hpp for more information on the C++ methods.

The NumInputParms() method returns a count of the number of input parameters. The InputParm() method returns individual parameters passed in with the command. The AddOutputParm() method allows the service provider to pass data back to the service requester. The SetReturnCode() method can be used to indicate if the command succeeded or failed.

The following example shows how these methods can be used together.


   public void MyCommandHandler( Command cmd ) {
     IntelByteBuffer input;
     String strOdd = "Odd number";
     String strEven = "Even number";
     byte[] oddBuf = strOdd.getBytes();
     byte[] evenBuf = strEven.getBytes();
     // Loop over all input data and determine if each is an odd or even number.
     for (int i=0; i < cmd.NumInputParms(); ++i) {
       input = new IntelByteBuffer(  InputParm(i) );
       int val = input.ReadLONG();
       if ( ( (val / 2) * 2 ) == val )
         AddOutputParm( evenBuf );
       else
         AddOutputParm( oddBuf );
     }
     cmd.SetReturnCode( 0 );
     return;
   }

Sending a reply

The reply is sent automatically when the CommandReceived() method exits unless the more advanced options for postponing a reply are used.

Postponing a Command reply

Commands are generally processed within the receiver's CommandReceived() method. Once this method returns true, the CommandComplete() method and any CommandCompleteListeners for the command are called. There are times when you may want to defer command complete processing. For example, you might want to handle the command outside of the scope of the CommandReceived() method. This can be done by calling the Command class method PostponeReply(). By postponing a command reply, the CommandCompleteListeners are not called until the postponed reply is sent or the command times out.

This is useful when you want to process a command asynchronously on another thread or need to communicate with another computer. Within the CommandReceived() method you can call PostponeReply() on the Command object. You can then process the command on another thread. When command processing is complete, you call SendPostponedReply() on the receiver's service node and pass the command object whose reply was postponed. Calling SendPostponedReply() causes the command complete listeners to be called.

This situation commonly occurs when a console command is sent to a server, but the server must acquire some data before replying to the console. The server might have to issue its own command and receive data before responding to the console.

In the following code from the FileExplorer sample, the reply to consoleCmd was postponed. When processing of a different command (GetClientDirectoryContentsCommand()) is done, the reply to consoleCmd is completed.

Here is where the original consoleCmd reply is postponed.


   GetClientDirectoryContentsCommand(BobCoTaskActivation act, Command consoleCmd) {
     super(getClientDirectoryContents);
     this.act = act;
     this.consoleCmd = consoleCmd;
     consoleCmd.PostponeReply();
     setCommandCompleteListener(this);
     // copy path parameter from console's input parameter to the client's command
     byte[] buffer = consoleCmd.InputParm(0);
     AddInputParm(buffer, IntelByteBuffer.SIZEOF_LONG64,                                                                         buffer.length-IntelByteBuffer.SIZEOF_LONG64);
     System.out.println("BobCo sending GetClientDirectoryContentsCommand to client");
     act.sendAsynchToClient(this);
   }

And here is where the postponed reply to consoleCmd is finally sent.


   /**
     * Command completion callback.  This listener method is called when a Command instance
     * registered to this listener is completed.
     * @param cmd - Command instance of command which has completed
     */
   public void CommandComplete(Command cmd) {
     long rc = ReturnCode();
     System.out.println("BobCo GetClientLocalFixedDrivesCommand complete, rc="+rc+",                                            NumOutputParms="+NumOutputParms());
     consoleCmd.SetReturnCode(rc);
     if (rc == RETURN_OKAY) {
       for (int i=0; i < NumOutputParms(); i++) {
         consoleCmd.AddOutputParm(OutputParm(i));
       }
     }
     sn.SendPostponedReply(consoleCmd);
   }

Extensions

New tasks are added to IBM Director as extensions. When IBM Director starts up, it discovers extensions and loads them. This makes them available to be invoked by a user or on a schedule.

When the IBM Director Server starts, it searches for files of the form install path\classes\extensions\*.TWGExt. TWGExt files are text files that define extensions. While there are many properties that can be specified in this file, the only required property is twg.extension.classname. This property specifies the fully qualified name of a class that extends the TWGExtension class. See Task Properties for more information.

For the FileExplorer sample, this property is


   twg.extension.classname=com.BobCo.FileExplorer.BobCoFileExplorerExtension
   public class BobCoFileExplorerExtension extends TWGExtension
   {
     ...
   }

The specified class is loaded and instantiated during the IBM Director Server's initialization and the required methods are called.

Server extensions

New tasks are created in the InitClassInstances() method of the TWGExtension subclass. This is done by instantiating a new TWGDefaultTask object. The name of a properties file is passed as a parameter to the TWGDefaultTask constructor. Among other things, this properties file contains a unique Task ID.

The TWGTask class is an abstract class that contains data essential to the instantiation of a task. For example, it contains the name of the task, the name of the Java class or executable file that implements the task, the state of the task, and other task attributes. The TWGDefaultTask class is a default implementation of TWGTask that provides the basic methods needed by many tasks. TWGDefaultTask is a good place to start when creating a new task. In the FileExplorer sample, TWGDefaultTask is used. The FileExplorer task is initialized with values specified in a properties file. The fully qualified name of that properties file is passed to the TWGDefaultTask constructor.


   // If the task  has not already been defined, create it now.
   // Construct the task using the values in the File Explorer properties file.
   if (!TWGTask.isTaskID("BobCo|FileExplorer"))  {
     try {
       TWGDefaultTask bobCoTask = new TWGDefaultTask("BobCoFileExplorerTask.properties");
     } catch (TWGTaskCreationException e) {
         System.out.println("Task creation exception.");
         e.printStackTrace();
     }
   }

TWGExtension

The following methods of the TWGExtension class must be implemented by your extension. The methods are invoked in the following order. Each method in the list is invoked for all extensions before the next method is invoked for any extension. All extensions are thus initialized and terminated in parallel.
InitClassRegistration()
In this method, you call the RegisterClass( task ID ) method to tell IBM Director about the existence of your new classes.
InitClassInstances()
In this method you create default instances of the classes you registered above.
InitCompletion()
This method can contain any other processing your extension needs after the above methods have completed. When this method is invoked, you can be sure that the InitClassRegistration and InitClassInstances methods have been called for all extensions. This is important if your extension depends on other extensions having initialized themselves.
TermBegin()
In this method, you only do the processing needed before other extensions have done their complete termination processing.
TermComplete()
The extension completes all processing here, as necessary.

Creating a TWGDefaultTask on the server

Here is code from the FileExplorer sample where the TWGDefaultTask class is instantiated.


   public void InitClassInstances() throws TWGExtensionInitException
   {
     ...     
     try {
       TWGDefaultTask bobCoTask = 
         new  TWGDefaultTask("BobCoFileExplorerTask.properties");
     }
     catch (TWGTaskCreationException e) {
       System.out.println("Task creation exception!!!");
       e.printStackTrace();
       err = true;
     }
   }
Summary
To create a new task, the following things need to be done (using the FileExplorer sample).
  1. Create file install dir\classes\extensions\*.TWGExt
  2. Ensure that the TWGExt file contains the line "twg.extension.classname = com.BobCo.BobsExtension" where the file BobsExtension.java contains the class BobsExtension that extends the TWGExtension class.
  3. In the BobsExtension class, implement the InitClassInstances() method. In that method, instantiate a TWGDefaultTask.

Console extensions

The starting point for console extensions is the TWGTaskFrame class. TWGTaskFrame ensures that your task has some common visual characteristics with the IBM Director Console, such as colors and a task status bar. For tasks that have a console component, the properties file contains the "GUI" property. This property specifies the name of your console class that extends TWGTaskFrame and is the class to load for your console (GUI) task. When the task is invoked, IBM Director will load this console class in a separate thread.

For the FileExplorer sample here is how this is coded.

BobCoFileExplorerTask.properties contains the following line.


   GUI = class:com.BobCo.FileExplorer.BobCoFileExplorerGUI

The BobCoFileExplorerGUI class starts out as follows.


   public class BobCoFileExplorerGUI
     extends TWGTaskFrame
     implements ActionListener
   {
     ...
   }

When IBM Director loads your GUI class, the TWGTaskFrame constructor is called. After the constructor completes, IBM Director will call the setTaskActivator() method. That method in turn calls the pInit() method. pInit is used to initialize non-GUI items. In fact, in the pInit() method you can control whether the GUI frame is actually displayed or not. For example, if initialization fails, you can prevent the GUI from appearing. The FileExplorer sample uses the pInit() method to create the console's service node.

Finally, IBM Director calls the consoleStart() method which in turn calls the buildView() method. The FileExplorer sample implements the buildView() method to create the Java panels, buttons, and so on for its GUI.

Here is the FileExplorer pInit() method.


   /**
     * Initialization routine for setting up non-gui items.
     */
   public boolean pInit( ) {
     boolean rc = true;
     try {
       sn = new ServiceNode();
     } catch (BadServiceNodeImplException snix) {
       System.out.println("BobCo GUI Error:  BadServiceNodeImplException while creating ServiceNode");
       rc = false;
     } catch (ServiceNodeClosedException sncx) {
       System.out.println("BobCo GUI Error:  ServiceNodeClosedException while creating ServiceNode");
       rc = false;
     } catch (ServiceNodeException snx) {
       System.out.println("BobCo GUI Error:  ServiceNodeException while creating ServiceNode");
       rc = false;
     }
     return rc;
   } // end pInit

Here is the FileExplorer buildView() method.


   /**
     * Create the client pane and return it.
     */
   public Container buildView( ) {
     // Set the frame's icon to the flashlight
     setIconImage(UFImageLoader.getImage("/images/find16.gif"));
     // Construct the top level client area pane that all panes are
     // placed into.  This code is provided as a temp for frames.
     JPanel returnPanel = new JPanel( );
     returnPanel.setBorder( new BevelBorder( BevelBorder.LOWERED ));
     returnPanel.setLayout(new BorderLayout());

     // Load and set the frame's title from the task resources.
     TWGTaskActivator ta = getTaskActivator();
     setTitle(ta.getTask().getName()+ta.getTargetTitle());
     LongValueSet moid = ta.getMoid();

     /* Put the Tree in a scroller. */
     tree = new BobCoFileExplorerTree(this, moid.GetValue(0));
     JScrollPane        sp = new JScrollPane();
     sp.setPreferredSize(new Dimension(300, 300));
     sp.getViewport().add(tree);

     /* And show it. */
     returnPanel.setLayout(new BorderLayout());
     returnPanel.add("Center", sp);

     // Create a horizontal panel for the buttons.
     JPanel buttonPane = new JPanel();
     buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));

     // Put glue in first so that all buttons are justified left.
     buttonPane.add(Box.createHorizontalGlue());

     // Create cancel button and add to button panel.
     JButton cancelBtn = new JButton( "Cancel" );
     buttonPane.add( cancelBtn );
     cancelBtn.addActionListener( this );

     returnPanel.add( "South", buttonPane );

     return( returnPanel );
   } // end buildView

Running in a separate JVM

For most tasks, it is more efficient and preferable to run in the same JVM as the IBM Director Server. However, in some cases it may be preferable to run a task in its own JVM. Specifically, if your task uses large amounts of memory, runs to completion quickly, and does not need to stay resident, then it may be preferable to run the task in a separate JVM. This section describes the implementation differences when running in the same or a separate JVM.

Properties file

Your server task properties file will have the following differences. See
Task Properties for a description of server task properties and their values.

Imported classes

The Java file where you implement your server task will need to import different files depending on whether your task runs in the same JVM or a separate JVM. Notice that tasks that run in the same JVM as IBM Director will typically use the Local version of a class, while tasks that run in a separate JVM will use the Remote version of a class. This naming convention reinforces the location of the task with respect to IBM Director.

Interface to implement

The interface your server task implements will differ with your sameJVM setting.

Similar methods with different names

Since different interfaces are implemented base on your selection for sameJVM, the methods you implement to perform a given function will be different.

ServiceNode creation and initialization

The process for creating a ServiceNode differs with the setting of sameJVM.

Termination processing

Other options

This section describes other programming functions for IBM Director extensions.

Command timeout

Command objects have a time out value that represents a reasonable amount of time to perform the operation. IBM Director's default command timeout value is 15 seconds. The time out value is the elapsed time from when the command is sent to time the command is completed.

If a service provider is processing a command and the command's time out value has elapsed, the service requester's command complete processing occurs immediately for that command and the command's return code is set to CMDRET_SEND_TIMEOUT by IBM Director. Postponing a command reply has no effect on the time out processing for the command. A postponed reply allows command processing to continue asynchronously, but the command still must complete within the time out limit.

You can change a command's time out value using the Command class's SetTimeOut() method.

Targeted managed objects

Often a task is invoked against a particular managed object and might need to communicate with that managed object. We saw an example of this in the Service Requester Actions topic above. What that code was doing was communicating with a targeted managed object that handled service requester commands. In that example, we saw how the service requester determined the destination address of the targeted managed object. Here we explain a little more about the classes and methods used to do that.

If you need to communicate with the targeted managed object, you perform the following steps.

  1. Get a reference to the managed object (TWGManagedObject).
  2. Check the state of the TWGManagedObject by calling its getState() method.
  3. Create a new instance of a TWGNativeAddressEntryShadow object.
  4. Get the address entry record from the TWGManagedObject by calling its getAddressEntryRecord() method.
  5. Initialize the TWGNativeAddressEntryShadow object by calling its initAddressEntry() method, passing in the address entry record.
  6. Finally, use the TWGNativeAddressEntryShadow getIPCPath() method to obtain the addressing information.

Example:


   class MyCommand extends Command
   {
     public MyCommand ( long objectID ) {
       ServiceNode sn = new ServiceNode();
       ...
       int pid = TWGPersistentObject.toPersistID( objectID );
       TWGManagedObject manObj = TWGManagedObject.getManagedObjectByID( pid );
       if (manObj.getState != TWGManagedObject.MOSTATE_NORMAL_ONLINE){
         ... handle error ...
       }
       byte[] address = manObj.getAddressEntryRecord();

       TWGNativeAddressEntryShadow shadow = new TWGNativeAddressEntryShadow();
       String ipcPath;
       try {
         shadow.initAddressEntry( address, 0, address.length );
         ipcPath = shadow.getIPCPath();
       } catch (Exception x) {
         ... handle exception ...
       }      

       SetDestinationAddress( ipcPath + "::MyAgent" );
       SetTimeOut( 20000 );
       ... attach parameters to command ...

       try {
         sn.SendAsynchCommand( this );
       } catch (Exception x) {
         ... handle exception ...
       }
     }
     ...
   }

Shadow objects

For efficiency, IBM Director uses the concept of shadow objects. Managed objects are maintained by the IBM Director Server. It is common that an IBM Director extension needs to access some of the information contained in a managed object. Rather than copy the entire object to the remote component (e.g., a console task), IBM Director provides a smaller shadow of the object on the server. The shadow object consumes less space and can be transmitted faster than the entire object. Whenever the object at the server is updated, IBM Director automatically updates the corresponding information in the shadow object.

TWGConManagedObject instantiates a shadow object. Here is an example of how it might be used.


   public String getObjectName( long objectID )
   {
     TWGConManagedObject cmo = null;
     String name = "";
    
     cmo  = (TWGConManagedObject)TWGConObject.FindObject( objectID );
     name = cmo.getName();
     return name;
   }

Agent-initiated Commands

Commands are commonly sent from a server to an agent. For most services, this kind of request/response relationship is sufficient. However, in cases where it is necessary for an agent to initiate commands to a server, the security mechanisms on the two systems can cause problems. Normally, security is set up to allow remote access to a system's services only for trusted managers. This relationship is not mutual; an IBM Director Server task is not normally set up to allow remote access to its services.

One way to address this is to use the SendAuthorizationCommand class. SendAuthorizationCommand is a special command to temporarily authorize one system to send commands to another system.

In Java:

 
   public SendAuthorizationCommand(int timeout);

In C++:


   virtual SendAuthorizationCommand(ULONG time_out);

The sending system is authorized to send further commands until the time out occurs. If a time out of zero is specified, an outstanding authorization is canceled.

Predefined constants

Several IBM Director classes define constants that are commonly used with other IBM Director classes.
- com.tivoli.twg.engine.TWGTaskConstants
Contains constants used by TWGTask and related classes.
- com.tivoli.twg.engine.TWGManagedObjectConstants
Contains constants used by TWGManagedObject, TWGConManagedObject, and similar classes.

Error logging

The TWGRas class provides a standard mechanism for your extension to write trace and log information. The methods of this class are static; however, the TWGRas.initialize() method must be called prior to using the other TWGRas methods.