//******************************************************************************************************
// AuthorizedMeasurementsQuery.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/06/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.Data;
using System.Linq;
using System.Threading;
using GSF.Collections;
using GSF.Console;
using GSF.Data;
using GSF.ServiceProcess;
using GSF.Threading;
namespace GSF.TimeSeries.UI
{
///
/// Represents an object that will query DataSubscriber instances to determine if measurements are authorized.
///
public class AuthorizedMeasurementsQuery : 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 full list of authorized measurement signal IDs.
///
///
/// is an array of the based signal IDs of the authorized measurements.
///
public event EventHandler> AuthorizedMeasurements;
///
/// 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_authorizedSignalIDs;
private LongSynchronizedOperation m_authorizationQueryOperation;
private ISet m_authorizationQueryIDs;
private object m_authorizationQueryLock;
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 AuthorizedMeasurementsQuery()
{
m_serviceClient = CommonFunctions.GetWindowsServiceClient();
m_serviceClient.Helper.ReceivedServiceResponse += Helper_ReceivedServiceResponse;
m_authorizedSignalIDs = new List();
m_responseComplete = new AutoResetEvent(false);
m_authorizationQueryOperation = new LongSynchronizedOperation(ExecuteAuthorizationQuery) { IsBackground = true };
m_authorizationQueryIDs = new HashSet();
m_authorizationQueryLock = new object();
m_responseTimeout = DefaultResponseTimeout;
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~AuthorizedMeasurementsQuery()
{
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 authorization state of the specified .
///
/// Measurement signal IDs to request authorization state for.
public void RequestAuthorizationStatus(IEnumerable sourceMeasurements)
{
if ((object)sourceMeasurements != null)
{
lock (m_authorizationQueryLock)
{
m_authorizationQueryIDs.UnionWith(sourceMeasurements);
if (m_authorizationQueryIDs.Count > 0)
m_authorizationQueryOperation.RunOnceAsync();
}
}
}
// Execute authorization query
private void ExecuteAuthorizationQuery()
{
try
{
List sourceMeasurements;
lock (m_authorizationQueryLock)
{
sourceMeasurements = m_authorizationQueryIDs.ToList();
m_authorizationQueryIDs.Clear();
}
List deviceIDs = new List();
AdoDataConnection database = null;
// Query associated device ID list for given measurements
try
{
database = new AdoDataConnection(CommonFunctions.DefaultSettingsCategory);
string guidPrefix = database.DatabaseType == DatabaseType.Access ? "{" : "'";
string guidSuffix = database.DatabaseType == DatabaseType.Access ? "}" : "'";
string query = string.Format("SELECT DISTINCT DeviceID FROM ActiveMeasurement WHERE ProtocolType = 'Measurement' AND SignalID IN ({0})", sourceMeasurements.Select(signalID => guidPrefix + signalID.ToString() + guidSuffix).ToDelimitedString(", "));
DataTable measurementDevices = database.Connection.RetrieveData(database.AdapterType, query);
foreach (DataRow row in measurementDevices.Rows)
{
int? deviceID = row.ConvertNullableField("DeviceID");
if (deviceID.HasValue)
{
// Validate that device ID is unique (not trusting all databases will handle DISTINCT properly)
if (deviceIDs.BinarySearch(deviceID.Value) < 0)
{
deviceIDs.Add(deviceID.Value);
deviceIDs.Sort();
}
}
}
}
finally
{
if ((object)database != null)
database.Dispose();
}
// Clear existing destination signal ID lists
lock (m_authorizedSignalIDs)
{
m_authorizedSignalIDs.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 DataSubscribers to determine signal authorizations
foreach (int deviceID in deviceIDs)
{
// Send command for authorized signals for this device
CommonFunctions.SendCommandToService(string.Format("INVOKE {0} GetAuthorizedSignalIDs", deviceID));
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 source measurements to use as a filter to authorized measurements
List sourceFilter = new List(sourceMeasurements);
sourceFilter.Sort();
Guid[] authorizedSignalIDs = null;
// Provide user with a distinct list of query results - if there are any
lock (m_authorizedSignalIDs)
{
if (m_authorizedSignalIDs.Count > 0)
authorizedSignalIDs = m_authorizedSignalIDs.Distinct().Where(signalID => sourceFilter.BinarySearch(signalID) >= 0).ToArray();
}
if (authorizedSignalIDs != null && authorizedSignalIDs.Length > 0)
OnAuthorizedMeasurements(authorizedSignalIDs);
}
}
catch (Exception ex)
{
OnProcessException(new InvalidOperationException("Authorized measurements query error: " + ex.Message, ex));
}
}
///
/// Raises event.
///
/// Authorized measurements.
protected virtual void OnAuthorizedMeasurements(Guid[] authorizedMeasurements)
{
if (AuthorizedMeasurements != null)
AuthorizedMeasurements(this, new EventArgs(authorizedMeasurements));
}
///
/// 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