Tivoli Directory Integrator, Version 7.1.1

Developing a Connector

Implementing the Connector's Java source code

All Tivoli® Directory Integrator Connectors implement the "com.ibm.di.connector.ConnectorInterface" Java interface. This interface provides a number of methods to implement addressing all the possible ways of using a Connector within Tivoli Directory Integrator. Usually the Connectors you write will not require all the options provided by Tivoli Directory Integrator and you will actually need to implement only a subset of the methods presented in the "ConnectorInterface" interface. It is the "com.ibm.di.connector.Connector" class that makes this possible.

"com.ibm.di.connector.Connector" is an abstract class implementing "ConnectorInterface" that contains core Connector functionality (for example processing of Connector's configuration) and also provides empty or default implementation to many of the methods from "ConnectorInterface". This allows you to start implementing your Connector by subclassing "com.ibm.di.connector.Connector" and focusing on (implementing) only those methods from "ConnectorInterface" that provide value in your case, and that are actually necessary for your Connector.

Listed below are the "ConnectorInterface" methods that build the backbone of a real Connector, and which you will usually need to implement:

Connector's constructor
Required for all Connector modes.

In the constructor you will usually set the name of your Connector (using the "setName(...)" method) and define what modes - Iterator, Lookup, AddOnly, Server, Delta etc. - that your Connector supports (using the "setModes(...)" methods). For an example of a Connector implementation, look at the "DirectoryConnector.java" Connector included in this package.

public void initialize (Object object)
This method is called by the AssemblyLine before it starts cycling. In general anybody who creates and uses a Connector programmatically should call "initialize(...)" after constructing the Connector and before calling any other method.

Usually the "initialize(...)" method reads the Connector's parameters and makes the necessary preparations for the actual work (creates a connection, etc.) based on the parameter values specified.

public void selectEntries ()
Required for Iterator mode. This method is called only when the Connector is used in Iterator mode, after it has been initialized.

Place in "selectEntries(...)" any code you need to execute prior to actually starting to iterate over the Entries. When the Connector operates on a database, that code could be an SQL SELECT query that returns a result set; when the Connector operates on an LDAP directory, that code could be a search operation that returns a result set. The result of the "selectEntries(...)" (result set, etc.) is later used by the "getNextEntry(...)" method to return a single Entry on each call/AssemblyLine iteration. Of course you might not need any preparation to iterate over the Entries (as in the case with the FileSystem Connector) in which case there is no need to implement "selectEntries(...)". By subclassing "com.ibm.di.connector.Connector" you will inherit its default implementation that does nothing.

public Entry getNextEntry ()
Required for Iterator mode. This is the method called on each AssemblyLine's iteration when the Connector is in Iterator mode.

It is expected to return a single Entry that feeds the rest of the AssemblyLine.

There are no general guidelines for implementing this method - it all depends on the information this Connector is supposed to access. This method retrieves data from the connected data source and must create an Entry object and populate it with Attributes. For example, a database Connector would read the next record from a table/result set and build an Entry object whose Attributes correspond to the record's fields.

public Entry findEntry (SearchCriteria search)
Required for Lookup, Update and Delete modes. It is called once on each AssemblyLine iteration when the Connector performs a Lookup operation.

This method finds matching data in the connected system based on the "Link Criteria" specified in the Config Editor GUI. For example, a database Connector would execute a SELECT query with the appropriate WHERE clause based on Link Criteria and then build an Entry from the database record, in the same way as "getNextEntry()" does. Please consult the Java Docs for the structure of the SearchCriteria input parameter.

Use the following implementation pattern to achieve the above required Connector behavior: for each Entry found call Connector's "addFindEntry(...)" method. When finished, call "getFindEntryCount(...)" to get the number of Entries you have found - if it is 1, return the value returned by "getFirstFindEntry(...)" , otherwise return NULL.

For example: In a database Connector, "modEntry(...)" executes an SQL UPDATE query, using the Attributes of the entry parameter as database fields and the SearchCriteria in the search parameter to build the WHERE clause.

public void putEntry (Entry entry)
Required for AddOnly and Update modes. It is called once on each AssemblyLine iteration when the Connector is used in AddOnly mode, or for Update mode when no matching entry is found in the connected data source.

The goal of this method is to add/save/store the Entry object (passed in as parameter to this method) into the Connector's data source. So, a database Connector would execute an INSERT SQL statement using the Entry's Attributes' names and values and table fields names and values.

public void modEntry (Entry entry, SearchCriteria search, Entry old)
--or--
public void modEntry (Entry entry, SearchCriteria search)
Required for Update mode.

Before discussing the "modEntry(...)" method, a short clarification of the Update mode is necessary: When the AssemblyLine encounters a Connector in Update mode, it will first execute Connector's "findEntry(...)" method using the specified Link Criteria. If "findEntry(...)" finds no matching Entry, then the Connector's "putEntry(...)" method is called to add the Entry to the data source. If "findEntry(...)" finds exactly one Entry, the Connector's "modEntry(...)" method is called. Finally, if the "findEntry(...)" method finds more than one Entry, the "On Multiple Entries" hook is executed and depending on what the user specified either no Connector's calls are invoked or one of "putEntry(...)", alternatively "modEntry(...)" methods is invoked.

As seen above there are two variants of the "modEntry(...)" method - one with three and one with two input parameters. The two parameters that you get in both cases are: entry, the output mapped conn Entry, ready to be written to the data source; and search, the SearchCriteria to be used to make the modify call to the underlying system. When this method is invoked by the Update mode logic (the "update(...)" method of an AssemblyLineComponent), this will reference the actual SearchCriteria built from the Link Criteria (after evaluation of Attribute values, etc.).

The extra parameter is old. This is the original Entry in the data source as it looks right now, before the modification is applied. This information might be useful in certain cases like "rename" operations when you need the old name to perform the rename.

It is up to you to decide which of these methods to use. Of course you could implement both of them. One of them is sufficient for your Connector to support Update mode.

Following the analogy with the database Connector, "modEntry(...)" would execute an SQL UPDATE query, using the Attributes of the entry parameter as database fields and the data from the search parameter to build the WHERE clause of the SQL query.

public Entry queryReply (Entry entry)
Required for CallReply mode. It is called once on each AssemblyLine iteration when the Connector is used in CallReply mode.

This mode is appropriate when your Connector participates in some kind of request-response communication. The output mapped entry parameter contains the data necessary to perform the "call" or "request" part of the operation. For example, the Web Service Connector builds and transmits a SOAP call based on the Attributes in entry. The method then must build and return an Entry object from the reply/response data.

public void deleteEntry (Entry entry, SearchCriteria search)
Required for Delete mode.

Delete mode will cause the Connector to perform a "findEntry(...)" to try and locate the Entry to be deleted. If the "findEntry(...)" method returns exactly one Entry, the "deleteEntry(...)" method is called with this Entry and the Link Criteria used in the Lookup as parameters. If "findEntry(...)" returns zero or more than one Entries the corresponding Connector hooks are called. Depending on what the user specified in the script code, either nothing more is executed or the "deleteEntry(...)" method is called with the Entry specified by the user script via the AssemblyLineComponent method setCurrent( entry ). Unless the current entry is set in the On Multiple Found hook, nothing more happens, and control passes down the AssemblyLine.

Back to our database Connector example, "deleteEntry()" would execute an SQL DELETE statement.

public ConnectorInterface getNextClient()
The "getNextClient()" method is used for Connectors in Server mode to accept a client request. This method usually blocks until a client request arrives. When a request is received it creates and returns a new instance of itself. This new instance is then handed over to the AssemblyLine that spawns a new AssemblyLine thread for that Connector instance. The AssemblyLine then calls the "getNextEntry()" method on this new Connector instance in the new thread until there are no more Entries for processing. Right after the "getNextClient()" method returns and the AssemblyLine spawns a new thread to handle the client request, the AssemblyLine calls again "getNextClient()" to accept the next client request.

Since Connectors in Server mode handle client requests which require a response, the AssemblyLine will call the "replyEntry(...)" Connector method at the end of the AssemblyLine. Use this method to place your code that returns response to the client. In case your Connector might need to return multiple responses on a single request you can code the "putEntry(...)" method so that it returns an individual response Entry. In this case it will be the responsibility of the AssemblyLine developer to call the "putEntry(...)" method of the Connector by scripting and this fact has to be documented in the Connector's documentation.

When implementing a Connector in Server mode, you also have to take care about terminating the Connector on external request. Place your termination code in the "terminateServer()" method. Take into consideration that this method can be called on the master Connector instance that accepts client requests and also on a child Connector instance processing a client request. In both cases proper termination should happen: it is usually a good termination practice to stop accepting new requests from the master Server Connector instance but let all child Connectors finish their processing. The "terminateServer()" method usually sets some flag that is checked by the "getNextClient()" method of the master Server Connector instance - if termination is requested the "getNextClient()" method will return NULL. This is a signal to the AssemblyLine that this Server Connector has terminated and the AssemblyLine will not call anymore its "getNextClient()" method.

public void terminate ()
The "terminate(...)" method is called by the AssemblyLine after it has finished cycling and before it terminates. You would put here any cleanup code, that is, release connections, resources that you created in the "initialize(...)" method or later during processing.

The methods listed above are the core ConnectorInterface methods that bring life to your Connector. And remember, you only need to implement the methods corresponding to the Connector modes that your Connector will support.

Modes to methods mapping

When you write a connector you should take into account that users may call the connector methods in no particular order. This means you should have sanity checks on each method in case the connector requires certain methods to be called before others. From a Tivoli Directory Integrator server perspective the methods called on a connector is determined by the value of the mode parameter. In this section you will see the call order for methods for each mode. When the AssemblyLine uses a connector it always calls initialize() as the first method before any other methods are called. However, it is possible that the user sneaks in a call to other methods before this is called.

Mode Methods Comments
Iterator
Initialize()
selectEntries()
getNextEntry()
terminate()
getNextEntry should return NULL to signal end of input.
AddOnly
Initialize()
putEntry()
terminate()
Lookup
nitialize()
findEntry()
terminate() 
If you find more than one entry you should use clearFindEntries() and addFindEntry() for each entry found in this method.
Delete
Initialize()
findEntry()
deleteEntry()
terminate()
Update
Initialize()
findEntry()
putEntry()
modEntry()
terminate()
If findEntry returns a single entry, modEntry will be called. If findEntry returns null, putEntry will be called.
Delta
Initialize()
findEntry()
putEntry()
modEntry()
deleteEntry()
terminate()
See note below.
How to implement a Delta mode Connector

First of all, to enable delta mode for your connector you must add "Delta" to the list of supported modes. Delta mode is a bit special since it can be emulated by the AssemblyLine or directly implemented by the connector. Emulated delta mode simply means that the incremental update is generated by the AssemblyLine based on what it is returned by the findEntry() method and what is being written to the target system. If the target system supports incremental updates you can code your connector by translating a delta Entry object to the underlying protocol of your connector. In the latter case you configure your connector by returning true in the isDeltaSupported() method. This will cause the AssemblyLine to forward the delta Entry directly to your connector's putEntry(), modEntry() or deleteEntry() methods, bypassing findEntry() and the algorithm to compute the delta entry.

Query Schema behavior

In Tivoli Directory Integrator 7.1.1 a default behavior for schema discovery is implemented for all Connectors. This default behavior is used by Connectors that do not implement their own logic of schema processing, that is, do not override the querySchema(Object) method. The default behavior depends on the Parsers that a Connector has (if any).

Static Schema
Each Connector static schema is determined by its own static schema plus the static schema of the Parser it uses. Both schemas are displayed in the Connector Input/Output Map.

Under a static schema we understand the schema that is configured in the tdi.xml file for the Connectors and Parsers. To add a static schema definition to your Connector, Parser or Function definition file, add a <Schema> tag inside the <Connector>, <Parser> or <Function> element. The name of the Schema should be "input" or "Output".

For example:

<Connector name="ibmdi.Mailbox">
	<Schema name="Input">
		<SchemaItem>
			<Name>mail.body</Name>
			<Syntax>javax.mail.Multipart</Syntax>
		</SchemaItem>
	</Schema>
	<Schema name="Output">
		<SchemaItem>
			<Name>Flag.Answered</Name>
			<Syntax>boolean</Syntax>
		</SchemaItem>
	</Schema>
</Connector>
Dynamic Schema
This type of schema will require an interaction with the System to which the Connector is configured to connect. To do so the Connector may ask the configured Parser if it could discover a schema by using its configuration.
Implementing querySchema()

ConnectorInterface also provides other methods that address aspects of the possible use of a Connector and which you might want to implement. One example is "querySchema(...)". This method returns the schema of the connected data source . If you implement it, the Config Editor presents the returned values as the Connector's Schema.

These return values are stored as a Vector of Entry objects, one for each column/attribute in the schema. For example, a database Connector would return one Entry for each column in the connected database table.

Each Entry in the Vector returned should contain the following attributes:

name The name of the attribute (column, field, etc.) Required.
syntax The syntax (like VARCHAR or TIMESTAMP) or expected value type of this attribute. Optional

Specified by: querySchema in ConnectorInterface

Parameters: source - The object on which to discover schema. Usually NULL. This may be an Entry or a string value.

Returns: A vector of com.ibm.di.entry.Entry objects describing each entity, or in the case of error, a java.lang.Exception is thrown.

Using a Parser in your Connector

If your connector extends the base implementation of the TDI connector (com.ibm.di.connectors.Connector), you can invoke the initParser() method to initialize the associated parser:

  /**
   * Initialize the connector's parser with input and output streams. If the parser
   * has not been loaded then an attempt is made to load it. The input and output objects
   * may be Stream objects (InputStream,OutputStream), java.io.Reader object, String object,
   * java.net.Socket, byte and character array objects.
   * 
   * @param  is  The input object.
   * @param  os  the output object.
   * @exception  Any exception thrown by the parser
   * @see    #getParser
   */
  public void initParser (Object is, Object os) throws Exception;

You have to provide the input and/or output streams the parser will use for its read/write operations. The mode of your connector typically determines which way the flow goes (note that your initialize(Object obj) connector method will have the connector mode in the "obj" object). You are not required to initialize the parser at the time of connector initialization, but you should do so unless there is a good reason to initialize it elsewhere. In any case you should invoke the initParser() method to properly initialize the parser with logging objects, debug flags and other standard TDI objects/behaviors.

The parser can be chosen either by the user or you can hide the parser selection and either provide the configuration in your "tdi.xml" file or programmatically configure the parser in your connector (or both).

  1. Let the user choose the parser.

    In this case you must set the parameter "parserOption" in your connector's "tdi.xml" file to the value "true". Once this field is defined the selection of the parser is delegated to the user through a standard user interface (note that you can prefill the parserConfig section of your tdi.xml file with a default parser). Here is a snippet from the FileSystem connector's "tdi.xml" file showing "parserOption" as "Required", which means the connector requires a parser (that is, an error is thrown if none is defined in the configuration):

    <Connector name="ibmdi.FileSystem">
    	<Configuration>
    	...
    		<parameter name="parserOption">Required</parameter>
    	</Configuration>
    </Connector>

    The value for the "parserOption" parameter can be "Required", "Useless" (no parser allowed) or "Optional".

  2. Use a predefined parser using the "tdi.xml" file.

    You can include the parserConfig section in your "tdi.xml" file if you always use the same parser, for example if you inherit from the CSV Parser:

    <Connector name="myconnector">
    	<Configuration>
    		<parameter name="parserOption">Required</parameter>
    	</Configuration>
    	<Parser>
    		<InheritFrom>system:/Parsers/ibmdi.CSV</InheritFrom>
    		... Optional parameter values to make the parser functional
    	</Parser>
    </Connector>
  3. Configure the parser at runtime.

    Your connector has access to the ConnectorConfig object via the Connector.getConfiguration() method. Through the ConnectorConfig object you can obtain the ParserConfig interface object for the connector. Use that object to configure the parser before you invoke the initParser() method:

    import com.ibm.di.config.interfaces.ConnectorConfig;
    
    	public void initialize(Object obj) throws Exception {
    
    		// Check mode
    		String mode = "" + obj;
    		boolean isIterator = mode.equals(ConnectorConfig.ITERATOR_MODE);
    
    		ConnectorConfig cc = (ConnectorConfig)getConfiguration();
    
    		// Get the parser config object
    		ParserConfig parser = cc.getParserConfig();
    
    		// -- use the csv parser and set the column separator parameter
    		parser.setParameter("parserType", "com.ibm.di.parser.CSVParser");
    		parser.setParameter("csvColumnSeparator", "\t");
    
    		if(isIterator)
    			initParser(inputStream, null);
    		else
    			initParser(null, outputStream);

    Once the parser has been initialized you can invoke the readEntry() and writeEntry() methods to translate com.ibm.di.entry.Entry objects to and from the stream format defined by the parser. You typically invoke the readEntry() method in your getNextEntry() method and the writeEntry method from your putEntry method. You obtain the parser interface handle through the getParser() method.

  4. Optional parser and dynamic reinitialization

    If your connector can function with or without a parser you can invoke the hasParser() method to determine whether a parser is configured or not:

    if(hasParser())
    			doSomething();

    If you use multiple instances of the parser during the life time of your connector you should close the parser interface to ensure data is written to the outputstream and that system resources are released. The methods used to re-initialize a parser can differ based on which parser you use but the following method calls should be sufficient for most parsers:

    		// Close parser to release system resources
    		if(getParser() != null)
    			getParser().closeParser();
    
    		// assuming you just got a new input stream ... reinitialize the parser
    		initParser(inputStream, null);

    When your connector is terminated it will automatically invoke the closeParser() method if one is in use by the connector.

Logging from a Connector

The com.ibm.di.connector.Connector class that your Connector will be extending, has a number of methods to enable you to log messages to the AssemblyLine's configured log files. The simplest way of logging is using one of the following methods:

 /**
   * Log a message to the connector's log. The message is prefixed by the connector's
   * name.
   * 
   * @param  msg  The message to write to the log
   */
  public void logmsg(String msg)
  
  /**
   * Log a debug message to the connector's log
   * 
   * @param  msg  The message to write to the log
   */
  public void debug(String msg)

You can call these methods with code like

 	logmsg("initializing my connector");

This will cause your string to be issued to the AssemblyLine's configured log appenders, at INFO level. If you want to do more advanced logging, the com.ibm.di.connector.Connector class also has this field:

 /**    
   * The log object for logging messages
   */
   protected  com.ibm.di.server.Log        myLog;    

This com.ibm.di.server.Log class has many methods for logging. You could therefore use the myLog object to do logging like this:

  	myLog.logerror("Something very bad happened");

This issues a message to the log(s) at ERROR level. There are corresponding methods for logging at different levels, like loginfo() and logfatal().

Building the Connector's source code

When building the source code of your Connector, set up your CLASSPATH to include the jar files from the "jars/common" folder of the IBM® Tivoli Directory Integrator installation. At minimum you would need to include "miserver.jar" and "miconfig.jar".

Note:
When integrating your Java code with IBM Tivoli Directory Integrator, pay attention to the collection of pre-existing components that comprise IBM Tivoli Directory Integrator, notably in the jars directory. If your code relies upon one of your own library components that overlap or clash with one or more that are part of the Tivoli Directory Integrator installation there will most likely be loader problems during execution.

Implementing the Connector's GUI configuration form

Introduction

When you create a custom Tivoli Directory Integrator component you also have to provide an additional file that describes your component to TDI. This file is located at the root of your jar file and is named tdi.xml. The syntax and contents of this file is described in this document.

The first part of this section explains the format of the tdi.xml file and also shows the minimum requirements for a component definition file.

The second part of this section focuses on the form definition and the various options you have when you define a form. This form definition is used by the Tivoli Directory Integrator Configuration Editor to let the user configure your component. While the UI options in the form definition are basic and somewhat limited, you can still perform advanced operations using your own custom java based UI components as well as associating scripts with form events.

tdi.xml file format

The files are created in XML format looking much the same as a Tivoli Directory Integrator Configuration file.

A skeleton for the file could look something like this:

<?xml version="1.0" encoding="UTF-8">
<MetamergeConfig version="7.0">
	<Folder name="Connectors">
		<Connector name="CustomConnector">
			<Configuration>
				<parameter name="connectorType">com.acme.CustomConnector</parameter>
				... more parameters ...
			</Configuration>
		</Connector>
	</Folder>
	<Folder name="Forms">
		<Form name="com.acme.CustomConnector">
			<TranslationFile>CustomConnector</TranslationFile>
			... many more elements which will be defined later ...
		</Form>
	</Folder>
</MetamergeConfig>

This defines a Connector named system:/Connectors/CustomConnector. The Java class that implements this Connector is com.acme.CustumerConnector.class.

Localization of labels and descriptions in this file can be provided by adding properties files with the locale identifier in the standard way. In this example, the properties file is CustomConnector.properties. Then the German version of this file would be CustomConnecter_de.properties, and the Brazilian Portuguese version would be CustomConnector_pt_BR.properties. The individual properties in these localized files take the same keys, but with localised values. Each line is of the format

key=value

Comments in these files can be included by starting the line with a # (hash).

Basic Component Definitions

When you first create your component definition file you add the main sections for the components your jar file contains. For each component you add a section where you as a minimum define Java class. The syntax is as follows for the three main components:

Table 65.
Component Type Minimum Section Contents
Connector
	<Folder name="Connectors">
		<Connector name="your_name">
			<Configuration>
				<parameter name="connectorType">your_javaclass_name</parameter>
			</Configuration>
		</Connector>
	</Folder>
Parser
	<Folder name="Parsers">
		<Parser name="your_name">
			<parameter name="class">your_javaclass_name</parameter>
		</Parser>
	</Folder>
Function
	<Folder name="Functions">
		<Function name="your_name">
			<Configuration>
				<parameter name="javaclass">your_javaclass_name</parameter>
			</Configuration>
		</Function>
	</Folder>

In addition you should always include a form definition for each of your components. This is to prevent the configuration editor to report errors of missing forms. If your component has no configurable parameters you should include a form that says so.

Note:
The current configuration object that the Form refers to is always the connectorConfig/parserConfig/functionConfig object. If you need to access the main component's parameters you should use the "config.getParent()" method to obtain for example the ConnectorConfig interface for the configuration.
Install Location

When you start either the configuration editor or the server there is a component called the Tivoli Directory Integrator Loader that runs through its configured jar directories looking for *.jar/*.zip files that contain an "tdi.xml" file at its root level. All the definitions in these files are put into the system namespace.

The locations of these files are:

When you put your jar file in either of these directories your component will show up in the configuration editor with the name you chose as part of the system namespace.

Note:
Adding your jar file to the CLASSPATH or PATH alone does not include it in the system templates and hence will not be visible to the user.

Form description

The form description is used to provide custom input panels for components. While most of the user interface in the configuration editor is static, most components need specific user interfaces to let the user define its behavior.

Component/Form Association

The form definition defines the input fields and labels that the configuration editor will build when you open the configuration for a component. The binding between the component (for example, connector, parser) and its form is through the Java class of the component. Using the example above, the connectorConfig has a "connectorType" parameter that defines the implementing class for the component (com.acme.CustomConnector). When a component of this type is presented to the user, the configuration editor will look for a form with the same name as the implementing Java class.

Form/Configuration Binding

When the form has been created it also has a binding object for each parameter to the configuration object. These binding objects will set the initial value of the input field (using the default value provided by the form if the configuration object returns null for the value) and also function as the controller between the input field and the configuration object. When the input field changes its value the binding will update the configuration object and vice versa. The configuration object is read and updated using the primitives of the configuration object (for example, BaseConfiguration.getParameter/setParameter). It is possible to have the binding object invoke specific methods rather than using the primitives, but for component developers this is rarely needed.

Form Definition

Forms are defined the same way as components are defined. Below is an example of a form with three input fields and one event handler trapping changes to one of the parameters. The form definition is divided into two sections; General and Advanced. The General section contains two parameters ("firstParameter" and "$GLOBAL.debug"), whereas the second section contains just one parameter ("secondParameter").

We have only defined a label for the two parameters; $GLOBAL.debug is a Tivoli Directory Integrator global parameter that enables detailed logging when checked.

<Folder name="Forms">
	<Form name="com.acme.CustomConnector">
		<TranslationFile>CustomConnector</TranslationFile>
		<parameter name="title">title_key</parameter>
		<parameter name="formevents">function firstParameter_changed() { form.alert("First param modified"); }</parameter>
		<FormSectionNames>
			<ListItem>General</ListItem>
			<ListItem>Advanced</ListItem>
		</FormSectionNames>
		<FormSection name="General">
			<FormSectionNames>
				<ListItem>firstParameter</ListItem>
				<ListItem>$GLOBAL.debug</ListItem>
			</FormSectionNames>
		</FormSection>
		<Formsection name="Advanced">
			<parameter name="title">Advanced_Title</parameter>
			<parameter name="initiallyExpanded">false</parameter>
			<FormSectionNames>
				<ListItem>secondParameter</ListItem>
			</FormSectionNames>
		</FormSection>
		<FormItem name="firstParameter">
			<parameter name="label">first_param_label</parameter>
		</FormItem>
		<FormItem name="secondParameter">
			<parameter name="label">second_param_label</parameter>
		</FormItem>
	</Form>
</Folder>

The translation file (CustomConnector_en.properties) would contain:

title_key=This is the title/heading that appears at the top of the form
Advanced_Title=This is the title heading for the section for Advanced Users
first_param_label=First Param Label
second_param_label=Second Param Label
Forms definition elements

A FormSection element contains a list of FormSections or FormItems. This list has the tag <FormSectionNames>; the FormSection can optionally include a title and redefinitions of FormItems. These FormItems inherit from the FormItem in the Form that the FormSection is part of. This allows you to, for example, override the Tooltip for that FormItem. The Form contains a list of FormSections; this list is tagged <FormSectionNames>. In any list of FormSections, the word $Mode will be replaced by the current mode for the Connector. This allows you to show parameters depending on the mode of the Connector.

Here is a somewhat complex example of a complete form:

<Form name="com.ibm.di.connector.FileConnector">
			<TranslationFile>NLS/idi_conn_filesys</TranslationFile>
			<FormItemNames>
				<ListItem>filePath</ListItem>
				<ListItem>fileAwaitDataTimeout</ListItem>
				<ListItem>fileAppend</ListItem>
				<ListItem>exclusiveLock</ListItem>
				<ListItem>$GLOBAL.debug</ListItem>
				<ListItem>$GLOBAL.help</ListItem>
			</FormItemNames>
			<FormSectionNames>
				<ListItem>$Mode-General</ListItem>
				<ListItem>$Mode-Advanced</ListItem>
			</FormSectionNames>
			<FormSection name="Iterator-General">
				<FormSectionNames>
					<ListItem>filePath</ListItem>
				</FormSectionNames>
				<FormItem name="filePath">
					<parameter name="description">path_desc_in</parameter>
				</FormItem>
				<parameter name="title">General_title</parameter>
			</FormSection>
			<FormSection name="AddOnly-General">
				<FormSectionNames>
					<ListItem>filePath</ListItem>
					<ListItem>fileAppend</ListItem>
				</FormSectionNames>
				<FormItem name="filePath">
					<parameter name="description">path_desc_out</parameter>
				</FormItem>
				<parameter name="title">General_title</parameter>
			</FormSection>
			<FormSection name="Iterator-Advanced">
				<FormSectionNames>
					<ListItem>fileAwaitDataTimeout</ListItem>
					<ListItem>exclusiveLock</ListItem>
				</FormSectionNames>
				<FormItem name="exclusiveLock">
					<parameter name="description">exlock_desc_in</parameter>
				</FormItem>
			</FormSection>
			<FormSection name="AddOnly-Advanced">
				<FormSectionNames>
					<ListItem>exclusiveLock</ListItem>
				</FormSectionNames>
				<FormItem name="exclusiveLock">
					<parameter name="description">exlock_desc_out</parameter>
				</FormItem>
			</FormSection>
			<FormItem name="exclusiveLock">
				<parameter name="label">exlock_label</parameter>
				<parameter name="description">exlock_desc</parameter>
				<parameter name="syntax">boolean</parameter>
			</FormItem>
			<FormItem name="fileAppend">
				<parameter name="description">append_desc</parameter>
				<parameter name="label">append_label</parameter>
				<parameter name="syntax">boolean</parameter>
			</FormItem>
			<FormItem name="fileAwaitDataTimeout">
				<Values>
					<ListItem>-1</ListItem>
					<ListItem>10</ListItem>
					<ListItem>60</ListItem>
				</Values>
				<parameter name="description">time_desc</parameter>
				<parameter name="label">time_label</parameter>
				<parameter name="syntax">DROPEDIT</parameter>
			</FormItem>
			<FormItem name="filePath">
				<Values>
					<ListItem>&lt;&gt;</ListItem>
				</Values>
					<parameter name="description">path_desc</parameter>
					<parameter name="label">path_label</parameter>
					<parameter name="script">selectFile</parameter>
					<parameter name="scriptLabel">path_sript_label</parameter>
					<parameter name="scripthelp">path_script_help</parameter>
					<parameter name="syntax">DROPEDIT</parameter>
			</FormItem>
			<parameter name="title">CONN_TITLE</parameter>
		</Form>

Definition of XML Tags:

XML Translation considerations

Instead of merging the translated values from the properties file into the XML file, there is a new tag in the Form, <TranslationFile>. The correct local version of this translation file will be read in when using the Config Editor, and the values will then be used.

Example for the File Connector: the tdi.xml file for the File Connector contains this tag for the Form:

<TranslationFile>NLS/idi_conn_filesys</TranslationFile>

You would package this XML file with all the NLS/idi_conn_filesys.properties files in the jar file.

Parameter Definitions

These are the recognized parameters that can be used in a FormItem.

Table 66. FormItem parameters
Keyword Description
label The label appearing in the left column of the form (for example, LDAP URL)
description The tooltip for the parameter
default Default value for the parameter. The preferred way of providing a default value is in the component configuration itself (in the tdi.xml file). This default value will only be set if the user uses the CE to view/modify the configuration for the component.
script
script2
Specifying this parameter adds a button to the right of the input field. When the button is clicked, the named JavaScript function is executed.

Script2 allows for a second button to the right of the first one.

scriptLabel
scriptLabel2
The button text
scriptHelp
scriptHelp2
Tooltip for the script button
syntax Specifies the syntax of the parameter. This also affects the choice of UI control used to represent the value. See the syntax section for more info.
reflect If present the binding will use this method to get/set the parameter value. The binding will prepend "get" or "set" accordingly to this value (for example, specify Name to invoke getName and setName). This is only used when the configuration object performs specific logic when getting/setting a parameter value. For component developers this is rarely needed as component configurations only have get/set primitives.
Dynamic Values

The values list can contain static and dynamic values. The dynamic values are expanded and added to the array at runtime to populate the dropdown list.

Table 67.
Value Description
@ASSEMBLYLINES@ Adds all known AssemblyLines to the array
@CONNECTORS@ Adds all known connectors to the array
@PARSERS@ Adds all known parsers to the array
@FUNCTIONS@ Adds all known function components to the array
@ATTRS@ Adds all attributes from the input map
Syntax

The syntax parameter for a FormItem can have any of the following values:

Table 68.
Value Description
String This is the default syntax. A one line text field is created for text input.
Password A password field is created for text input. Be aware that if the user has configured a password store then FormUI will not insert the value in the configuration object but insert a property reference. The actual value is then stored in the password store.

If you modify this parameter via script or java code make sure to invoke BaseConfiguration.setProtectedParameter() instead of BaseConfiguration.setParameter(). The setProtectedParameter will automatically create a new property if there isn't one in place already. If the password store is not configured setProtectedParameter will simply invoke setParameter instead.

Boolean A checkbox is created for true/false values
Droplist
Dropedit
Dropdown with values from the values parameter. Dropedit is the editable version where the user also has a text field to specify a custom value. See Dynamic Values for special values.
TextArea Creates a text area control for multi-line text input
Script Creates a button that invokes a script
Static Creates a text label for viewing only (same as TextArea, but readonly)
EditorWindow This syntax causes the form to be a tabbed pane. The non-editorwindow parameters appear in the left most tab whereas each editorwindow parameter has its own tab with an editor input control. Used when you need the complete display area for input (for example, scripts)
Component This enables you to provide your own UI component if you need complex input mechanisms or otherwise want more control over the UI. Specify the java class name in the component keyword that you want inserted into the form:

syntax:component
component:pub.test.CustomUI
Version 7.x - Eclipse SWT Components

The class is instantiated by FormWidget2 at runtime and should be an SWT Control subclass (something that can be a child of a Composite). Also, it must have a constructor as shown in this example:

package pub.test;

import org.eclipse.swt.widgets.Composite;
import com.ibm.tdi.eclipse.widgets.FormWidget2;
import com.ibm.di.config.interfaces.BaseConfiguration;

public class CustomUI extends Composite {
       /*
        * form - the FormWidget2 object
        * parent - The Composite in which this control is placed
        * config - the config object being edited
        * paramname - the parameter of config being edited
        */
	public CustomUI(FormWidget2 form, Composite parent, BaseConfiguration config, 
			String paramname) {
	   super(parent, 0);
   }

}

This component will be placed in a Composite using GridLayout. Do not set the GridData of the custom UI object as this is done by the form widget after creating the custom class.

Form Scripts

In your form definitions you can add calls to script functions. These functions execute in the form's script engine. The form's script engine provides the following predefined objects:

Examples

Look at TDI's components in the configuration editor to find an example you find suitable. Use a zip/jar tool (for example, winzip, unzip) and extract the "tdi.xml" file from the component's jar file (TDI_install_dir/jars/components subdirectory).

Also, the examples/connector_java folder of this package contains the "tdi.xml" file of the Directory Connector.

Connector Reconnect Rules definition

In order to take advantage of the Tivoli Directory Integrator Reconnect feature, the Connector's .xml file may contain rules that tailor the Connector's response to interruptions in connectivity. These rules are in addition to any built-in rules of the Reconnect engine of the Tivoli Directory Integrator Server.1

The rules for the particular Connector appear in the "connectors" section as a sibling of the "connectorConfig" sub-section like this:

<Connector name="CustomConnector">
	<Configuration>
   ... various configuration options ...
	</Configuration>
	<Reconnect>
		<ReconnectRules>
			<Rule>
				<parameter name="exceptionClass">java.sql.SQLException</parameter>
				<parameter name="exceptionMessageRegExp">^I/O.*</parameter>
				<parameter name="action">reconnect</parameter>
			</Rule>
			<Rule>
				<parameter name="exceptionClass">java.sql.SQLException</parameter>
				<parameter name="exceptionMessageRegExp">^Io.*</parameter>
				<parameter name="action">reconnect</parameter>
			</Rule>
			... more rules go here ...
		</ReconnectRules>
	</Reconnect>

Each rule has the following parameters:

exceptionClass: fully qualified name of the Java class of the exception
exceptionMessageRegExp: regular expression in Java syntax
action: error or reconnect

Parameters "exceptionClass" and "exceptionMessageRegExp" are optional - if not specified, the rule will match all exception classes and all exception messages respectively.

For a detailed description of the regular expression syntax used in "exceptionMessageRegExp", please see the the JavaDoc of the java.util.regex.Pattern class at http://java.sun.com/j2se/1.5.0/docs/api/java/util/regex/Pattern.html.

Example
<Connector name="ibmdi.ReconnectTest">
	<Configuration>
		<parameter name="connectorType">com.ibm.di.connector.ReconnectTestConnector</parameter>
  	</Configuration>
	<Reconnect>
		<ReconnectRules>
			<Rule>
				<parameter name="exceptionClass">java.io.IOException</parameter>
				<parameter name="exceptionMessageRegExp">.*file not found.*</parameter>
				<parameter name="action">error</parameter>
			</Rule>
			<Rule>
				<parameter name="action">reconnect</parameter>
			</Rule>
		</ReconnectRules>
	</Reconnect>
</Connector>

Packaging and deploying the Connector

Now that we have the Connector source code compiled and supplied the "tdi.xml" file, we are ready to package and deploy the Connector.

What you need to do is create a jar file (typically with the same name as that of the Connector) and include in it:

  1. The class file(s) of the Connector
  2. The "tdi.xml" file, in the root of the jar file. If you are using translated files, also include them using the standard Java internationalization schema, for example "CustomConnector_de.properties" for German, "CustomConnector_fr.properties" for French, "CustomConnector_pt_BR.properties" for Brazilian Portuguese.

After you have created the jar file of the new Connector, you need only drop that jar file in the "jars/connectors" folder of the IBM Tivoli Directory Integrator installation. The next time the system starts up, it will automatically load the new Connector and make it ready for use.


1.
In order for Tivoli Directory Integrator to remain compatible with earlier versions, there are two built-in rules that emulate previous version's behavior. Unless reconnect is switched off entirely for a Component, a reconnect will be attempted for all Exceptions of type java.io.IOException and javax.naming.CommunicationException.

[ Top of Page | Previous Page | Next Page | Contents | Terms of use | Feedback ]
(C) Copyright IBM Corporation, 2003, 2012. All Rights Reserved.
IBM Tivoli Directory Integrator 7.1.1