/* ========================================================================
* Copyright (c) 2005-2016 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Opc.Ua;
using Opc.Ua.Server;
using Opc.Ua.Com;
using OpcRcw.Da;
namespace Opc.Ua.Com.Client
{
///
/// Manages the COM connections used by the UA server.
///
public class ComClientManager : IDisposable
{
#region Constructors
///
/// Initializes a new instance of the class.
///
public ComClientManager()
{
}
#endregion
#region IDisposable Members
///
/// Frees any unmanaged resources.
///
public void Dispose()
{
Dispose(true);
}
///
/// An overrideable version of the Dispose.
///
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Utils.SilentDispose(m_statusTimer);
m_statusTimer = null;
Utils.SilentDispose(m_defaultClient);
m_defaultClient = null;
}
}
#endregion
#region Public Members
///
/// Selects the COM client to use for the current context.
///
/// The context.
/// The whether to use the default context.
/// A COM client instance.
public virtual ComClient SelectClient(ServerSystemContext context, bool useDefault)
{
if (useDefault)
{
return DefaultClient;
}
return GetLocalizedClient(context.UserIdentity, context.PreferredLocales);
}
///
/// Initializes the manager by creating the default instance.
///
public void Initialize(
ServerSystemContext context,
ComClientConfiguration configuration,
ComServerStatusState statusNode,
object statusNodeLock,
WaitCallback reconnectCallback)
{
m_defaultSystemContext = context;
m_configuration = configuration;
m_statusNode = statusNode;
m_statusNodeLock = statusNodeLock;
m_statusUpdateInterval = m_configuration.MaxReconnectWait;
m_reconnectCallback = reconnectCallback;
// limit status updates to once per 10 seconds.
if (m_statusUpdateInterval < 10000)
{
m_statusUpdateInterval = 10000;
}
StartStatusTimer(OnStatusTimerExpired);
}
///
/// Returns a localized client based on the preferred locales.
///
/// The identity to use.
/// The locales to use.
/// A localized client.
public ComClient GetLocalizedClient(IUserIdentity identity, IList preferredLocales)
{
return GetLocalizedClient(identity, ComClient.SelectLocaleId(AvailableLocaleIds, preferredLocales));
}
///
/// Returns a localized client for the specified locale id.
///
/// The identity.
/// The locales id.
/// A localized client.
public ComClient GetLocalizedClient(IUserIdentity identity, int localeId)
{
// check if a logon is required.
string userName = null;
if (identity != null && identity.TokenType == UserTokenType.UserName)
{
userName = (identity.GetIdentityToken() as UserNameIdentityToken).UserName;
}
if (String.IsNullOrEmpty(userName) && localeId == ComUtils.LOCALE_SYSTEM_DEFAULT)
{
Utils.Trace("COM Client Selected: DEFAULT (no match for locale)");
return DefaultClient;
}
// create the key.
StringBuilder buffer = new StringBuilder();
buffer.Append(localeId);
if (!String.IsNullOrEmpty(userName))
{
buffer.Append(':');
buffer.Append(userName);
}
string key = buffer.ToString();
if (m_localizedClients == null)
{
m_localizedClients = new Dictionary();
}
ComClient client = null;
if (!m_localizedClients.TryGetValue(key, out client))
{
client = CreateClient();
client.Key = key;
client.LocaleId = localeId;
client.UserIdentity = identity;
client.CreateInstance();
m_localizedClients[key] = client;
}
// Utils.Trace("COM Client Seleted: {0}", key);
return client;
}
#endregion
#region Protected Members
///
/// Gets the default system context.
///
/// The default system context.
protected ServerSystemContext DefaultSystemContext
{
get { return m_defaultSystemContext; }
}
///
/// Gets the configuration.
///
/// The configuration.
protected ComClientConfiguration Configuration
{
get { return m_configuration; }
}
///
/// Gets or sets the default COM client instance.
///
/// The default client.
protected ComClient DefaultClient
{
get { return m_defaultClient; }
set { m_defaultClient = value; }
}
///
/// The locale ids supported by the server.
///
protected int[] AvailableLocaleIds { get; private set; }
///
/// Gets the status node.
///
/// The status node.
protected ComServerStatusState StatusNode
{
get { return m_statusNode; }
}
///
/// Gets the synchronization object for the status node.
///
/// The ynchronization object for the status node.
protected object StatusNodeLock
{
get { return m_statusNodeLock; }
}
///
/// Starts the status timer.
///
/// The callback to use when the timer expires.
protected void StartStatusTimer(TimerCallback callback)
{
if (m_statusTimer != null)
{
m_statusTimer.Dispose();
m_statusTimer = null;
}
m_statusTimer = new Timer(callback, null, 0, m_statusUpdateInterval);
}
///
/// Creates a new client object.
///
protected virtual ComClient CreateClient()
{
return null;
}
///
/// Updates the status node.
///
/// True if the update was successful.
protected virtual bool UpdateStatus()
{
return false;
}
#endregion
#region Private Methods
///
/// Called when thes status timer expires.
///
/// The state.
private void OnStatusTimerExpired(object state)
{
try
{
// create the client if it has not already been created.
if (m_defaultClient == null)
{
m_defaultClient = CreateClient();
m_defaultClient.Key = String.Empty;
// set default locale.
if (AvailableLocaleIds != null && AvailableLocaleIds.Length > 0)
{
m_defaultClient.LocaleId = AvailableLocaleIds[0];
}
else
{
m_defaultClient.LocaleId = ComUtils.LOCALE_SYSTEM_DEFAULT;
}
m_defaultClient.CreateInstance();
m_lastStatusUpdate = DateTime.UtcNow;
}
// check if the last status updates appear to be hanging.
bool reconnected = false;
if (m_lastStatusUpdate.AddMilliseconds(m_statusUpdateInterval*1.1) < DateTime.UtcNow)
{
if (m_reconnecting)
{
return;
}
m_reconnecting = true;
try
{
Utils.Trace("Communication with COM server failed. Disposing server and reconnecting.");
// dispose existing client in the background in case it blocks.
ThreadPool.QueueUserWorkItem(OnDisposeClient, m_defaultClient);
// dispose localized clients.
if (m_localizedClients != null)
{
foreach (ComClient localizedClient in m_localizedClients.Values)
{
ThreadPool.QueueUserWorkItem(OnDisposeClient, localizedClient);
}
m_localizedClients.Clear();
}
// create a new client.
m_defaultClient = CreateClient();
m_defaultClient.CreateInstance();
reconnected = true;
}
finally
{
m_reconnecting = false;
}
}
// fetches the status from the server and updates the status node.
if (UpdateStatus())
{
AvailableLocaleIds = m_defaultClient.QueryAvailableLocales();
m_lastStatusUpdate = DateTime.UtcNow;
if (reconnected && m_reconnectCallback != null)
{
m_reconnectCallback(this);
}
}
}
catch (Exception e)
{
ComUtils.TraceComError(e, "Error fetching status from the COM server.");
}
}
///
/// Called when a misbehaving client object needs to be disposed.
///
/// The client object.
private void OnDisposeClient(object state)
{
Utils.SilentDispose(state);
}
#endregion
#region Private Fields
private ComClientConfiguration m_configuration;
private ServerSystemContext m_defaultSystemContext;
private ComServerStatusState m_statusNode;
private object m_statusNodeLock;
private ComClient m_defaultClient;
private Timer m_statusTimer;
private DateTime m_lastStatusUpdate;
private int m_statusUpdateInterval;
private WaitCallback m_reconnectCallback;
private bool m_reconnecting;
private Dictionary m_localizedClients;
#endregion
}
}