The Blotter API provides convenient abstractions for maintaining a blotter in real-time. A blotter is a table of generally static data augmented over time. The rows correspond to BlotterItems and the table to a BlotterChannel. The BlotterProvider allows multiple BlotterChannels, each associated with a user, to be serviced in a single namespace. Each channel is represented to the Liberator as a container and each constituent item a record.
Key Concepts
Blotter
- BlotterChannel - corresponds to a single published blotter. Blotter Items are added to this.
- BlotterItem - corresponds to a single row in the blotter
Nested Blotter Concepts
- Nested Blotter A Blotter that has more than one tier of data associated with a user. To use nesting, you must set the SubcontainerNamespace. With nesting, any BlotterItems may have associated child BlotterItems.
- BlotterItem parent Mechanism provided for adding BlotterItem children to a parent BlotterItem. Caplin.DataSource.Blotter.BlotterItem#SetParent
- Subcontainer Representation The default representation of the tree. Every BlotterItem with children will be have a field 'SubcontainerSubject'. The subcontainer contains all of the children of that BlotterItem. If those children have children themselves, the former will have their own 'SubcontainerSubject' field. In the case that Caplin.DataSource.Blotter.BlotterConfiguration#SetSubcontainerMaxDepth is set, the every tier from the top of the tree down to the 'subcontainerMaxDepth'th tier will use the Subcontainer Representation and all tiers lower than that will use the Materialised Path Representation.
- SubcontainerSubject field BlotterItem field with subject of subcontainer in the Subcontainer Representation portion of tree.
- Materialised Path Representation An optional representation of a Nested Blotter. The children and children's children, and so on, of a subcontainer at a depth of 'subcontainerMaxDepth' will all be returned upon requesting this 'SubcontainerSubject'. No BlotterItem in the Materialised Path Representation portion of the tree (from the 'subcontainerMaxDepth'th tier and down) will have a 'SubcontainerSubject' field. Instead its location in the tree can be determined from its 'Address' field. See Subcontainer Representation for details of when this applies.
- Address field Set on all BlotterItems throughout a Nested Blotter, the 'Address' field contains a representation of this BlotterItem's location in the tree. The representation is as follows, a BlotterItem with no children will simply have it's parent redords subject followed by a colon and it's own subject, a child of this BlotterItem will have the former BlotterItem's record subject followed by a colon followed by the latter BlotterItem's subject. That is, with three BlotterItems with uniqueIds "parent", "child" and "grandchild", the Address will be: "/CHANNEL/ITEM/parent", "/CHANNEL/ITEM/parent:/CHANNEL/ITEM/child" and "/CHANNEL/ITEM/parent:/CHANNEL/ITEM/child:/CHANNEL/ITEM/grandchild" respectively. The subjects will be generated according to the namespaces configured in the BlotterConfiguration.
Blotter Configuration
- Blotter Identifier - identifies the blotter in logs and over jmx
- Channel Namespace - specifies where to extract the username from an incoming blotter channel subscription.
It should match an object-map in rttpd.conf
e.g.
Channel Namespace passed into BlotterConfiguration:
"/BLOTTER/%u/CHANNEL"
Matching object-map in rttpd.conf:
object-map /BLOTTER/CHANNEL /BLOTTER/%u/CHANNEL
which takes subscriptions on /BLOTTER/CHANNEL and substitute the %u with the username of that user session.
- Item Namespace - specifies where to extract the username and item identifier from an incoming blotter item subscription.
It too should match an object-map in rttpd.conf
e.g.
Item Namespace passed into BlotterConfiguration:
"/BLOTTER/%u/ITEM/%i"
Matching object-map in rttpd.conf:
object-map /BLOTTER/ITEM/%2 /BLOTTER/%u/ITEM/%2
which takes subscriptions on /BLOTTER/ITEM/uniqueId and substitutes the %u with the username of that user session and leave the item identifier unchanged.
- Delta Updates Setting this option to true tells the Blotter API to only send out the data that has changed between a BlotterItem being sent and re-sent into the BlotterChannel.
- Subcontainer Namespace (nested blotter only)
Specifies where to extract the username and item identifier from an incoming blotter subcontainer subscription.
e.g.
Subcontainer Namespace passed into BlotterConfiguration :
"/BLOTTER/%u/SUBCONTAINER/%i"
No object mapping necessary.
The subject contained within the 'SubcontainerSubject' field will contain the fully mapped subject.
- SubcontainerMaxDepth (nested blotter only) How many layers of subcontainers to use before using a materialised path representation of nested BlotterItems
More on Representations
The tree in this example would be constructed as follows:
using System.Collections.Generic;
namespace Caplin.DataSource.Blotter.Example
{
public class CreatingATree
{
void SendTree(IBlotterChannel blotterChannel){
BlotterItem trade1 = new BlotterItem("Trade 1");
BlotterItem itemA = new BlotterItem("Item A");
BlotterItem itemB = new BlotterItem("Item B");
itemA.SetParent(trade1);
itemB.SetParent(trade1);
BlotterItem trade2 = new BlotterItem("Trade 2");
BlotterItem itemC = new BlotterItem("Item C");
BlotterItem itemD = new BlotterItem("Item D");
itemC.SetParent(trade2);
itemD.SetParent(trade2);
List<BlotterItem> blotterItemsToSend = new List<BlotterItem>();
blotterItemsToSend.Add(trade1);
blotterItemsToSend.Add(trade2);
blotterItemsToSend.Add(itemA);
blotterItemsToSend.Add(itemB);
blotterItemsToSend.Add(itemC);
blotterItemsToSend.Add(itemD);
blotterChannel.SendBlotterItems(blotterItemsToSend);
}
}
}
On the left is the Subcontainer Representation. This is the representation that will be used by default. In this case, subscribing to the 'ChannelSubejct' will return two BlotterItems, 'Trade 1' and 'Trade 2'. Each of these BlotterItems is a parent, so they will both have the field 'SubcontainerSubject'. Subscribing to 'Trade 1's 'SubcontainerSubject' will return the children of 'Trade 1', 'Trade 1:Item A' and 'Trade 1:Item B' and likewise for 'Trade 2's 'SubcontainerSubject'.
On the right is the Materialised Path Representation. The Blotter API is configured to use this representation from the top of the tree by setting Caplin.DataSource.Blotter.BlotterConfiguration#SetSubcontainerMaxDepth to nought. In this case, subscribing to the 'ChannelSubject' would return a flat representation of the tree, all the BlotterItems associated with this channel. The position of the BlotterItems in the tree is represented only by the 'Address' field which will contain the uniqueIds of every parent of this BlotterItem in order and this BlotterItem's uniqueId.
Getting Started
Instantiating a BlotterProvider
Here is how you would instantiate a BlotterProvider given a BlotterApplicationListener called BlotterUpdateGenerator:
using System;
using Caplin.DataSource.Blotter;
using System.Collections.Generic;
using Caplin.Logging;
namespace Caplin.DataSource.Blotter.Example
{
public class BlotterUpdateGenerator : IBlotterApplicationListener
{
private Logging.ILogger logger;
private int currentMessageIndex = 0;
public BlotterUpdateGenerator(Logging.ILogger logger)
{
this.logger = logger;
}
public void BlotterChannelOpened(IBlotterChannel channel)
{
logger.Log(LogLevels.Info, "channel", "BlotterChannelOpened: " + channel.Subject);
channel.SendBlotterItem(BlotterItemFor(channel.Username));
}
public void BlotterChannelClosed(IBlotterChannel channel)
{
logger.Log(LogLevels.Info, "channel", "BlotterChannelClosed: " + channel.Subject);
}
private BlotterItem BlotterItemFor(string username)
{
BlotterItem message = new BlotterItem(Convert.ToString(++currentMessageIndex));
message.SetField("tradeDate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss z"));
message.SetField("currencyPair", "EUR/USD");
message.SetField("submittedBy", username);
return message;
}
}
}
Sample ApplicationListener
Here is the example ApplicationListener, BlotterUpdateGenerator:
using System;
using Caplin.DataSource;
using Caplin.Logging;
using Caplin.DataSource.Blotter;
using System.Threading;
using System.Collections.Generic;
namespace Caplin.DataSource.Blotter.Example
{
public class BlotterAdapterExample
{
private static string CHANNEL_NAMESPACE = "/BLOTTER/%u/CHANNEL";
private static string ITEM_NAMESPACE = "/BLOTTER/%u/ITEM/%i";
static void Main(string[] args)
{
ConsoleLogger logger = new ConsoleLogger();
IDataSource datasource = new DataSource("blotterExample.conf", logger);
BlotterConfiguration configuration = new BlotterConfiguration("BlotterDemoDotNet", CHANNEL_NAMESPACE, ITEM_NAMESPACE);
new BlotterAdapterExample(datasource, configuration);
datasource.Start();
while (true)
{
Thread.Sleep(1000);
}
}
private ILogger logger;
public BlotterAdapterExample(IDataSource datasource, BlotterConfiguration configuration)
{
this.logger = datasource.Logger;
BlotterUpdateGenerator updateGenerator = new BlotterUpdateGenerator(logger);
new BlotterProvider(datasource, configuration, updateGenerator);
datasource.AddConnectionListener(new ConnectionListener(logger));
}
}
public class ConnectionListener : IConnectionListener
{
private ILogger logger;
public ConnectionListener(ILogger logger)
{
this.logger = logger;
}
public void ServiceStatus (IServiceStatusEvent ev)
{
logger.Log(LogLevels.Info, "status", "ServiceStatus changed to " + ev.Status);
}
public void PeerStatus (IPeerStatusEvent ev)
{
logger.Log(LogLevels.Info, "status", "PeerStatus changed to " + ev.Status);
}
}
}