Connector Development v4.1 Sample Service Implementation
In the following sample implementation, we will be implementing two connectors, sharing one agent and each with schema providers. This requires that the steps taken in Getting Started were correctly followed.
In this example, we’ll be managing an existing SQL Server Database: Employees. The Employees database has two tables, Details and Terminations.
The details table maintains very (very) simple information about a person, namely his/her first name and last name:
And the terminations table states whether the employee has been terminated:
In this scenario, we will create a connector for each table, and an agent to describe the Employees database.
Developing the Agent
First things first, we need to be able to talk to the Employees database. Agents encapsulate the connection information required to talk to the system, so we’ll start there.
public class EmployeesAgent : IAgent
{
/// <summary>
/// The connection string describing the location of the Employees database
/// </summary>
public string ConnectionString { get; set; }
/// <summary>
/// Creates an instance of this agent.
/// </summary>
/// <param name="connectionString">The connection string describing the location of the Employees database</param>
public EmployeesAgent(string connectionString)
{
ConnectionString = connectionString;
}
/// <summary>
/// Gets the connection for the Employees database.
/// </summary>
public IDbConnection Connection
{
get { return new SqlConnection(ConnectionString); }
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(IAgent other)
{
var employeesAgent = other as EmployeesAgent;
return employeesAgent != null && string.Equals(ConnectionString, employeesAgent.ConnectionString);
}
/// <summary>
/// Tests the connection of an agent.
/// </summary>
/// <remarks>
/// Perform the least required to confirm that the connection is successful. Exceptions should be thrown for connection issues.
/// </remarks>
public void TestConnection()
{
using (IDbConnection connection = Connection)
connection.Open();
}
}
In the above, we sufficiently implement an agent which will test the connection information for the Employees database, and provides a standard access point for our connectors.
You’ll notice that a connection string needs to be provided. That will be a part of the configuration of the agent. The configuration is provided in XML to a Factory which creates the agent. As such, the next step is to write a factory for the agent:
public class EmployeesAgentFactory : IAgentFactory
{
/// <summary>Creates a instance of the target type from the information provided by <paramref name="factoryInformation"/>.</summary>
/// <param name="factoryInformation">The information used to create the plugin object.</param>
/// <returns>An instance of the plug in.</returns>
public IAgent CreateComponent(IAgentConfiguration factoryInformation)
{
XElement xml = factoryInformation.Extended;
XAttribute connectionString = xml.Attribute("connectionString");
if (connectionString == null)
throw new ArgumentOutOfRangeException("factoryInformation", string.Format("The agent is missing a connectionString attribute at {0}", xml));
return new EmployeesAgent(connectionString.Value);
}
/// <summary>The name for the object.</summary>
public string Name
{
get { return "Unify.Agents.Employees"; }
}
/// <summary>Display name of the agent type.</summary>
public string DisplayName
{
get { return "Employees"; }
}
}
Agent factories have a DisplayName which is the publicly facing name that shows up in the drop down list of available agents on creation. They also have a Name, which is a unique identifier used to describe the type of the agent. And finally, they have the core generation method CreateComponent.
A block of XML is given to the factory to create the agent, this XML can come from a variety of sources, but typically it will be from the UNIFY Identity Broker user interface. Any custom user interface parts are just decorators for this xml.
Developing the Connectors
Now we have an Agent, and a factory to create it. So next up we’re going to create our two connectors. Both are going to consume the Connection property on our EmployeesAgent when connecting to the database.
First up, the Details connector:
public class DetailsConnector : IMultiKeyedReadingConnector
{
private readonly EmployeesAgent _Agent;
private readonly IEntitySchema _Schema;
/// <summary>Creates an instance of this connector.</summary>
/// <param name="agent">The agent describing where the employees database is.</param>
/// <param name="schema">Schema describing the format of the data managed by this connector.</param>
public DetailsConnector(EmployeesAgent agent, IEntitySchema schema)
{
_Agent = agent;
_Schema = schema;
}
/// <summary>Returns a list of all entities from the connected source.</summary>
/// <param name="storedValueState">Allows a connector to keep state between accesses. Particularly useful if this means less changes need to be checked.</param>
/// <returns>A list of all the entities existing on the connected source. If no entities exist on the connected source, an empty list is returned instead of null.</returns>
/// <exception cref="T:System.ArgumentNullException">Thrown if <paramref name="storedValueState"/> is null.</exception>
public IEnumerable<IConnectorEntity> GetAllEntities(IStoredValueCollection storedValueState)
{
using (IDbConnection connection = _Agent.Connection)
{
connection.Open();
using (IDbCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT FirstName, LastName, ID FROM Details";
using (IDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
IConnectorEntity entity = new ConnectorEntity();
foreach (IEntitySchemaFieldDefinition field in _Schema.Values)
{
object databaseValue = reader[field.FieldName.ToString()];
IValue idbValue = field.CreateValue(databaseValue);
entity.SetValue(field.FieldName, idbValue);
}
yield return entity;
}
}
}
}
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
}
/// <summary>The key value of the item.</summary>
public IMultiKey<GroupedNameValueCollectionKey> Key
{
get { return _Schema.SchemaKey; }
}
}
In the above, we implement a Details connector sufficiently to read all information in the Details table. In this example we’re only demonstrating the constituent parts of Identity Broker components, for more information on the other connector operations read past the step-by-step.
The source code will not be attached for the Terminations connector in this document as it should be apparent what changes would need to be made.
Similar to the EmployeesAgent, we need to create a factory to create the connector:
public class DetailsConnectorFactory : IMultiKeyedConnectorFactory
{
/// <summary>Creates a instance of the target type from the information provided by <paramref name="factoryInformation"/>.</summary>
/// <param name="factoryInformation">The information used to create the plugin object.</param>
/// <returns>An instance of the plug in.</returns>
public IMultiKeyedConnector CreateComponent(IMultiKeyedConnectorFactoryInformation factoryInformation)
{
Guid agentId = factoryInformation.Agents["EmployeesAgent"];
var employeesAgent = factoryInformation.AgentRepository.GetAgent<EmployeesAgent>(agentId);
return new DetailsConnector(employeesAgent, factoryInformation.Schema);
}
/// <summary>The name for the object.</summary>
public string Name
{
get { return "Unify.Connectors.Details"; }
}
/// <summary>The display name of the connector factory.</summary>
public string DisplayName
{
get { return "Employee Details"; }
}
/// <summary>The description of the connector factory.</summary>
public string Description
{
get { return "Manages the Details table in the Employees database."; }
}
/// <summary>Default image of a connector factory.</summary>
public Image DefaultImage
{
get { return EmployeesConnectorResources.details; }
}
}
Unlike our agent, this particular connector doesn’t require any xml configuration; however it does make use of an agent and also requires a reference to its own schema.
Developing the Schema Provider
We now have our agent, both connectors and the factories required to create them. Before we register them to the service, we’ll finally create a Schema Provider.
public class DetailsConnectorSchemaProvider : ISchemaProvider
{
/// <summary>The sets of schemas supported by this connector factory.</summary>
/// <remarks>If null or an empty collection, a completely dynamic schema is assumed.</remarks>
public IEntitySchemaConfiguration GetSchema(ISchemaProviderFactoryInformation factoryInformation)
{
IEntitySchemaConfigurationUtility schemaUtilty = factoryInformation.SchemaConfigurationUtility;
IEntitySchemaConfiguration schema = new EntitySchemaConfiguration();
var id = schemaUtilty.GenerateFieldDefinition(ValueType.Integer, "ID");
id.Key = true;
schema.Add(id);
schema.Add(schemaUtilty.GenerateFieldDefinition(ValueType.String, "FirstName"));
schema.Add(schemaUtilty.GenerateFieldDefinition(ValueType.String, "LastName"));
return schema;
}
/// <summary>The publicly facing display name of the schema provider.</summary>
public string Name
{
get { return "Entire Schema"; }
}
/// <summary>The unique type of the schema provider.</summary>
public string FactoryName
{
get { return "Unify.Providers.Details"; }
}
}
Each schema provider has a public facing display Name which is what appears in the drop-down of available schema providers. Each provider also has a FactoryName which is a unique identifier used to denote the usage of the provider; and finally each provider has its own implementation of GetSchema which can either be statically defined as above, or dynamically generated using the connector as a source.
Registering the Components
We’ve now defined every component that we need on the Service-side, but now we need to register it to the service.
The component that handles this is the IUnifyEnginePlugIn. We’ll need to implement our own to register the various components:
public class EmployeePlugIn : IUnifyEnginePlugIn
{
IAgentEngine _AgentEngine;
IConnectorEngine _ConnectorEngine;
public EmployeePlugIn(IConnectorEngine connectorEngine, IAgentEngine agentEngine)
{
_AgentEngine = agentEngine;
_ConnectorEngine = connectorEngine;
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
}
/// <summary>Initializes the plug in.</summary>
public void Initialize()
{
_AgentEngine.AddAgentFactory(new EmployeesAgentFactory());
var detailsConnectorFactory = new DetailsConnectorFactory();
_ConnectorEngine.AddConnectorFactory(detailsConnectorFactory);
_ConnectorEngine.ComponentRepository(detailsConnectorFactory.Name).AddSchemaProvider(new DetailsConnectorSchemaProvider());
var terminationsConnectorFactory = new TerminationsConnectorFactory();
_ConnectorEngine.AddConnectorFactory(terminationsConnectorFactory);
_ConnectorEngine.ComponentRepository(terminationsConnectorFactory.Name).AddSchemaProvider(new TerminationsConnectorSchemaProvider());
}
/// <summary>Starts the plug in.</summary>
public void Start()
{
}
/// <summary>Stops the plug in.</summary>
public void Stop()
{
}
}
In the above, the EmployeePlugIn adds the EmployeesAgentFactory to the agent engine, adds the DetailsConnectorFactory and TerminationsConnectorFactory to the connector engine, and registers the DetailsConnectorSchemaProvider and TerminationsSchemaProvider to the respective component repositories.
You will note that the connector engine and agent engine come in through a constructor; similar to the other components, we will need a factory to create this plugin:
public class EmployeePlugInKey : UnifyEnginePlugInKeyBase<EmployeePlugInKey>
{
/// <summary>The name for the object.</summary>
public override string Name
{
get { return "Employee"; }
}
}
public class EmployeePlugInFactory : IUnifyEnginePlugInFactory<IIdentityBrokerBranding>
{
/// <summary>Creates a instance of the target type from the information provided by <paramref name="factoryInformation"/>.</summary>
/// <param name="factoryInformation">The information used to create the plugin object.</param>
/// <returns>An instance of the plug in.</returns>
public IUnifyEnginePlugIn CreateComponent(IUnifyEnginePlugInFactoryInformation<IIdentityBrokerBranding> factoryInformation)
{
IAgentEngine agentEngine = new AgentEngineAccessor(factoryInformation.PlugIns);
IConnectorEngine connectorEngine = new ConnectorEngineAccessor(factoryInformation.PlugIns);
return new EmployeePlugIn(connectorEngine, agentEngine);
}
/// <summary>The list of plug in identifiers that are required for this plug in to work.</summary>
public IEnumerable<IUnifyEnginePlugInKey> Dependencies
{
get { return new IUnifyEnginePlugInKey[] { ConnectorEnginePlugInKey.Instance, AgentEnginePlugInKey.Instance }; }
}
/// <summary>The identifier of this plug in.</summary>
public IUnifyEnginePlugInKey PlugInKey
{
get { return EmployeePlugInKey.Instance; }
}
/// <summary>The version of the plugin.</summary>
public IPlugInVersion PlugInVersion
{
get { return ServiceVersion.Instance; }
}
}
As seen in the above, Plugin Factories maintain a series of properties and methods. First are the plugin’s dependencies. These dependencies describe what other plugins this plugin requires, which in this case are the Connector and Agent engine plugins.
Second, is the Plugin Key, which is used to uniquely identify this plugin; this is only used when dependencies are concerned, but is still required.
Thirdly is the Plugin Version, which is used to define the version of this plugin. Finally, the CreateComponent method generates the plugin itself.
Customer support service by UserEcho