//******************************************************************************************************
// IClientSubscription.cs - Gbtc
//
// Copyright © 2010, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/eclipse-1.0.php
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 06/24/2011 - Ritchie
// Generated original version of source code.
//
//******************************************************************************************************
using GSF.Collections;
using GSF.TimeSeries.Adapters;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
namespace GSF.TimeSeries.Transport
{
///
/// Represents a common set of interfaces for a client adapter subscription to the .
///
public interface IClientSubscription : IActionAdapter
{
///
/// Indicates to the host that processing for an input adapter (via temporal session) has completed.
///
///
/// This event is expected to only be raised when an input adapter has been designed to process
/// a finite amount of data, e.g., reading a historical range of data during temporal procesing.
///
event EventHandler> ProcessingComplete;
///
/// Gets the client TCP connection identifier of this .
///
Guid ClientID
{
get;
}
///
/// Gets the based subscriber ID of this .
///
Guid SubscriberID
{
get;
}
///
/// Gets the current signal index cache of this .
///
SignalIndexCache SignalIndexCache
{
get;
}
///
/// Gets the input filter requested by the subscriber when establishing this .
///
string RequestedInputFilter
{
get;
}
///
/// Gets or sets flag that determines if the compact measurement format should be used in data packets of this .
///
bool UseCompactMeasurementFormat
{
get;
set;
}
///
/// Gets size of timestamp in bytes.
///
int TimestampSize
{
get;
}
///
/// Gets or sets host name used to identify connection source of client subscription.
///
string HostName
{
get;
set;
}
///
/// Explictly raises the event.
///
/// New status message.
void OnStatusMessage(string status);
///
/// Explictly raises the event.
///
/// Processing .
void OnProcessException(Exception ex);
///
/// Explictly raises the event.
///
/// raising the notification.
/// Event arguments for notification, if any.
void OnProcessingCompleted(object sender, EventArgs e);
}
///
/// Defines static extension functions for implementations.
///
public static class IClientSubscriptionExtensions
{
// Define cache of dynamically defined event handlers associated with each client subscription
private static readonly ConcurrentDictionary>> s_statusMessageHandlers = new ConcurrentDictionary>>();
private static readonly ConcurrentDictionary>> s_processExceptionHandlers = new ConcurrentDictionary>>();
private static readonly ConcurrentDictionary s_processingCompletedHandlers = new ConcurrentDictionary();
///
/// Returns a new temporal for a .
///
/// instance to create temporal for.
/// New temporal for a .
public static IaonSession CreateTemporalSession(this IClientSubscription clientSubscription)
{
IaonSession session;
// Cache the specified input measurement keys requested by the remote subscription
// internally since these will only be needed in the private Iaon session
MeasurementKey[] inputMeasurementKeys = clientSubscription.InputMeasurementKeys;
IMeasurement[] outputMeasurements = clientSubscription.OutputMeasurements;
// Since historical data is requested, we "turn off" interaction with the outside real-time world
// by removing this adapter from external routes. To accomplish this we expose I/O demands for an
// undefined measurement. Note: assigning to null would mean "broadcast" of all data is desired.
clientSubscription.InputMeasurementKeys = new MeasurementKey[] { MeasurementKey.Undefined };
clientSubscription.OutputMeasurements = new IMeasurement[] { Measurement.Undefined };
// Create a new Iaon session
session = new IaonSession();
session.Name = "<" + clientSubscription.HostName.ToNonNullString("unavailable") + ">@" + clientSubscription.StartTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss");
// Assign requested input measurement keys as a routing restriction
session.InputMeasurementKeysRestriction = inputMeasurementKeys;
// Setup default bubbling event handlers associated with the client session adapter
EventHandler> statusMessageHandler = (sender, e) =>
{
if (e.Argument2 == UpdateType.Information)
clientSubscription.OnStatusMessage(e.Argument1);
else
clientSubscription.OnStatusMessage("0x" + (int)e.Argument2 + e.Argument1);
};
EventHandler> processExceptionHandler = (sender, e) => clientSubscription.OnProcessException(e.Argument);
EventHandler processingCompletedHandler = (sender, e) => clientSubscription.OnProcessingCompleted(sender, e);
// Cache dynamic event handlers so they can be detached later
s_statusMessageHandlers[clientSubscription] = statusMessageHandler;
s_processExceptionHandlers[clientSubscription] = processExceptionHandler;
s_processingCompletedHandlers[clientSubscription] = processingCompletedHandler;
// Attach handlers to new session - this will proxy all temporal session messages through the client session adapter
session.StatusMessage += statusMessageHandler;
session.ProcessException += processExceptionHandler;
session.ProcessingComplete += processingCompletedHandler;
// Send the first message indicating a new temporal session is being established
statusMessageHandler(null, new EventArgs(
string.Format("Initializing temporal session for host \"{0}\" spanning {1} to {2} processing data {3}...",
clientSubscription.HostName.ToNonNullString("unknown"),
clientSubscription.StartTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff"),
clientSubscription.StopTimeConstraint.ToString("yyyy-MM-dd HH:mm:ss.fff"),
clientSubscription.ProcessingInterval == 0 ? "as fast as possible" :
clientSubscription.ProcessingInterval == -1 ? "at the default rate" : "at " + clientSubscription.ProcessingInterval + "ms intervals"),
UpdateType.Information));
// Duplicate current real-time session configuration for adapters that report temporal support
session.DataSource = IaonSession.ExtractTemporalConfiguration(clientSubscription.DataSource);
// Define an in-situ action adapter for the temporal Iaon session used to proxy data back to the client subscription
DataTable actionAdapters = session.DataSource.Tables["ActionAdapters"];
DataRow proxyAdapterRow = actionAdapters.NewRow();
// Define connection string for proxy adapter based on original inputs and ouputs as requested by client subscription
StringBuilder connectionString = new StringBuilder();
if (inputMeasurementKeys != null && inputMeasurementKeys.Length > 0)
connectionString.AppendFormat("inputMeasurementKeys={{{0}}}", inputMeasurementKeys.Select(key => key.SignalID).ToDelimitedString(";"));
if (outputMeasurements != null && outputMeasurements.Length > 0)
{
if (connectionString.Length > 0)
connectionString.Append("; ");
connectionString.AppendFormat("outputMeasurements={{{0}}}", outputMeasurements.Select(m => m.ID).ToDelimitedString(";"));
}
// Assign critical adapter properties
proxyAdapterRow["ID"] = 0;
proxyAdapterRow["AdapterName"] = "PROXY!SERVICES";
proxyAdapterRow["AssemblyName"] = "GSF.TimeSeries.dll";
proxyAdapterRow["TypeName"] = "GSF.TimeSeries.Transport.TemporalClientSubscriptionProxy";
proxyAdapterRow["ConnectionString"] = connectionString.ToString();
// Add proxy row to Iaon action adapter definitions
actionAdapters.Rows.Add(proxyAdapterRow);
// Initialize temporal session adapters without starting them
session.Initialize(false);
// Get reference to temporal session proxy adapter to it can be associated with the client subscription
TemporalClientSubscriptionProxy proxyAdapter = null;
IActionAdapter adapter0;
// Lookup proxy adapter defined with reserved ID zero
if (session.ActionAdapters.TryGetAdapterByID(0, out adapter0))
proxyAdapter = adapter0 as TemporalClientSubscriptionProxy;
if ((object)proxyAdapter == null)
throw new InvalidOperationException("Failed to define temporal subscription proxy adapter - cannot complete temporal session initialization.");
// Associate proxy with client subscription
proxyAdapter.Parent = clientSubscription;
// Load current temporal constraint parameters
Dictionary settings = clientSubscription.Settings;
string startTime, stopTime, parameters;
settings.TryGetValue("startTimeConstraint", out startTime);
settings.TryGetValue("stopTimeConstraint", out stopTime);
settings.TryGetValue("timeConstraintParameters", out parameters);
// Assign requested temporal constraints to all private session adapters
session.AllAdapters.SetTemporalConstraint(startTime, stopTime, parameters);
session.AllAdapters.ProcessingInterval = clientSubscription.ProcessingInterval;
// Start temporal session adapters
session.AllAdapters.Start();
// Recalculate routing tables to accomodate addtion of proxy adapter and handle
// input measurement keys restriction
session.RecalculateRoutingTables();
return session;
}
///
/// Disposes a temporal created using .
///
/// source instance.
/// instance to dispose.
public static void DisposeTemporalSession(this IClientSubscription adapter, ref IaonSession session)
{
if (session != null)
{
EventHandler> statusMessageFunction;
EventHandler> processExceptionFunction;
EventHandler processingCompletedFunction;
// Remove and detatch from event handlers
if (s_statusMessageHandlers.TryRemove(adapter, out statusMessageFunction))
session.StatusMessage -= statusMessageFunction;
if (s_processExceptionHandlers.TryRemove(adapter, out processExceptionFunction))
session.ProcessException -= processExceptionFunction;
if (s_processingCompletedHandlers.TryRemove(adapter, out processingCompletedFunction))
session.ProcessingComplete -= processingCompletedFunction;
session.Dispose();
}
session = null;
}
}
}