//****************************************************************************************************** // 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; } } }