//****************************************************************************************************** // AlarmStatusQuery.cs - Gbtc // // Copyright © 2013, 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: // ---------------------------------------------------------------------------------------------------- // 05/16/2013 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Threading; using GSF.Communication; using GSF.Console; using GSF.ServiceProcess; using GSF.Threading; namespace GSF.TimeSeries.UI { /// /// Represents as object that will query current alarm state. /// public class AlarmStatusQuery : IDisposable { #region [ Members ] // Constants /// /// Default value for . /// public const int DefaultResponseTimeout = 20000; // Default to waiting twenty seconds, could be lots of alarms // Events /// /// Provides collection containing raised alarms with the highest severity for each signal in the system. /// /// /// is a collection of the current high-severity states. /// public event EventHandler>> RaisedAlarmStates; /// /// 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 ICollection m_raisedAlarms; private readonly LongSynchronizedOperation m_alarmStateQueryOperation; private AutoResetEvent m_responseComplete; private int m_responseTimeout; private bool m_connectedToService; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new . /// public AlarmStatusQuery() { // Attach to service connected events CommonFunctions.ServiceConnectionRefreshed += CommonFunctions_ServiceConnectionRefreshed; // Determine initial state of connectivity UpdateServiceConnectivity(); m_alarmStateQueryOperation = new LongSynchronizedOperation(ExecuteAlarmStateQuery) { IsBackground = true }; m_responseComplete = new AutoResetEvent(false); m_responseTimeout = DefaultResponseTimeout; } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~AlarmStatusQuery() { 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; // Detach from service connected events CommonFunctions.ServiceConnectionRefreshed -= CommonFunctions_ServiceConnectionRefreshed; } } finally { m_disposed = true; // Prevent duplicate dispose. } } } private void UpdateServiceConnectivity(bool refreshRaisedAlarms = false) { // Detach from original service response if refreshing service connection if ((object)m_serviceClient != null && (object)m_serviceClient.Helper != null) m_serviceClient.Helper.ReceivedServiceResponse -= Helper_ReceivedServiceResponse; // Get new service client m_serviceClient = CommonFunctions.GetWindowsServiceClient(); ClientHelper clientHelper = ((object)m_serviceClient != null) ? m_serviceClient.Helper : null; IClient remotingClient = ((object)clientHelper != null) ? clientHelper.RemotingClient : null; if ((object)clientHelper != null) clientHelper.ReceivedServiceResponse += Helper_ReceivedServiceResponse; if ((object)remotingClient == null || remotingClient.CurrentState != ClientState.Connected) { // Remoting client is not connected to service m_connectedToService = false; // Release any waiting threads if service has been disconnected if ((object)m_responseComplete != null) m_responseComplete.Set(); } else { // If remoting client is connected, refresh raised alarms if requested and attach to connection terminated event m_connectedToService = true; if (refreshRaisedAlarms) RequestRaisedAlarmStates(); remotingClient.ConnectionTerminated += RemotingClient_ConnectionTerminated; } } private void CommonFunctions_ServiceConnectionRefreshed(object sender, EventArgs eventArgs) { // Determine new state of connectivity - since we are reconnecting and this could be to a // new node an/or a restarted system, re-request raised alarm states UpdateServiceConnectivity(true); } private void RemotingClient_ConnectionTerminated(object sender, EventArgs eventArgs) { IClient remotingClient = sender as IClient; // Attempt to detach from the event that just occurred if ((object)remotingClient != null) remotingClient.ConnectionTerminated -= RemotingClient_ConnectionTerminated; // Determine new state of connectivity UpdateServiceConnectivity(); } /// /// Requests the current state of highest severity alarms. /// public void RequestRaisedAlarmStates() { // Create single item operation queue so that // multiple requests will be handled one at a time m_alarmStateQueryOperation.RunOnceAsync(); } // Execute alarm state query private void ExecuteAlarmStateQuery() { // Nothing to do if not connected to service if (!m_connectedToService) return; try { // Clear any existing raised alarms m_raisedAlarms = null; // Reset state of response complete event (in case a prior event set never completed) if ((object)m_responseComplete != null) m_responseComplete.Reset(); // Send command to alarm services to get highest severity alarms CommonFunctions.SendCommandToService("INVOKE ALARM!SERVICES GetRaisedAlarms"); // Wait for command response allowing for processing time if ((object)m_responseComplete != null) { if (!m_responseComplete.WaitOne(m_responseTimeout)) OnProcessException(new TimeoutException(string.Format("Timed-out after {0} seconds waiting for service response{1}.", (m_responseTimeout / 1000.0D).ToString("0.00"), m_connectedToService ? "" : " - service is disconnected"))); } // If alarms were returned, expose them to consumer if ((object)m_raisedAlarms != null) OnRaisedAlarmStates(m_raisedAlarms); } catch (Exception ex) { OnProcessException(new InvalidOperationException("Alarm state query error: " + ex.Message, ex)); } } /// /// Raises event. /// /// Alarm states. protected virtual void OnRaisedAlarmStates(ICollection raisedAlarms) { if ((object)RaisedAlarmStates != null) RaisedAlarmStates(this, new EventArgs>(raisedAlarms)); } /// /// Raises event. /// /// Processing . protected virtual void OnProcessException(Exception ex) { if ((object)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; // A GetHighestSeverityAlarms INVOKE will have two attachments: an alarm array, 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"], "GetRaisedAlarms", true) == 0) { m_raisedAlarms = attachments[0] as ICollection; // Release waiting thread once desired response has been received if ((object)m_responseComplete != null) m_responseComplete.Set(); } } } } } } } #endregion } }