/// <summary> /// Disposes a temporal <see cref="IaonSession"/> created using <see cref="CreateTemporalSession"/>. /// </summary> /// <param name="adapter"><see cref="IClientSubscription"/> source instance.</param> /// <param name="session"><see cref="IaonSession"/> instance to dispose.</param> public static void DisposeTemporalSession(this IClientSubscription adapter, ref IaonSession session) { if ((object)session != null) { EventHandler <EventArgs <string, UpdateType> > statusMessageFunction; EventHandler <EventArgs <Exception> > processExceptionFunction; EventHandler processingCompletedFunction; // Remove and detach 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; }
/// <summary> /// Initializes <see cref="SynchronizedClientSubscription"/>. /// </summary> public override void Initialize() { MeasurementKey[] inputMeasurementKeys; string setting; if (Settings.TryGetValue("inputMeasurementKeys", out setting)) { // IMPORTANT: The allowSelect argument of ParseInputMeasurementKeys must be null // in order to prevent SQL injection via the subscription filter expression inputMeasurementKeys = AdapterBase.ParseInputMeasurementKeys(DataSource, false, setting); m_requestedInputFilter = setting; // IMPORTANT: We need to remove the setting before calling base.Initialize() // or else we will still be subject to SQL injection Settings.Remove("inputMeasurementKeys"); } else { inputMeasurementKeys = new MeasurementKey[0]; m_requestedInputFilter = null; } base.Initialize(); // Set the InputMeasurementKeys and UsePrecisionTimer properties after calling // base.Initialize() so that the base class does not overwrite our settings InputMeasurementKeys = inputMeasurementKeys; UsePrecisionTimer = false; if (Settings.TryGetValue("bufferBlockRetransmissionTimeout", out setting)) { m_bufferBlockRetransmissionTimeout = double.Parse(setting); } else { m_bufferBlockRetransmissionTimeout = 5.0D; } if (Settings.TryGetValue("requestNaNValueFilter", out setting)) { m_isNaNFiltered = m_parent.AllowNaNValueFilter && setting.ParseBoolean(); } else { m_isNaNFiltered = false; } m_bufferBlockRetransmissionTimer = Common.TimerScheduler.CreateTimer((int)(m_bufferBlockRetransmissionTimeout * 1000.0D)); m_bufferBlockRetransmissionTimer.AutoReset = false; m_bufferBlockRetransmissionTimer.Elapsed += BufferBlockRetransmissionTimer_Elapsed; // Handle temporal session initialization if (this.TemporalConstraintIsDefined()) { m_iaonSession = this.CreateTemporalSession(); } }
/// <summary> /// Initializes <see cref="UnsynchronizedClientSubscription"/>. /// </summary> public override void Initialize() { base.Initialize(); string setting; if (Settings.TryGetValue("inputMeasurementKeys", out setting)) { m_requestedInputFilter = setting; } else { m_requestedInputFilter = null; } if (Settings.TryGetValue("publishInterval", out setting)) { m_publishInterval = int.Parse(setting); } else { m_publishInterval = -1; } if (Settings.TryGetValue("includeTime", out setting)) { m_includeTime = setting.ParseBoolean(); } else { m_includeTime = true; } if (Settings.TryGetValue("useMillisecondResolution", out setting)) { m_useMillisecondResolution = setting.ParseBoolean(); } else { m_useMillisecondResolution = false; } if (m_parent.UseBaseTimeOffsets && m_includeTime) { m_baseTimeRotationTimer = new System.Timers.Timer(); m_baseTimeRotationTimer.Interval = m_useMillisecondResolution ? 60000 : 420000; m_baseTimeRotationTimer.AutoReset = true; m_baseTimeRotationTimer.Elapsed += BaseTimeRotationTimer_Elapsed; } // Handle temporal session intialization if (this.TemporalConstraintIsDefined()) { m_iaonSession = this.CreateTemporalSession(); } }
/// <summary> /// Initializes <see cref="SynchronizedClientSubscription"/>. /// </summary> public override void Initialize() { base.Initialize(); base.UsePrecisionTimer = false; if (!Settings.TryGetValue("inputMeasurementKeys", out m_requestedInputFilter)) { m_requestedInputFilter = null; } // Handle temporal session intialization if (this.TemporalConstraintIsDefined()) { m_iaonSession = this.CreateTemporalSession(); } }
/// <summary> /// Initializes <see cref="SynchronizedClientSubscription"/>. /// </summary> public override void Initialize() { string setting; base.Initialize(); base.UsePrecisionTimer = false; if (!Settings.TryGetValue("inputMeasurementKeys", out m_requestedInputFilter)) { m_requestedInputFilter = null; } if (Settings.TryGetValue("bufferBlockRetransmissionTimeout", out setting)) { m_bufferBlockRetransmissionTimeout = double.Parse(setting); } else { m_bufferBlockRetransmissionTimeout = 5.0D; } if (Settings.TryGetValue("requestNaNValueFiltering", out setting)) { m_isNaNFiltered = m_parent.AllowNaNValueFilter && setting.ParseBoolean(); } else { m_isNaNFiltered = false; } m_bufferBlockRetransmissionTimer = new Timer(); m_bufferBlockRetransmissionTimer.AutoReset = false; m_bufferBlockRetransmissionTimer.Interval = m_bufferBlockRetransmissionTimeout * 1000.0D; m_bufferBlockRetransmissionTimer.Elapsed += BufferBlockRetransmissionTimer_Elapsed; // Handle temporal session initialization if (this.TemporalConstraintIsDefined()) { m_iaonSession = this.CreateTemporalSession(); } }
/// <summary> /// Returns a new temporal <see cref="IaonSession"/> for a <see cref="IClientSubscription"/>. /// </summary> /// <param name="clientSubscription"><see cref="IClientSubscription"/> instance to create temporal <see cref="IaonSession"/> for.</param> /// <returns>New temporal <see cref="IaonSession"/> for a <see cref="IClientSubscription"/>.</returns> 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 the client subscription adapter from external routes. To accomplish this we expose // I/O demands for an undefined measurement as assigning to null would mean "broadcast" is desired. clientSubscription.InputMeasurementKeys = new[] { 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 <EventArgs <string, UpdateType> > statusMessageHandler = (sender, e) => { if (e.Argument2 == UpdateType.Information) { clientSubscription.OnStatusMessage(e.Argument1); } else { clientSubscription.OnStatusMessage("0x" + (int)e.Argument2 + e.Argument1); } }; EventHandler <EventArgs <Exception> > processExceptionHandler = (sender, e) => clientSubscription.OnProcessException(e.Argument); EventHandler processingCompletedHandler = clientSubscription.OnProcessingCompleted; // 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, UpdateType>( 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); // Initialize temporal session adapters without starting them session.Initialize(false); // Define an in-situ action adapter for the temporal Iaon session used to proxy data back to the client subscription. Note // to enable adapters that are connect-on-demand in the temporal session, we must make sure the proxy adapter is setup to // respect input demands. The proxy adapter produces no points into the temporal session - all received points are simply // internally proxied out to the parent client subscription outside the purview of the Iaon session; from the perspective // of the Iaon session, points seem to dead-end in the proxy adapter. The proxy adapter is an action adapter and action // adapters typically produce measurements, as such, actions default to respecting output demands not input demands. Using // the default settings of not respecting input demands and the proxy adapter not producing any points, the Iaon session // would ignore the adapter's input needs. In this case we want Iaon session to recognize the inputs of the proxy adapter // as important to the connect-on-demand dependency chain, so we request respect for the input demands. TemporalClientSubscriptionProxy proxyAdapter = new TemporalClientSubscriptionProxy { // Assign critical adapter properties ID = 0, Name = "PROXY!SERVICES", ConnectionString = "", DataSource = session.DataSource, RespectInputDemands = true, InputMeasurementKeys = inputMeasurementKeys, OutputMeasurements = outputMeasurements, Parent = clientSubscription, Initialized = true }; // Add new proxy adapter to temporal session action adapter collection - this will start adapter session.ActionAdapters.Add(proxyAdapter); // Load current temporal constraint parameters Dictionary <string, string> 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 accommodate addition of proxy adapter and handle // input measurement keys restriction session.RecalculateRoutingTables(); return(session); }
/// <summary> /// Initializes <see cref="UnsynchronizedClientSubscription"/>. /// </summary> public override void Initialize() { base.Initialize(); string setting; if (Settings.TryGetValue("inputMeasurementKeys", out setting)) { m_requestedInputFilter = setting; } else { m_requestedInputFilter = null; } if (Settings.TryGetValue("publishInterval", out setting)) { m_publishInterval = int.Parse(setting); } else { m_publishInterval = -1; } if (Settings.TryGetValue("includeTime", out setting)) { m_includeTime = setting.ParseBoolean(); } else { m_includeTime = true; } if (Settings.TryGetValue("useMillisecondResolution", out setting)) { m_useMillisecondResolution = setting.ParseBoolean(); } else { m_useMillisecondResolution = false; } if (Settings.TryGetValue("requestNaNValueFilter", out setting)) { m_isNaNFiltered = m_parent.AllowNaNValueFilter && setting.ParseBoolean(); } else { m_isNaNFiltered = false; } if (Settings.TryGetValue("bufferBlockRetransmissionTimeout", out setting)) { m_bufferBlockRetransmissionTimeout = double.Parse(setting); } else { m_bufferBlockRetransmissionTimeout = 5.0D; } if (m_parent.UseBaseTimeOffsets && m_includeTime) { m_baseTimeRotationTimer = new Timer(); m_baseTimeRotationTimer.Interval = m_useMillisecondResolution ? 60000 : 420000; m_baseTimeRotationTimer.AutoReset = true; m_baseTimeRotationTimer.Elapsed += BaseTimeRotationTimer_Elapsed; } m_bufferBlockRetransmissionTimer = new Timer(); m_bufferBlockRetransmissionTimer.AutoReset = false; m_bufferBlockRetransmissionTimer.Interval = m_bufferBlockRetransmissionTimeout * 1000.0D; m_bufferBlockRetransmissionTimer.Elapsed += BufferBlockRetransmissionTimer_Elapsed; // Handle temporal session initialization if (this.TemporalConstraintIsDefined()) { m_iaonSession = this.CreateTemporalSession(); } }
/// <summary> /// Initializes <see cref="UnsynchronizedClientSubscription"/>. /// </summary> public override void Initialize() { MeasurementKey[] inputMeasurementKeys; string setting; if (Settings.TryGetValue("inputMeasurementKeys", out setting)) { // IMPORTANT: The allowSelect argument of ParseInputMeasurementKeys must be null // in order to prevent SQL injection via the subscription filter expression inputMeasurementKeys = ParseInputMeasurementKeys(DataSource, false, setting); m_requestedInputFilter = setting; // IMPORTANT: We need to remove the setting before calling base.Initialize() // or else we will still be subject to SQL injection Settings.Remove("inputMeasurementKeys"); } else { inputMeasurementKeys = new MeasurementKey[0]; m_requestedInputFilter = null; } base.Initialize(); // Set the InputMeasurementKeys property after calling base.Initialize() // so that the base class does not overwrite our setting InputMeasurementKeys = inputMeasurementKeys; if (!Settings.TryGetValue("publishInterval", out setting) || !double.TryParse(setting, out m_publishInterval)) { m_publishInterval = -1; } if (Settings.TryGetValue("includeTime", out setting)) { m_includeTime = setting.ParseBoolean(); } else { m_includeTime = true; } if (Settings.TryGetValue("useMillisecondResolution", out setting)) { m_useMillisecondResolution = setting.ParseBoolean(); } else { m_useMillisecondResolution = false; } if (Settings.TryGetValue("requestNaNValueFilter", out setting)) { m_isNaNFiltered = m_parent.AllowNaNValueFilter && setting.ParseBoolean(); } else { m_isNaNFiltered = false; } if (Settings.TryGetValue("bufferBlockRetransmissionTimeout", out setting)) { m_bufferBlockRetransmissionTimeout = double.Parse(setting); } else { m_bufferBlockRetransmissionTimeout = 5.0D; } if (m_parent.UseBaseTimeOffsets && m_includeTime) { m_baseTimeRotationTimer = Common.TimerScheduler.CreateTimer(m_useMillisecondResolution ? 60000 : 420000); m_baseTimeRotationTimer.AutoReset = true; m_baseTimeRotationTimer.Elapsed += BaseTimeRotationTimer_Elapsed; } m_bufferBlockRetransmissionTimer = Common.TimerScheduler.CreateTimer((int)(m_bufferBlockRetransmissionTimeout * 1000.0D)); m_bufferBlockRetransmissionTimer.AutoReset = false; m_bufferBlockRetransmissionTimer.Elapsed += BufferBlockRetransmissionTimer_Elapsed; // Handle temporal session initialization if (this.TemporalConstraintIsDefined()) { m_iaonSession = this.CreateTemporalSession(); } }
/// <summary> /// Returns a new temporal <see cref="IaonSession"/> for a <see cref="IClientSubscription"/>. /// </summary> /// <param name="clientSubscription"><see cref="IClientSubscription"/> instance to create temporal <see cref="IaonSession"/> for.</param> /// <returns>New temporal <see cref="IaonSession"/> for a <see cref="IClientSubscription"/>.</returns> 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 <EventArgs <string, UpdateType> > statusMessageHandler = (sender, e) => { if (e.Argument2 == UpdateType.Information) { clientSubscription.OnStatusMessage(e.Argument1); } else { clientSubscription.OnStatusMessage("0x" + (int)e.Argument2 + e.Argument1); } }; EventHandler <EventArgs <Exception> > 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, UpdateType>( 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 <string, string> 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); }