Microsoft Identity Integration Server 2003 Developer Reference

Example: Connected Data Source Extension for Call-Based Data Sources

This topic contains a code example to create a connected data source extension for call-based data sources.

The example assumes that the call-based data source consumes an XML file in the following format:

<sample-objects>
	<object>
		<objectclass>Person</objectclass>
		<delta>Add</delta>
		<anchor-attribute>1</anchor-attribute>
		<name>Object1</name>
		<email>Object1@fabrikam.com</email>
	</object>
</sample-objects>

The example then generates a comma-delimited file whose contents are then imported into the metaverse and then exported to a call-based data source.

The following C# code example shows how to create a connected data source extension for call-based data sources.

using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using Microsoft.MetadirectoryServices;
using System.Data; 
using System.Data.SqlClient;  
using System.Data.OleDb;

namespace Miis_CallExport
{
		public class MACallExport :  
				IMAExtensibleFileImport, 
				IMAExtensibleCallExport
		{
				public MACallExport() {}

				// ------------------ C O N S T A N T S -----------------------------------------------
				private const string PARAM_SQLTABLENAME = "Table";
				private const string PARAM_SQLWHERECLAUSE = "SQLWhereClause";
				private const string PARAM_SQLDATABASE = "Database";
				private const string PARAM_SQLTIMEOUT = "Timeout";
				private const string PARAM_SQLSECURITY = "Security";
				private const string PARAM_SQLPROVIDER = "Provider";
				// Not needed: private const string PARAM_ESCAPEANCHOR = "EscapeAnchor";


				// ------------------  I M P O R T  ---------------------------------------------------
				//
				private string EscapeStringForImport(string escape)
				{
						if(escape == null)
								return null;

						escape.Replace("\"", "\"\""); 
						return String.Format("\"{0}\"", escape);
			} // EscapeStringForImport

				private string ConstructConnectionString
						(
						string					connectTo, 
						string					user, 
						string					password, 
						ConfigParameterCollection   configParameters
						)
				{
						string connectionString = "";
						if(configParameters[PARAM_SQLSECURITY].Value.ToUpper().Equals("SSPI"))
						{
								connectionString = String.Format("Provider={0};Persist Security Info=False;Integrated Security=SSPI;Initial Catalog={1};Data Source={2};Connect Timeout={3}",
										configParameters[PARAM_SQLPROVIDER].Value,
										configParameters[PARAM_SQLDATABASE].Value,
										connectTo,
										configParameters[PARAM_SQLTIMEOUT].Value);
					}
						else
						{
								connectionString = String.Format("Provider={0};Persist Security Info=False;User ID={1};Password={2};Initial Catalog={3};Data Source={4};Connect Timeout={5}",
										configParameters[PARAM_SQLPROVIDER].Value,
										user,
										password,
										configParameters[PARAM_SQLDATABASE].Value,
										connectTo,
										configParameters[PARAM_SQLTIMEOUT].Value);
					}
						return connectionString;
			} //ConstructConnectionString

				public void GenerateImportFile
						( 
						string					filename, 
						string					connectTo, 
						string					user, 
						string					password, 
						ConfigParameterCollection   configParameters,
						bool						fullImport, 
						TypeDescriptionCollection   types,
						ref string				customData 
						)
				{
						if(!fullImport)
						{
								throw new TerminateRunException("This MA only supports full import"); 
					}

						//-----------------------------------------------------------------------------------------------
						// Construct the columns to select from the SQL table by
						// enumerating through the attributes of the first object type.
						// This assumes that the DB MA schema exposes all attributes available for all object types
						//-----------------------------------------------------------------------------------------------
						string sqlColumns = "";
						string objectType = "";
						ArrayList attributeList = new ArrayList();
						foreach(TypeDescription t in types)
						{
								objectType = t.Name; 
								foreach(AttributeDescription a in t.Attributes)
								{
										sqlColumns = sqlColumns + a.Name + ","; 
										attributeList.Add(a.Name);
							}
								break;
					}
						if(sqlColumns.Equals(""))
						{
								throw new TerminateRunException("No attributes in schema definition");
					}
						sqlColumns = sqlColumns.Substring(0, sqlColumns.Length-1); // remove last comma

						//-----------------------------------------------------------------------------------------------
						// Construct the SQL Select statement for the intermediate import file.
						//-----------------------------------------------------------------------------------------------
						string commandString = String.Format("SELECT {0} FROM {1}",
								sqlColumns,
								configParameters[PARAM_SQLTABLENAME].Value);
						try
						{
								if(!configParameters[PARAM_SQLWHERECLAUSE].Value.Equals(""))
								{
										commandString = String.Format("{0} WHERE {1}", commandString, configParameters[PARAM_SQLWHERECLAUSE].Value); 
							}
					}
						catch { /* ignore if the parameter "SQLWhereClause" doesn't exist */ }

						//-----------------------------------------------------------------------------------------------
						// Construct the connection string and connect to the SQL database
						//-----------------------------------------------------------------------------------------------
						string connectionString = ConstructConnectionString(connectTo, user, password, configParameters);

						OleDbConnection myConnection = new OleDbConnection(connectionString);
						OleDbCommand myCommand = new OleDbCommand(commandString, myConnection);

						//-----------------------------------------------------------------------------------------------
						// Open the SQL database and create the intermediate file
						// Note that this code only supports String, Integer, GUID, and DBNULL values
						// Further datatypes must be added by extending the code.
						//-----------------------------------------------------------------------------------------------
						myConnection.Open(); 
						OleDbDataReader result = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

						StreamWriter stream = new StreamWriter(filename, false, System.Text.Encoding.Unicode);
						while(result.Read())
						{
								for(int i=0; i<result.FieldCount;i++)
								{
										if(result[i] is string)
												stream.WriteLine(String.Format("{0}: {1}", attributeList[i], result[i]));
										else if (result[i] is System.Int64)
												stream.WriteLine(String.Format("{0}: {1}", attributeList[i], result[i]));
										else if(result[i] is System.Guid)
												stream.WriteLine(String.Format("{0}: {{{1}}}", attributeList[i], System.Convert.ToString(result[i])));
										else if (result[i] is System.DBNull)
												; // no output --> NULL value
										else
												throw new UnexpectedDataException(String.Format("Unknown type in SQL Column: {0}", attributeList[i])); 										
							}
								stream.WriteLine(); // new record, seperate by new line
					}
						stream.Close();

			} //GenerateImportFile


				// ------------------  E X P O R T  ---------------------------------------------------
				// Globals for Export routines
				private string ExportConnectionString;
				private string ExportTableName;
				private OleDbConnection ExportConnection;
				private TypeDescriptionCollection ExportTypes;
				// not needed: private bool ExportEscapeAnchor;

				public void BeginExport
						( 
						string					connectTo, 
						string					user, 
						string					password,
						ConfigParameterCollection   configParameters,
						TypeDescriptionCollection   types
						)
				{
						//-----------------------------------------------------------------------------------------------
						// Construct the connection string and connect to the SQL database
						//-----------------------------------------------------------------------------------------------
						ExportConnectionString = ConstructConnectionString(connectTo, user, password, configParameters);
						ExportConnection = new OleDbConnection(ExportConnectionString);
						ExportConnection.Open(); 

						// Save Type (Schema) information for later to determine the anchor for a given object type
						ExportTypes = types;

						// Save the Database table name ro run the Insert/Update/Deletes
						ExportTableName = configParameters[PARAM_SQLTABLENAME].Value;

						// Save for later if in the where clause the anchor needs to get enclosed in ''
						// not needed: ExportEscapeAnchor = configParameters[PARAM_ESCAPEANCHOR].Value.ToLower().Equals("yes") ? true : false;  
			}

				public void ExportEntry
						( 
						ModificationType	modificationType, 
						string[]			changedAttributes,
						CSEntry			 csentry 
						)
				{
						switch(modificationType)
						{
								case ModificationType.Replace:
										UpdateEntry(changedAttributes, csentry);
										break;

								case ModificationType.Add:
										InsertEntry(changedAttributes, csentry);
										break;

								case ModificationType.Delete:
										DeleteEntry(csentry);
										break;
					}
			} //ExportEntry

				private string EscapeStringForExport(string escape)
				{
						if(escape == null)
								return null;

						return escape.Replace("'", "''");
			} // EscapeStringForExport

				private string GetAnchorAttributeName(CSEntry cs)
				{
						TypeDescription t = ExportTypes[cs.ObjectType]; 
						//-----------------------------------------------------------
						// Note that this MA only supports one Anchor attribute
						//-----------------------------------------------------------
						if(t.AnchorAttributes.Count > 1) 
								throw new TerminateRunException(String.Format("ObjectType {0} has more than one anchor attribute", cs.ObjectType));

						foreach(AttributeDescription attr in t.AnchorAttributes)
						{
								return attr.Name; 
					}
						return null;
			} // GetAnchorAttributeName

				private bool EscapeAnchorAttribute(CSEntry cs)
				{
						TypeDescription t = ExportTypes[cs.ObjectType]; 
						//-----------------------------------------------------------
						// Note that this MA only supports one Anchor attribute
						//-----------------------------------------------------------
						if(t.AnchorAttributes.Count > 1) 
								throw new TerminateRunException(String.Format("ObjectType {0} has more than one anchor attribute", cs.ObjectType));

						foreach(AttributeDescription attr in t.AnchorAttributes)
						{
								switch (attr.DataType)
								{
										case AttributeType.String:
												return true;
										case AttributeType.Integer:
												return false;
										case AttributeType.Binary:
												return true;
										default:
												throw new TerminateRunException(String.Format("Anchor type unsupported. ObjectType: {0}, Attribute: {1}, Type: {2}", cs.ObjectType, attr.Name, attr.DataType.ToString()));
							}
					}
						return true;
			} // EscapeAnchorAttribute

				private void UpdateEntry(string[] changedAttributes, CSEntry cs)
				{
						//------------------------------------------------------------------------------
						// Build the SQL Update Query
						//------------------------------------------------------------------------------
						string commandString = String.Format("UPDATE {0} SET", ExportTableName);
						foreach(string attribName in changedAttributes)
						{
								string val = "";
								Attrib attr = cs[attribName]; 
								if(attr.IsPresent)
								{
										switch(attr.DataType)
										{
												case AttributeType.String:
														val = "'" + EscapeStringForExport(attr.StringValue) + "'";
														break;
												case AttributeType.Integer:
														val = System.Convert.ToString(attr.IntegerValue, 10);   
														break;
												default:
														throw new UnexpectedDataException(String.Format("Unknown data type for export update. Attr: {0}, Type: {1}", attribName, attr.DataType.ToString())); 
									}
							}
								else
								{
										val = "NULL";
							}

								commandString = commandString + String.Format(" {0}={1},", attribName, val);
					}
						commandString = commandString.Substring(0, commandString.Length-1); // remove last comma

						string anchor = GetAnchorAttributeName(cs);
						string anchorValue = cs[anchor].Value.ToString();
						if(EscapeAnchorAttribute(cs))
						{
								anchorValue = String.Format("'{0}'", anchorValue); 
					}
						commandString = commandString + String.Format(" WHERE {0}={1}", anchor, anchorValue);
						//------------------------------------------------------------------------------
						// Build the SQL Update Query
						//------------------------------------------------------------------------------

						//------------------------------------------------------------------------------
						// Execute SQL Update Query
						//------------------------------------------------------------------------------
						OleDbCommand myCommand = new OleDbCommand(commandString, ExportConnection);
						try { myCommand.ExecuteNonQuery(); }
						catch { throw; } // need better exception to report back the command sent to SQL

			} // UpdateEntry

				private void InsertEntry(string[] changedAttributes, CSEntry cs)
				{
						//------------------------------------------------------------------------------
						// Build the SQL Insert Query
						//------------------------------------------------------------------------------
						string commandString = String.Format("INSERT INTO {0} (", ExportTableName);
						foreach(string attribName in changedAttributes)
						{
								commandString = commandString + attribName + ",";
					}
						commandString = commandString.Substring(0, commandString.Length-1); // remove last comma
						commandString = commandString + ") VALUES (";
 
						foreach(string attribName in changedAttributes)
						{
								string val = "";
								Attrib attr = cs[attribName]; 
								if(attr.IsPresent)
								{
										switch(attr.DataType)
										{
												case AttributeType.String:
														val = "'" + EscapeStringForExport(attr.StringValue) + "'";
														break;
												case AttributeType.Integer:
														val = System.Convert.ToString(attr.IntegerValue, 10);   
														break;
												case AttributeType.Binary: // assuming GUID value (e.g. Anchor)
														val = "'" + attr.Value.ToString() + "'";  
														break;
												default:
														throw new UnexpectedDataException(String.Format("Unknown data type for export add. Attr: {0} Type: {1}" + attribName, attr.DataType.ToString())); 
									}
							}
								else
								{
										val = "NULL";
							}

								commandString = commandString + val + ",";
					}
						commandString = commandString.Substring(0, commandString.Length-1); // remove last comma
						commandString = commandString + ")";
						//------------------------------------------------------------------------------
						// Build the SQL Insert Query
						//------------------------------------------------------------------------------

						//------------------------------------------------------------------------------
						// Execute the SQL Insert Query
						//------------------------------------------------------------------------------
						OleDbCommand myCommand = new OleDbCommand(commandString, ExportConnection);
						try { myCommand.ExecuteNonQuery(); }
						catch { throw; } // need better exception to report back the command sent to SQL
			} // insert entry

				private void DeleteEntry(CSEntry cs)
				{
						string anchor = GetAnchorAttributeName(cs);
						string anchorValue = cs[anchor].Value.ToString();
						if(EscapeAnchorAttribute(cs))
						{
								anchorValue = String.Format("'{0}'", anchorValue); 
					}

						// Build the SQL Delete Query
						string commandString = String.Format("DELETE FROM {0} WHERE {1}={2}", ExportTableName, anchor, anchorValue);

						// Execute the SQL Delete Query
						OleDbCommand myCommand = new OleDbCommand(commandString, ExportConnection);
						try { myCommand.ExecuteNonQuery(); }
						catch { throw; } // need better exception to report back the command sent to SQL
			} // DeleteEntry

				public void EndExport()
				{
						ExportConnection.Close();
			}
	}
}