Novell Home

Case Study: Integrating an Oracle Application with Identity Manager

Novell Cool Solutions: Feature
By Michel Bluteau

Digg This - Slashdot This

Posted: 17 Mar 2005
 

Case Study: Integrating an Oracle Application with Identity Manager

Michel Bluteau
mbluteauTAKETHISOUT@novell.com

This article is a complement to the documentation for the Identity Manager 2.01 JDBC driver for Oracle. While the documentation provides an example application (EMP) and related triggers, it does not cover in detail how to link an application in a schema other than the DirXML Schema (where EMP and Eventlog reside), how to assign the privileges, how to customize the driver so it can manage multiple instances for the application simultaneously, etc. Also, the example provided with the driver does not manage Group Memberships.

About the Example Application

For the above reasons, I believe that a more comprehensive example can be helpful when it comes the time to customize the JDBC driver for a given Oracle Application. For this article, I chose a simple(actually simplified) Application called Protec, which consists of a few tables.

In the PROTUSR table below, we are mainly interested in USRID and FULLNAME.

Figure 1: Protec PROTUSR table

The PROTUSRGRP table defines Groups.

Figure 2: Table PROTUSRGRP.

The PROTUSRGRPMBR table defines Group Memberships. Users in the table PROTUSR are assigned Group Memberships through this table, for Groups which are defined in table PROTUSRGRP.

Figure 3: Table PROTUSRGRPMBR.

So this is a faily simple application, with 3 main tables in a schema separated from the driver Schema(dirxml). It goes beyond just Users - it also includes Groups and Group Memberships. This example can be extended to provide with the driver for both managing Group Memberships, as well as using more granular privileges than the "GRANT ALL PRIVILEGES TO dirxml".

Protec is a real application being used by some of my customers. It actually does more than what we are describing here: it is mainly aimed at managing access and privileges for Oracle resources and applications, and it also support other databases like Microsoft SQL Server. The complete information on Protec and its other tables and objects is not required for this article.

The SQL script for creating a simplified version of Protec is provided with this example, and its purpose is to provide someone with a working example, that can be adapted to work with another application. These scripts do not provide the complete Protec Application.

Extending the Schema

Before the driver can be imported into eDirectory under a driver set, some schema extensions are required in order to manage the information from the application. The following auxiliary class contains two attributes, ProUsrID and ProFullname, which are Case Ignore String, Single-Value, Sync Immediate.

Figure 4: Auxiliary Class ProtecAcct for User.

*Larger image

The next auxiliary class includes three attributes, ProFullName, ProID and ProUsrGrpID, which are Case Ignore String, Single-Value, Sync Immediate.

Figure 5: Auxiliary Class ProtecGroup for Group.

*Larger image

The effective class that follows is derived from Top. It can be contained by Domain, Organization and Organization Unit, and it contains three additional attributes in addition to CN (naming) and any other inherited by Top:

  • ProMbrType: Integer, Single-Value, Sync Immediate;
  • ProUsrGrpID and ProUsrID: same attributes used for the two Auxiliary Classes.

Figure 6: Effective Class ProGrpMbr.

*Larger image

Using the Example Application

If you want to use the example application discussed in this article, you can load the following scripts into Oracle. Here are the steps to follow:

1. Create a user/schema called Protec as SYSTEM with SQLplus or another utility:

CREATE USER Protec identified by protec
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP;
GRANT ALL PRIVILEGES TO Protec;

2. Log in with Protec and run the following script:

CREATE TABLE "PROTEC"."PROTUSR" ("USRID" VARCHAR2(16 byte) NOT 
  NULL, "FULLNAME" VARCHAR2(40 byte) NOT NULL, "PASSWORD" 
  RAW(40), "ATTRSVAL" VARCHAR2(4000 byte), 
  CONSTRAINT "PK_01_PROTUSR" PRIMARY KEY("USRID")) 
  USING INDEX 
  LOGGING; 

CREATE TABLE "PROTEC"."PROTUSRGRP" ("USRGRPID" VARCHAR2(16 byte) 
  NOT NULL, "FULLNAME" VARCHAR2(40 byte) NOT NULL, 
  CONSTRAINT "PK_01_PROTUSRGRP" PRIMARY KEY("USRGRPID") 
  USING INDEX ) 
   LOGGING ;

CREATE TABLE "PROTEC"."PROTUSRGRPMBR" ("USRGRPID" VARCHAR2(16 
  byte) NOT NULL, "MBRTYPE" NUMBER(1) NOT NULL, "MBRID" 
  VARCHAR2(16 byte) NOT NULL, 
  CONSTRAINT "PK_01_PROTUSRGRPMBR" PRIMARY KEY("USRGRPID", 
  "MBRTYPE", "MBRID") 
  USING INDEX )
  LOGGING;

CREATE INDEX "PROTEC"."IX_01_PROTUSRGRPMBR" 
  ON "PROTEC"."PROTUSRGRPMBR" ("MBRID") 
  LOGGING;

3. Create the dirxml user. Login as SYSTEM and execute the following script:

CREATE USER dirxml IDENTIFIED BY dirxml
default tablespace USERS
quota unlimited on USERS
temporary tablespace TEMP;
GRANT CONNECT TO dirxml;
GRANT CREATE SESSION, ALTER SESSION, CREATE TABLE, CREATE SEQUENCE, CREATE PROCEDURE, CREATE VIEW TO dirxml;
Then we need to grant privileges to dirxml so views can be created and used:

GRANT INSERT, UPDATE, SELECT ON PROTEC.PROTUSR TO dirxml;
GRANT INSERT, UPDATE, SELECT ON PROTEC.PROTUSRGRP TO dirxml;
GRANT INSERT, UPDATE, SELECT ON PROTEC.PROTUSRGRPMBR TO dirxml;

4. Log in as dirxml and create the views for Protec tables:

CREATE VIEW dirxml.view_PROTUSR (pk_USRID, FULLNAME, PASSWORD, ATTRSVAL)
AS
/* Table 'PROTUSR' would need to be schema-prefixed if it were located outside */
/* of the driver's schema. */
SELECT USRID, FULLNAME, PASSWORD, ATTRSVAL FROM PROTEC.PROTUSR;

CREATE VIEW dirxml.view_PROTUSRGRP (pk_USRGRPID, FULLNAME)
AS
/* Table 'PUSRGRPID' would need to be schema-prefixed if it were located outside */
/* of the driver's schema. */
SELECT USRGRPID, FULLNAME FROM PROTEC.PROTUSRGRP;

CREATE VIEW dirxml.view_PROTUSRGRPMBR (pk_USRGRPID, pk_MBRTYPE, pk_MBRID)
AS
/* Table 'PPROTUSRGRPMBR' would need to be schema-prefixed if it were located outside */
/* of the driver's schema. */
SELECT USRGRPID, MBRTYPE, MBRID FROM PROTEC.PROTUSRGRPMBR;

5. Create the Eventlog table in the dirxml schema:

CREATE TABLE dirxml.eventlog
(
	record_id		NUMBER(32) 		NOT NULL
							UNIQUE,
	table_key		VARCHAR2(96)	NOT NULL,		
	status		CHAR(1)		DEFAULT 'N',
	event_type		NUMBER(1) 		NOT NULL, 
	event_time		DATE			DEFAULT SYSDATE 
							NOT NULL, 			
	perpetrator		VARCHAR2(32)	DEFAULT USER,
	table_name		VARCHAR2(32) 	NOT NULL,
	column_name		VARCHAR2(32),
	old_value		VARCHAR2(64),
	new_value		VARCHAR2(64),
	CONSTRAINT chk_eventlog_event_type CHECK(event_type IN (1, 2, 3, 4, 5, 6, 7, 8))
);	
CREATE UNIQUE INDEX i_eventlog_1 ON dirxml.eventlog(table_key, record_id, status);
CREATE UNIQUE INDEX i_eventlog_2 ON dirxml.eventlog(record_id, table_key);


CREATE SEQUENCE dirxml.seq_recid
	START WITH 1
	INCREMENT BY 1
	NOMINVALUE
	NOMAXVALUE
	CACHE 100
	ORDER;

6. Grant the right to the Protec user to insert records in the Eventlog table. Run the following statement while still logged in as dirxml:

GRANT INSERT ON dirxml.eventlog TO protec;

Next, we need to login as Protec and create the Publication Triggers:

CREATE TRIGGER PROTEC.t_PROTUSR
	AFTER INSERT OR UPDATE OR DELETE ON PROTEC.PROTUSR
	FOR EACH ROW

DECLARE	
	opcode 	NUMERIC(1);
	table_name 	VARCHAR2(64);
	key 		VARCHAR2(64);

BEGIN

	table_name 	:= 'view_PROTUSR'; 
	key := 'pk_USRID=';

	IF INSERTING THEN
		opcode := 5; /* Insert Row */
		key := key || :new.USRID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	ELSIF UPDATING THEN
		opcode := 6; /* Update Row */
		key := key || :new.USRID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	ELSIF DELETING THEN
		opcode := 4; /* Delete Row */
		key := key || :old.USRID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	END IF;	
END;
/

CREATE TRIGGER PROTEC.t_PROTUSRGRP
	AFTER INSERT OR UPDATE OR DELETE ON PROTEC.PROTUSRGRP
	FOR EACH ROW

DECLARE	
	opcode 	NUMERIC(1);
	table_name 	VARCHAR2(64);
	key 		VARCHAR2(64);

BEGIN

	table_name 	:= 'view_PROTUSRGRP'; 
	key := 'pk_USRGRPID=';

	IF INSERTING THEN
		opcode := 5; /* Insert Row */
		key := key || :new.USRGRPID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	ELSIF UPDATING THEN
		opcode := 6; /* Update Row */
		key := key || :new.USRGRPID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	ELSIF DELETING THEN
		opcode := 4; /* Delete Row */
		key := key || :old.USRGRPID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	END IF;	
END;
/

CREATE OR REPLACE TRIGGER PROTEC.t_PROTUSRGRPMBR
	AFTER INSERT OR UPDATE OR DELETE ON PROTEC.PROTUSRGRPMBR
	FOR EACH ROW

DECLARE
	opcode 	NUMERIC(1);
	table_name 	VARCHAR2(64);
	key 		VARCHAR2(64);

BEGIN

	table_name 	:= 'view_PROTUSRGRPMBR';

	IF INSERTING THEN
		opcode := 5; /* Insert Row */
		key := 'pk_USRGRPID=' || :new.USRGRPID || '+pk_MBRTYPE=' || :new.MBRTYPE || '+pk_MBRID=' || :new.MBRID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	ELSIF UPDATING THEN
		opcode := 6; /* Update Row */
		key := 'pk_USRGRPID=' || NVL(:new.USRGRPID,:old.USRGRPID) || '+pk_MBRTYPE=' || NVL(:new.MBRTYPE,:old.MBRTYPE) || '+pk_MBRID=' || NVL(:new.MBRID,:old.MBRID);

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	ELSIF DELETING THEN
		opcode := 4; /* Delete Row */
		key := 'pk_USRGRPID=' || :old.USRGRPID || '+pk_MBRTYPE=' || :old.MBRTYPE || '+pk_MBRID=' || :old.MBRID;

		INSERT INTO dirxml.eventlog(record_id, event_type, table_name, table_key)
			VALUES(dirxml.seq_recid.nextval, opcode, table_name, key);

	END IF;	
END;
/

The last trigger provides a good example on how to construct the key for a view with a virtual Primary Key of more than one column. Each column in the view must be prefixed with pk_, and the key inserted into the Eventlog table must be a concatenation of those columns. eDirectory uses this key to uniquely identify each record or row in the view. An example of such a key would look like the following:

pk_USRGRPID=GROUPID101+pk_MBRTYPE=2+pk_MBRID=USERID201

Several elements of logic are hardcoded in the JDBC driver, so you should familiarize yourself with these elements (described in the documentation) before attempting to customize the driver for an Application and fully take advantage of the capabilities for the driver.

Importing and Testing the Driver

Now that you have all the required logic on the Oracle side, you're ready to import and test the driver.

Figure 7: DirXML driver overview for the Protec driver.

*Larger image

1. Change the IP Address, Port and SID to match the Oracle Instance.

Figure 8: Authentication Page for the Oracle Instance.

2. Use the USRID as the unique identifier, not the Primary Key. No Primary Key should be generated when creating a User in the Oracle Application.

Figure 9: Subscriber Settings.

3. Map three classes: User, Group, and Group Membership.

Figure 10: Mapping Rule for the driver

Figure 11: Mapped Attributes for User

Figure 12: Mapped Attributes for Group

Figure 13: Mapped Attributes for a Group Membership object(ProGrpMbr)

4. Create a filter for the driver to get only a Notify for Group Membership (User) on the Subscriber Channel. This will trigger the logic for adding a record into the view for Memberships.

Figure 14: Filter for the driver

Figure 15: Matching Rule for the driver, for both Publisher and Subscriber

Figure 16: Publisher (Application-to-eDirectory) Create Policy for required attributes

5. Add the auxiliary class ProtecAcct for User on creation (see figure below).

6. Add a Surname(Mandatory), and a Group Membership for the Application Instance's Control Group(P1Grp).

Figure 17: Publisher Create Policy - ProtecAcct

7. Add the auxiliary class ProtecGroup to Group on Creation for the Publisher Channel.

8. Set the value for attribute ProID to the Application Instance (P1). We also want to Add a Group Membership to the User when a Group Membership object is being created in eDirectory.

Figure 18: Publisher Create Policy - ProtecGroup

9. For User, make sure that the User does belong to the Application Instance (P1) managed by this driver. Also, make sure that the attribute ProFullname is available.

Figure 19: Subscriber Create Policy

10. Before you create a new Group in the Oracle Application, make sure the Group is under the OU for this Application Instance (\P1).

11. Set ProFullname as a required attribute.

Figure 20: Subscriber Create Policy

Note: There are rules in place for creating Memberships on the Subscriber Channel, but these rules should not be used - the Group Membership attribute for User is used instead. Do not create, delete or modify Group Membership objects directly in eDirectory: the driver takes care of managing those objects.

Figure 21: Subscriber Create Policy

12. Create a rule to force the USRID in the Oracle Application to be the same as CN. This is required by other elements of logic.

Figure 22: Subscriber Create Policy

13. Place Users under ca\novl\Users.

14. Place Groups under the OU for the Application Instance.

15. Create Membership objects in the same location as Groups.

16. Create the CN for a Membership object by concatenating UsrGrpID and UsrID, separated by a hyphen ("-").

Figure 23: Publisher Placement Policy.

Note: When a Group Membership is deleted in the Oracle Application, the associated object in eDirectory is deleted, so you must also add the logic to remove the Group Membership attribute value for User. When a User is deleted in the Oracle Application, block (veto) the delete operation in eDirectory and remove the Group Membership attribute value for the Control Group (P1) for the Application Instance managed by this driver.

Figure 24: Publisher Command Transform

Using XSLT Code

1. Use XSLT instead of Policy Builder for the rules pictured below. You need to encapsulate SQL DML(Data Manipulation Language) statements inside the logic and directly manipulate the data in the Application Tables (actually, through views in the dirxml Schema).

Figure 25: Subscriber Command Transform in XSLT

2. Detect a User Modification Event for the Group Membership attribute, and extract the added values and removed values. There may be multiple added and removed values for a single event:

	<!-- Adds or Drops a Protec Group Membership for User	-->
	<xsl:template match="modify[@class-name='User']" xmlns:jdbc="urn:dirxml:jdbc">
		<xsl:variable name="association" select="association"/>
		<xsl:variable name="addgroupmembership" select="./modify-attr[@attr-name='Group Membership']/add-value/value"/>
		<xsl:message>We are in the stylesheet Membership</xsl:message>
		<xsl:variable name="delgroupmembership" select="./modify-attr[@attr-name='Group Membership']/remove-value/value"/>
		<xsl:copy>
			<xsl:apply-templates select="node()|@*"/>
		</xsl:copy>
		<xsl:for-each select="$addgroupmembership">
			<xsl:call-template name="add-usermembership-sql">
				<xsl:with-param name="protecmember" select="$association"/>
				<xsl:with-param name="protecmembership" select="."/>
			</xsl:call-template>
		</xsl:for-each>
		<xsl:for-each select="$delgroupmembership">
			<xsl:call-template name="del-usermembership-sql">
				<xsl:with-param name="protecmember" select="$association"/>
				<xsl:with-param name="protecmembership" select="."/>
			</xsl:call-template>
		</xsl:for-each>
	</xsl:template>

We use a for-each in order to call another template for both added values and removed values.

3. In the template for adding a Membership in the Oracle Application, query eDirectory for additional information (attribute values) for the object being modified (User), then query the Oracle Application (Protec).

4. Make sure that the added or removed membership corresponds to a Group for the Application Instance this driver is managing (P1).

5. Generate an SQL statement for updating the table for Memberships through the view, and send it to Oracle:

<!-- Adds SQL to add a Protec Group Membership for User -->
	<xsl:template name="add-usermembership-sql" xmlns:Mapping="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.jdbc.util.MappingPolicy" xmlns:jdbc="urn:dirxml:jdbc">
		<xsl:param name="protecmember"/>
		<xsl:param name="protecmembership"/>
		<xsl:copy>
			<xsl:attribute name="jdbc:transaction-id">0</xsl:attribute>
			<xsl:attribute name="jdbc:op-id">0</xsl:attribute>
			<xsl:attribute name="jdbc:op-type">password-set-operation</xsl:attribute>
			<xsl:apply-templates select="node() | @*"/>
		</xsl:copy>
		<!--	_________________	Query eDirectory for existing attributes __________________ -->
		<xsl:variable name="src-query">
			<query class-name="Group" dest-dn="{$protecmembership}" scope="entry">
				<read-attr attr-name="ProUsrGrpID"/>
				<read-attr attr-name="ProID"/>
			</query>
		</xsl:variable>
		<!--	_________________	Paste exiting attributes into variables __________________ -->
		<xsl:variable name="result" select="query:query($srcQueryProcessor, $src-query)"/>
		<xsl:variable name="protecgrpid" select="$result/nds/output/instance[1]/attr[@attr-name='ProUsrGrpID']/value[1]"/>
		<xsl:variable name="protecid" select="$result/nds/output/instance[1]/attr[@attr-name='ProID']/value[1]"/>
		<!--	_________________	Query Protec for existing attributes __________________ --<
		<xsl:variable name="dest-query">
			<query class-name="User" dest-dn="{$protecmember}" scope="entry">
				<read-attr attr-name="ProUsrID"/>
			</query>
		</xsl:variable>
		<!--	_________________	Paste exiting attributes into variables __________________ -->
		<xsl:variable name="result2" select="query:query($destQueryProcessor, $dest-query)"/>
		<xsl:variable name="protecusrid" select="$result2/nds/output/instance[1]/attr[@attr-name='ProUsrID']/value[1]"/>
		<xsl:if test="string($protecid) = 'P1'">
			<!-- Add Membership in Protec table PROTUSRGRPMBR -->
			<jdbc:statement jdbc:op-id="0" jdbc:transaction-id="0" jdbc:transaction-type="manual" jdbc:type="update">
				<jdbc:sql>
					<xsl:text>INSERT INTO dirxml.view_PROTUSRGRPMBR(pk_USRGRPID, pk_MBRTYPE, pk_MBRID) values('</xsl:text>
					<xsl:value-of select="$protecgrpid"/>
					<xsl:text>', 9, '</xsl:text>
					<xsl:value-of select="$protecusrid"/>
					<xsl:text>')</xsl:text>
				</jdbc:sql>
			</jdbc:statement>
		</xsl:if>
	</xsl:template>

Here is a template that takes care of removing Group Memberships:

<!-- Adds SQL to remove a Protec Group Membership for User -->
	<xsl:template name="del-usermembership-sql" xmlns:Mapping="http://www.novell.com/nxsl/java/com.novell.nds.dirxml.driver.jdbc.util.MappingPolicy" xmlns:jdbc="urn:dirxml:jdbc">
		<xsl:param name="protecmember"/>
		<xsl:param name="protecmembership"/>
		<xsl:copy>
			<xsl:attribute name="jdbc:transaction-id">0</xsl:attribute>
			<xsl:attribute name="jdbc:op-id">0</xsl:attribute>
			<xsl:attribute name="jdbc:op-type">password-set-operation</xsl:attribute>
			<xsl:apply-templates select="node() | @*"/>
		</xsl:copy>
		<!--	_________________	Query eDirectory for existing attributes __________________ -->
		<xsl:variable name="src-query">
			<query class-name="Group" dest-dn="{$protecmembership}" scope="entry">
				<read-attr attr-name="ProUsrGrpID"/>
				<read-attr attr-name="ProID"/>
			</query>
		</xsl:variable>
		<!--	_________________	Paste exiting attributes into variables __________________ -->
		<xsl:variable name="result" select="query:query($srcQueryProcessor, $src-query)"/>
		<xsl:variable name="protecgrpid" select="$result/nds/output/instance[1]/attr[@attr-name='ProUsrGrpID']/value[1]"/>
		<xsl:variable name="protecid" select="$result/nds/output/instance[1]/attr[@attr-name='ProID']/value[1]"/>
		<!--	_________________	Query Protec for existing attributes __________________ -->
		<xsl:variable name="dest-query">
			<query class-name="User" dest-dn="{$protecmember}" scope="entry">
				<read-attr attr-name="ProUsrID"/>
			</query>
		</xsl:variable>
		<!--	_________________	Paste exiting attributes into variables __________________ -->
		<xsl:variable name="result2" select="query:query($destQueryProcessor, $dest-query)"/>
		<xsl:variable name="protecusrid" select="$result2/nds/output/instance[1]/attr[@attr-name='ProUsrID']/value[1]"/>
		<xsl:if test="string($protecid) = 'P1'">
			<!-- Add Membership in Protec table PROTUSRGRPMBR -->
			<jdbc:statement jdbc:op-id="0" jdbc:transaction-id="0" jdbc:transaction-type="manual" jdbc:type="update">
				<jdbc:sql>
					<xsl:text>DELETE FROM dirxml.view_PROTUSRGRPMBR WHERE pk_USRGRPID = '</xsl:text>
					<xsl:value-of select="$protecgrpid"/>
					<xsl:text>' AND pk_MBRID = '</xsl:text>
					<xsl:value-of select="$protecusrid"/>
					<xsl:text>'</xsl:text>
				</jdbc:sql>
			</jdbc:statement>
		</xsl:if>
	</xsl:template>

Conclusion

There are hundreds of Oracle Applications available. Some are developed by third parties like SCT Banner; others are custom applications developed by a single organization or shared by a few organizations. These applications store their data in their own tables, versus the tables owned by the SYS user, which contains information related to Security and Objects(Oracle Users, Roles, Privileges, Object Definitions, etc). Oracle Applications also sometimes store Identities (Users) and Groups in their own tables, and that is what we are after: we want to sync those identities with eDirectory. These identities may or may not be associated with Oracle Accounts (DBA_USERS view), but we are interested in synchronizing them for both cases.

The approach in this article can be used for simple (or simplified) applications like the one discussed, or it can be extended to deal with more complex applications like SCT Banner, a very popular Oracle Application in higher education in North America. The main idea is to use staging objects in eDirectory. For example you can manage Memberships and Transform events for these objects (Add, Delete, Modify) into modifications for attributes for User and Group, such as Group Membership.

The additional staging objects in eDirectory, even if plentiful, do not present a problem, and they do not consume too much disk space (less than 1K per object, typically). We can picture them as indexes that speed up the process of managing events - we can leverage with them referential integrity and do a direct query against them, rather than a search or tree scan(that would be required with another approach like multi-value attributes). They are also very important when determining if a piece of information (a Group Membership in the application) is new to eDirectory, or is already in eDirectory (Object and Association are present).

Of course, this example can also be adapted, or ported, to other databases like Microsoft SQL Server, IBM DB2, etc., for applications running on these other databases.

I am always trying to improve drivers for databases or database applications, so feel free to send me (Michel Bluteau) your feedback or comments, or even suggestions for improvements.


Novell Cool Solutions (corporate web communities) are produced by WebWise Solutions. www.webwiseone.com

© 2014 Novell