//****************************************************************************************************** // SubscriberStatusQuery.cs - Gbtc // // Copyright © 2012, 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 MIT License (MIT), 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/MIT // // 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: // ---------------------------------------------------------------------------------------------------- // 03/21/2012 - J. Ritchie Carroll // Generated original version of source code. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Linq; using System.Threading; using GSF.Console; using GSF.ServiceProcess; using GSF.Threading; namespace GSF.TimeSeries.UI { /// /// Represents an object that will query the external DataPublisher for real-time subscriber status. /// public class SubscriberStatusQuery : IDisposable { #region [ Members ] // Constants /// /// Default value for . /// public const int DefaultResponseTimeout = 1000; // Defaulting to waiting for one second for each round-trip request // Events /// /// Provides a dictionary of current subscriber statuses. /// /// /// is a dictionary keyed off the based subscriber IDs containing the current connected state and status information. /// public event EventHandler>>> SubscriberStatuses; /// /// Event is raised when there is an exception encountered while processing. /// /// /// is the exception that was thrown. /// public event EventHandler> ProcessException; // Fields private WindowsServiceClient m_serviceClient; private readonly List> m_subscriberStatuses; private LongSynchronizedOperation m_statusQueryOperation; private ISet m_statusQueryIDs; private object m_statusQueryLock; private AutoResetEvent m_responseComplete; private int m_requests; private int m_responses; private int m_responseTimeout; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new . /// public SubscriberStatusQuery() { m_serviceClient = CommonFunctions.GetWindowsServiceClient(); m_serviceClient.Helper.ReceivedServiceResponse += Helper_ReceivedServiceResponse; m_subscriberStatuses = new List>(); m_responseComplete = new AutoResetEvent(false); m_statusQueryOperation = new LongSynchronizedOperation(ExecuteStatusQuery) { IsBackground = true }; m_statusQueryIDs = new HashSet(); m_statusQueryLock = new object(); m_responseTimeout = DefaultResponseTimeout; m_statusQueryOperation.IsBackground = true; } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~SubscriberStatusQuery() { Dispose(false); } #endregion #region [ Properties ] /// /// Gets or sets allowed timeout, in milliseconds, for a command request response. /// public int ResponseTimeout { get { return m_responseTimeout; } set { m_responseTimeout = 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) { if ((object)m_serviceClient != null && (object)m_serviceClient.Helper != null) m_serviceClient.Helper.ReceivedServiceResponse -= Helper_ReceivedServiceResponse; m_serviceClient = null; if ((object)m_responseComplete != null) { // Release any waiting threads before disposing wait handle m_responseComplete.Set(); m_responseComplete.Dispose(); } m_responseComplete = null; } } finally { m_disposed = true; // Prevent duplicate dispose. } } } /// /// Requests the status of the specified . /// /// Subscriber IDs to request status for. public void RequestSubscriberStatus(IEnumerable subscriberIDs) { if ((object)subscriberIDs != null) { lock (m_statusQueryLock) { m_statusQueryIDs.UnionWith(subscriberIDs); if (m_statusQueryIDs.Count > 0) m_statusQueryOperation.RunOnceAsync(); } } } // Execute status query private void ExecuteStatusQuery() { try { List subscriberIDs; lock (m_statusQueryLock) { subscriberIDs = m_statusQueryIDs.ToList(); m_statusQueryIDs.Clear(); } // Clear existing subscriber statuses lock (m_subscriberStatuses) { m_subscriberStatuses.Clear(); } // Reset request and response counts to zero m_requests = 0; m_responses = 0; // Reset state of response complete event (in case a prior event set never completed) if ((object)m_responseComplete != null) m_responseComplete.Reset(); // Send service commands to external data publisher to determine subscriber states foreach (Guid subscriberID in subscriberIDs) { // Send command for authorized signals for this device CommonFunctions.SendCommandToService(string.Format("INVOKE TLS!DATAPUBLISHER GetSubscriberStatus {0}", subscriberID)); m_requests++; } if (m_requests > 0) { // Wait for command responses allowing processing time for each if ((object)m_responseComplete != null) { if (!m_responseComplete.WaitOne(m_requests * m_responseTimeout)) OnProcessException(new TimeoutException(string.Format("Timed-out after {0} seconds waiting for {1} service response{2}.", (m_requests * m_responseTimeout / 1000.0D).ToString("0.00"), m_requests, m_requests == 1 ? "" : "s"))); } // Create a sorted list of the subscriberIDs to use as a filter in case we get old responses from other queries List sourceFilter = new List(subscriberIDs); sourceFilter.Sort(); Dictionary> subscriberStatuses = null; // Provide user with a dictionary of query results - if there are any lock (m_subscriberStatuses) { if (m_subscriberStatuses.Count > 0) subscriberStatuses = m_subscriberStatuses.Where(tuple => sourceFilter.BinarySearch(tuple.Item1) >= 0).ToDictionary(key => key.Item1, value => new Tuple(value.Item2, value.Item3)); } if (subscriberStatuses != null && subscriberStatuses.Count > 0) OnSubscriberStatuses(subscriberStatuses); } } catch (Exception ex) { OnProcessException(new InvalidOperationException("Subscriber status query error: " + ex.Message, ex)); } } /// /// Raises event. /// /// Dictionary of subscriber statuses. protected virtual void OnSubscriberStatuses(Dictionary> subscriberStatuses) { if (SubscriberStatuses != null) SubscriberStatuses(this, new EventArgs>>(subscriberStatuses)); } /// /// Raises event. /// /// Processing . protected virtual void OnProcessException(Exception ex) { if (ProcessException != null) ProcessException(this, new EventArgs(ex)); } /// /// Handles ReceivedServiceResponse event. /// /// Source of the event. /// Event arguments. private void Helper_ReceivedServiceResponse(object sender, EventArgs e) { if ((object)e != null) { ServiceResponse response = e.Argument; if ((object)response != null) { string sourceCommand; bool responseSuccess; if (ClientHelper.TryParseActionableResponse(response, out sourceCommand, out responseSuccess) && responseSuccess) { if (!string.IsNullOrWhiteSpace(sourceCommand) && string.Compare(sourceCommand.Trim(), "INVOKE", true) == 0) { List attachments = response.Attachments; // An INVOKE for GetSubscriberStatus will have two attachments: a tuple of the values, item 0, and the original command arguments, item 1 if ((object)attachments != null && attachments.Count > 1) { Arguments arguments = attachments[1] as Arguments; // Check the method that was invoked - the second argument after the adapter ID if ((object)arguments != null && string.Compare(arguments["OrderedArg2"].ToNonNullString(), "GetSubscriberStatus", true) == 0) { Tuple subscriberStatus = attachments[0] as Tuple; if ((object)subscriberStatus != null) { lock (m_subscriberStatuses) { m_subscriberStatuses.Add(subscriberStatus); } } // A response for GetSubscriberStatus counts whether or not a status was returned m_responses++; } } } } // Release waiting thread once all responses have been received if (m_responses >= m_requests && (object)m_responseComplete != null) m_responseComplete.Set(); } } } #endregion } }