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