//****************************************************************************************************** // RoutingTables.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/30/2011 - J. Ritchie Carroll // Generated original version of source code. // 07/25/2011 - J. Ritchie Carroll // Added code to handle connect on demand adapters (i.e., where AutoStart = false). // //****************************************************************************************************** using GSF.Collections; using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace GSF.TimeSeries.Adapters { /// /// Represents the routing tables for the Iaon adapters. /// public class RoutingTables : IDisposable { #region [ Members ] // Events /// /// Event is raised when there is an exception encountered while processing. /// /// /// is the exception that was thrown. /// public event EventHandler> ProcessException; // Fields private InputAdapterCollection m_inputAdapters; private ActionAdapterCollection m_actionAdapters; private OutputAdapterCollection m_outputAdapters; private Dictionary> m_actionRoutes; private Dictionary> m_outputRoutes; private List m_actionBroadcastRoutes; private List m_outputBroadcastRoutes; private ReaderWriterLockSlim m_adapterRoutesCacheLock; private AutoResetEvent m_calculationComplete; private object m_queuedCalculationPending; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public RoutingTables() { m_adapterRoutesCacheLock = new ReaderWriterLockSlim(); m_calculationComplete = new AutoResetEvent(true); m_queuedCalculationPending = new object(); } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~RoutingTables() { Dispose(false); } #endregion #region [ Properties ] /// /// Gets or sets the active . /// public InputAdapterCollection InputAdapters { get { return m_inputAdapters; } set { m_inputAdapters = value; } } /// /// Gets or sets the active . /// public ActionAdapterCollection ActionAdapters { get { return m_actionAdapters; } set { m_actionAdapters = value; } } /// /// Gets or sets the active . /// public OutputAdapterCollection OutputAdapters { get { return m_outputAdapters; } set { m_outputAdapters = value; } } #endregion #region [ Methods ] /// /// Releases all the resources used by the object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { m_actionRoutes = null; m_outputRoutes = null; m_actionBroadcastRoutes = null; m_outputBroadcastRoutes = null; m_inputAdapters = null; m_actionAdapters = null; m_outputAdapters = null; if ((object)m_adapterRoutesCacheLock != null) m_adapterRoutesCacheLock.Dispose(); m_adapterRoutesCacheLock = null; if ((object)m_calculationComplete != null) { // Release any waiting threads before disposing wait handle m_calculationComplete.Set(); m_calculationComplete.Close(); } m_calculationComplete = null; } } finally { m_disposed = true; // Prevent duplicate dispose. } } } /// /// Spawn routing tables recalculation. /// /// Input measurement keys restriction. /// /// Set the to null to use full adapter I/O routing demands. /// public virtual void CalculateRoutingTables(MeasurementKey[] inputMeasurementKeysRestriction) { try { ThreadPool.QueueUserWorkItem(QueueRoutingTableCalculation, inputMeasurementKeysRestriction); } catch (Exception ex) { // Process exception for logging OnProcessException(new InvalidOperationException("Failed to queue routing table calculation: " + ex.Message, ex)); } } private void QueueRoutingTableCalculation(object state) { try { // Queue up a routing table calculation unless another thread has already requested one if (!m_disposed && Monitor.TryEnter(m_queuedCalculationPending)) { try { // Queue new routing table calculation after waiting for any prior calculation to complete if (m_calculationComplete.WaitOne()) ThreadPool.QueueUserWorkItem(CalculateRoutingTables, state); } finally { Monitor.Exit(m_queuedCalculationPending); } } } catch (Exception ex) { // Process exception for logging OnProcessException(new InvalidOperationException("Failed to queue routing table calculation: " + ex.Message, ex)); } } private void CalculateRoutingTables(object state) { try { // Pre-calculate internal routes to improve performance Dictionary> actionRoutes = new Dictionary>(); Dictionary> outputRoutes = new Dictionary>(); List actionAdapters, actionBroadcastRoutes = new List(); List outputAdapters, outputBroadcastRoutes = new List(); MeasurementKey[] measurementKeys; if ((object)m_actionAdapters != null) { lock (m_actionAdapters) { foreach (IActionAdapter actionAdapter in m_actionAdapters) { // Make sure adapter is initialized before calculating route if (actionAdapter.WaitForInitialize(actionAdapter.InitializationTimeout)) { measurementKeys = actionAdapter.InputMeasurementKeys; if ((object)measurementKeys != null) { foreach (MeasurementKey key in measurementKeys) { if (!actionRoutes.TryGetValue(key, out actionAdapters)) { actionAdapters = new List(); actionRoutes.Add(key, actionAdapters); } if (!actionAdapters.Contains(actionAdapter)) actionAdapters.Add(actionAdapter); } } else actionBroadcastRoutes.Add(actionAdapter); } else actionBroadcastRoutes.Add(actionAdapter); } } } if ((object)m_outputAdapters != null) { lock (m_outputAdapters) { foreach (IOutputAdapter outputAdapter in m_outputAdapters) { // Make sure adapter is initialized before calculating route if (outputAdapter.WaitForInitialize(outputAdapter.InitializationTimeout)) { measurementKeys = outputAdapter.InputMeasurementKeys; if ((object)measurementKeys != null) { foreach (MeasurementKey key in measurementKeys) { if (!outputRoutes.TryGetValue(key, out outputAdapters)) { outputAdapters = new List(); outputRoutes.Add(key, outputAdapters); } if (!outputAdapters.Contains(outputAdapter)) outputAdapters.Add(outputAdapter); } } else outputBroadcastRoutes.Add(outputAdapter); } else outputBroadcastRoutes.Add(outputAdapter); } } } // Synchronously update adapter routing cache m_adapterRoutesCacheLock.EnterWriteLock(); try { m_actionRoutes = actionRoutes; m_outputRoutes = outputRoutes; m_actionBroadcastRoutes = actionBroadcastRoutes; m_outputBroadcastRoutes = outputBroadcastRoutes; } finally { m_adapterRoutesCacheLock.ExitWriteLock(); } // Start or stop any connect on demand adapters HandleConnectOnDemandAdapters((MeasurementKey[])state); } catch (Exception ex) { OnProcessException(new InvalidOperationException("Routing tables calculation error: " + ex.Message, ex)); } finally { if ((object)m_calculationComplete != null) m_calculationComplete.Set(); } } /// /// Event handler for distributing new measurements in a routed fashion. /// /// Event source reference to adapter that generated new measurements. /// Event arguments containing a collection of new measurements. /// /// Time-series framework uses this handler to directly route new measurements to the action and output adapters. /// public virtual void RoutedMeasurementsHandler(object sender, EventArgs> e) { RoutedMeasurementsHandler(e.Argument); } /// /// Method for distributing new measurements in a routed fashion. /// /// Collection of new measurements. /// /// Time-series framework uses this handler to directly route new measurements to the action and output adapters. /// public virtual void RoutedMeasurementsHandler(IEnumerable newMeasurements) { if ((object)m_actionRoutes == null || (object)m_outputRoutes == null) return; List actionRoutes; List outputRoutes; Dictionary> actionMeasurements = new Dictionary>(); Dictionary> outputMeasurements = new Dictionary>(); List measurements; MeasurementKey key; m_adapterRoutesCacheLock.EnterReadLock(); try { // Loop through each new measurement and look for destination routes foreach (IMeasurement measurement in newMeasurements) { key = measurement.Key; if (m_actionRoutes.TryGetValue(key, out actionRoutes)) { // Add measurements for each destination action adapter route foreach (IActionAdapter actionAdapter in actionRoutes) { if (!actionMeasurements.TryGetValue(actionAdapter, out measurements)) { measurements = new List(); actionMeasurements.Add(actionAdapter, measurements); } measurements.Add(measurement); } } if (m_outputRoutes.TryGetValue(key, out outputRoutes)) { // Add measurements for each destination output adapter route foreach (IOutputAdapter outputAdapter in outputRoutes) { if (!outputMeasurements.TryGetValue(outputAdapter, out measurements)) { measurements = new List(); outputMeasurements.Add(outputAdapter, measurements); } measurements.Add(measurement); } } } // Send broadcast action measurements foreach (IActionAdapter actionAdapter in m_actionBroadcastRoutes) { if (actionAdapter.Enabled) actionAdapter.QueueMeasurementsForProcessing(newMeasurements); } // Send broadcast output measurements foreach (IOutputAdapter outputAdapter in m_outputBroadcastRoutes) { if (outputAdapter.Enabled) outputAdapter.QueueMeasurementsForProcessing(newMeasurements); } } finally { m_adapterRoutesCacheLock.ExitReadLock(); } // Send routed action measurements foreach (KeyValuePair> actionAdapterMeasurements in actionMeasurements) { IActionAdapter actionAdapter = actionAdapterMeasurements.Key; if (actionAdapter.Enabled) actionAdapter.QueueMeasurementsForProcessing(actionAdapterMeasurements.Value); } // Send routed output measurements foreach (KeyValuePair> outputAdapterMeasurements in outputMeasurements) { IOutputAdapter outputAdapter = outputAdapterMeasurements.Key; if (outputAdapter.Enabled) outputAdapter.QueueMeasurementsForProcessing(outputAdapterMeasurements.Value); } } /// /// Event handler for distributing new measurements in a broadcast fashion. /// /// Event source reference to adapter that generated new measurements. /// Event arguments containing a collection of new measurements. /// /// Time-series framework uses this handler to route new measurements to the action and output adapters; adapter will handle filtering. /// public virtual void BroadcastMeasurementsHandler(object sender, EventArgs> e) { ICollection newMeasurements = e.Argument; m_actionAdapters.QueueMeasurementsForProcessing(newMeasurements); m_outputAdapters.QueueMeasurementsForProcessing(newMeasurements); } /// /// Raises event. /// /// Processing . protected virtual void OnProcessException(Exception ex) { if ((object)ProcessException != null) ProcessException(this, new EventArgs(ex)); } /// /// Starts or stops connect on demand adapters based on current state of demanded input or output measurements. /// /// Input measurement keys restriction. /// /// Set the to null to use full adapter routing demands. /// protected virtual void HandleConnectOnDemandAdapters(MeasurementKey[] inputMeasurementKeysRestriction) { IEnumerable outputMeasurementKeys = null; IEnumerable inputMeasurementKeys = null; MeasurementKey[] requestedOutputMeasurementKeys, requestedInputMeasurementKeys, emptyKeys = new MeasurementKey[0]; if ((object)inputMeasurementKeysRestriction != null && inputMeasurementKeysRestriction.Any()) { // When an input measurement keys restriction has been defined, extract the needed input and output measurement keys by // walking the dependency chain of the restriction TraverseMeasurementKeyDependencyChain(inputMeasurementKeysRestriction, out outputMeasurementKeys, out inputMeasurementKeys); } else { // Get the full list of output measurements keys that can be provided in this Iaon session if ((object)m_inputAdapters != null) outputMeasurementKeys = m_inputAdapters.OutputMeasurementKeys(); if ((object)m_actionAdapters != null) { if ((object)outputMeasurementKeys == null || !outputMeasurementKeys.Any()) { outputMeasurementKeys = m_actionAdapters.OutputMeasurementKeys(); } else { IEnumerable actionAdapterOutputMeasurementKeys = m_actionAdapters.OutputMeasurementKeys(); if ((object)actionAdapterOutputMeasurementKeys != null && actionAdapterOutputMeasurementKeys.Any()) outputMeasurementKeys = outputMeasurementKeys.Concat(actionAdapterOutputMeasurementKeys).Distinct(); } } // Get the full list of input measurements that can be demanded in this Iaon session if ((object)m_outputAdapters != null) inputMeasurementKeys = m_outputAdapters.InputMeasurementKeys; if ((object)m_actionAdapters != null) { if (inputMeasurementKeys == null || !inputMeasurementKeys.Any()) { inputMeasurementKeys = m_actionAdapters.InputMeasurementKeys; } else { MeasurementKey[] actionAdapterInputMeasurementKeys = m_actionAdapters.InputMeasurementKeys; if ((object)actionAdapterInputMeasurementKeys != null && actionAdapterInputMeasurementKeys.Length > 0) inputMeasurementKeys = inputMeasurementKeys.Concat(actionAdapterInputMeasurementKeys).Distinct(); } } } // Handle connect on demand action adapters and output adapters based on currently provisioned output measurements if ((object)outputMeasurementKeys != null && outputMeasurementKeys.Any()) { if ((object)m_actionAdapters != null) { // Start or stop connect on demand action adapters based on need, i.e., they handle any of the currently created output measurements foreach (IActionAdapter actionAdapter in m_actionAdapters) { if (!actionAdapter.AutoStart && actionAdapter.RespectInputDemands) { // Create an intersection between the measurements the adapter can handle and those that are demanded throughout this Iaon session if ((object)actionAdapter.InputMeasurementKeys != null && actionAdapter.InputMeasurementKeys.Length > 0) requestedInputMeasurementKeys = actionAdapter.InputMeasurementKeys.Intersect(outputMeasurementKeys).ToArray(); else requestedInputMeasurementKeys = emptyKeys; // Only update requested input keys if they have changed since adapters may use this as a notification to resubscribe to needed data if (actionAdapter.RequestedInputMeasurementKeys.CompareTo(requestedInputMeasurementKeys) != 0) actionAdapter.RequestedInputMeasurementKeys = requestedInputMeasurementKeys; // Do not start or stop adapter // Adapter will be stopped or started later // after calculating requested output measurements } } } if ((object)m_outputAdapters != null) { // Start or stop connect on demand output adapters based on need, i.e., they handle any of the currently created output measurements foreach (IOutputAdapter outputAdapter in m_outputAdapters) { if (!outputAdapter.AutoStart) { // Create an intersection between the measurements the adapter can handle and those that are demanded throughout this Iaon session if ((object)outputAdapter.InputMeasurementKeys != null && outputAdapter.InputMeasurementKeys.Length > 0) requestedInputMeasurementKeys = outputAdapter.InputMeasurementKeys.Intersect(outputMeasurementKeys).ToArray(); else requestedInputMeasurementKeys = emptyKeys; // Only update requested input keys if they have changed since adapters may use this as a notification to resubscribe to needed data if (outputAdapter.RequestedInputMeasurementKeys.CompareTo(requestedInputMeasurementKeys) != 0) outputAdapter.RequestedInputMeasurementKeys = requestedInputMeasurementKeys; // Start or stop adapter outputAdapter.Enabled = ((object)outputAdapter.RequestedInputMeasurementKeys != null && outputAdapter.RequestedInputMeasurementKeys.Length > 0); } } } } else { // Handle special case of clearing requested input keys for connect on demand action adapters when no output measurement keys are defined if ((object)m_actionAdapters != null) { foreach (IActionAdapter actionAdapter in m_actionAdapters) { if (!actionAdapter.AutoStart && (object)actionAdapter.RequestedInputMeasurementKeys != null && actionAdapter.RespectInputDemands) actionAdapter.RequestedInputMeasurementKeys = null; // Do not start or stop adapter // Adapter will be stopped or started later // after calculating requested output measurements } } // Handle special case of clearing requested input keys and stopping connect on demand output adapters when no output measurement keys are defined if ((object)m_outputAdapters != null) { foreach (IOutputAdapter outputAdapter in m_outputAdapters) { if (!outputAdapter.AutoStart) { if ((object)outputAdapter.RequestedInputMeasurementKeys != null) outputAdapter.RequestedInputMeasurementKeys = null; outputAdapter.Enabled = false; } } } } // Handle connect on demand action adapters and input adapters based on currently demanded input measurements if ((object)inputMeasurementKeys != null && inputMeasurementKeys.Any()) { if ((object)m_actionAdapters != null) { // Start or stop connect on demand action adapters based on need, i.e., they provide any of the currently demanded input measurements foreach (IActionAdapter actionAdapter in m_actionAdapters) { if (!actionAdapter.AutoStart) { if (actionAdapter.RespectOutputDemands) { // Create an intersection between the measurements the adapter can provide and those that are demanded throughout this Iaon session if ((object)actionAdapter.OutputMeasurements != null && actionAdapter.OutputMeasurements.Length > 0) requestedOutputMeasurementKeys = actionAdapter.OutputMeasurementKeys().Intersect(inputMeasurementKeys).ToArray(); else requestedOutputMeasurementKeys = emptyKeys; // Only update requested output keys if they have changed since adapters may use this as a notification to resubscribe to needed data if (actionAdapter.RequestedOutputMeasurementKeys.CompareTo(requestedOutputMeasurementKeys) != 0) actionAdapter.RequestedOutputMeasurementKeys = requestedOutputMeasurementKeys; } // Start or stop adapter, action adapter should only be stopped if it also has no requested input measurements keys, as determined prior if (actionAdapter.RespectOutputDemands && (object)actionAdapter.RequestedOutputMeasurementKeys != null && actionAdapter.RequestedOutputMeasurementKeys.Length > 0) actionAdapter.Enabled = true; else actionAdapter.Enabled = (actionAdapter.RespectInputDemands && (object)actionAdapter.RequestedInputMeasurementKeys != null && actionAdapter.RequestedInputMeasurementKeys.Length > 0); } } } if ((object)m_inputAdapters != null) { // Start or stop connect on demand input adapters based on need, i.e., they provide any of the currently demanded input measurements foreach (IInputAdapter inputAdapter in m_inputAdapters) { if (!inputAdapter.AutoStart) { // Create an intersection between the measurements the adapter can provide and those that are demanded throughout this Iaon session if ((object)inputAdapter.OutputMeasurements != null && inputAdapter.OutputMeasurements.Length > 0) requestedOutputMeasurementKeys = inputAdapter.OutputMeasurementKeys().Intersect(inputMeasurementKeys).ToArray(); else requestedOutputMeasurementKeys = emptyKeys; // Only update requested output keys if they have changed since adapters may use this as a notification to resubscribe to needed data if (inputAdapter.RequestedOutputMeasurementKeys.CompareTo(requestedOutputMeasurementKeys) != 0) inputAdapter.RequestedOutputMeasurementKeys = requestedOutputMeasurementKeys; // Start or stop adapter inputAdapter.Enabled = ((object)inputAdapter.RequestedOutputMeasurementKeys != null && inputAdapter.RequestedOutputMeasurementKeys.Length > 0); } } } } else { // Handle special case of clearing requested output keys and stopping connect on demand action adapters when no input measurement keys are defined if ((object)m_actionAdapters != null) { foreach (IActionAdapter actionAdapter in m_actionAdapters) { if (!actionAdapter.AutoStart) { if (actionAdapter.RespectOutputDemands) { if ((object)actionAdapter.RequestedOutputMeasurementKeys != null) actionAdapter.RequestedOutputMeasurementKeys = null; } // Action adapter should be stopped if it has no requested input measurements keys, as determined prior if (!(actionAdapter.RespectInputDemands && (object)actionAdapter.RequestedInputMeasurementKeys != null && actionAdapter.RequestedInputMeasurementKeys.Length > 0)) actionAdapter.Enabled = false; } } } // Handle special case of clearing requested output keys and stopping connect on demand input adapters when no input measurement keys are defined if ((object)m_inputAdapters != null) { foreach (IInputAdapter inputAdapter in m_inputAdapters) { if (!inputAdapter.AutoStart) { if ((object)inputAdapter.RequestedOutputMeasurementKeys != null) inputAdapter.RequestedOutputMeasurementKeys = null; inputAdapter.Enabled = false; } } } } } /// /// Calculates the input and output measurement key dependency chain based on a set of input measurement keys restriction. /// /// Input measurement keys restriction. /// Dependent output measurement keys to return. /// Dependent input measurement keys to return. protected virtual void TraverseMeasurementKeyDependencyChain(MeasurementKey[] inputMeasurementKeysRestriction, out IEnumerable outputMeasurementKeys, out IEnumerable inputMeasurementKeys) { List inputMeasurementKeyList = new List(inputMeasurementKeysRestriction); outputMeasurementKeys = null; List actionAdapterList = new List(); IEnumerable actionAdapters = null; // Keep walking dependency chain until all subordinate action adapters have been found do { if ((object)actionAdapters != null) { actionAdapterList.AddRange(actionAdapters); IEnumerable newInputKeys = actionAdapterList.InputMeasurementKeys(); if ((object)newInputKeys != null && newInputKeys.Any()) inputMeasurementKeyList = inputMeasurementKeyList.Concat(newInputKeys).Distinct().ToList(); } actionAdapters = FindDistinctAdapters(m_actionAdapters, inputMeasurementKeyList, actionAdapterList); } while ((object)actionAdapters != null); if (actionAdapterList.Count > 0) outputMeasurementKeys = actionAdapterList.OutputMeasurementKeys(); List inputAdapterList = new List(); IEnumerable inputAdapters = null; // Keep walking dependency chain until all subordinate input adapters have been found do { if ((object)inputAdapters != null) { inputAdapterList.AddRange(inputAdapters); IEnumerable newInputKeys = inputAdapterList.InputMeasurementKeys(); if ((object)newInputKeys != null && newInputKeys.Any()) inputMeasurementKeyList = inputMeasurementKeyList.Concat(newInputKeys).Distinct().ToList(); } inputAdapters = FindDistinctAdapters(m_inputAdapters, inputMeasurementKeyList, inputAdapterList); } while ((object)inputAdapters != null); if (inputAdapterList.Count > 0) { if ((object)outputMeasurementKeys == null) outputMeasurementKeys = inputAdapterList.OutputMeasurementKeys(); else outputMeasurementKeys = outputMeasurementKeys.Concat(inputAdapterList.OutputMeasurementKeys()).Distinct(); } inputMeasurementKeys = inputMeasurementKeyList; } // Find all the distinct adapters (i.e., that haven't been found already) in the source list for the given input measurement keys list private IEnumerable FindDistinctAdapters(IEnumerable sourceCollection, IEnumerable inputMeasurementKeysList, IEnumerable existingList) where T : IAdapter { IEnumerable adapters = null; if ((object)sourceCollection != null) { lock (sourceCollection) { adapters = sourceCollection.Where(adapter => !existingList.Contains(adapter) && adapter.OutputMeasurementKeys().Any(inputMeasurementKeysList.Contains)); } if (!adapters.Any()) adapters = null; } return adapters; } #endregion } }