/* ========================================================================
* Copyright (c) 2005-2025 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.Threading;
using System.Threading.Tasks;
namespace Opc.Ua.Server
{
///
/// The factory for an
///
public static class AsyncNodeManagerAdapterFactory
{
///
/// returns an instance of
/// if the NodeManager does not implement the interface uses the
/// to create an IAsyncNodeManager compatible object
///
public static IAsyncNodeManager ToAsyncNodeManager(this INodeManager nodeManager)
{
if (nodeManager is IAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager;
}
return new AsyncNodeManagerAdapter(nodeManager);
}
}
///
/// An adapter that makes a synchronous INodeManager conform to the IAsyncNodeManager interface.
///
///
/// This allows synchronous, or only partially asynchronous node managers to be treated as asynchronous, which can help
/// unify the calling logic within the MasterNodeManager.
///
public class AsyncNodeManagerAdapter : IAsyncNodeManager, IDisposable
{
///
/// Initializes a new instance of the class.
///
/// The synchronous node manager to wrap.
/// is null.
public AsyncNodeManagerAdapter(INodeManager nodeManager)
{
SyncNodeManager = nodeManager ?? throw new ArgumentNullException(nameof(nodeManager));
}
///
public IEnumerable NamespaceUris => SyncNodeManager.NamespaceUris;
///
public INodeManager SyncNodeManager { get; }
///
public ValueTask AddReferencesAsync(
IDictionary> references,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.AddReferencesAsync(references, cancellationToken);
}
SyncNodeManager.AddReferences(references);
// Return a completed ValueTask since the underlying call is synchronous.
return default;
}
///
public ValueTask BrowseAsync(
OperationContext context,
ContinuationPoint continuationPoint,
IList references,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is IBrowseAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.BrowseAsync(context, continuationPoint, references, cancellationToken);
}
SyncNodeManager.Browse(context, ref continuationPoint, references);
// Return a completed ValueTask since the underlying call is synchronous.
return new ValueTask(continuationPoint);
}
///
public ValueTask CallAsync(
OperationContext context,
ArrayOf methodsToCall,
IList results,
IList errors,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is ICallAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.CallAsync(context, methodsToCall, results, errors, cancellationToken);
}
SyncNodeManager.Call(context, methodsToCall, results, errors);
// Return a completed ValueTask since the underlying call is synchronous.
return default;
}
///
public ValueTask FindMethodStateAsync(
OperationContext context,
CallMethodRequest methodToCall,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is INodeManager3 nodeManager)
{
MethodState method = nodeManager.FindMethodState(context, methodToCall);
return new ValueTask(method);
}
return new ValueTask(result: null!);
}
///
public ValueTask ConditionRefreshAsync(
OperationContext context,
IList monitoredItems,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is IConditionRefreshAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.ConditionRefreshAsync(context, monitoredItems, cancellationToken);
}
SyncNodeManager.ConditionRefresh(context, monitoredItems);
// Return a completed ValueTask since the underlying call is synchronous.
return default;
}
///
public ValueTask CreateAddressSpaceAsync(
IDictionary> externalReferences,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.CreateAddressSpaceAsync(externalReferences, cancellationToken);
}
SyncNodeManager.CreateAddressSpace(externalReferences);
// Return a completed ValueTask since the underlying call is synchronous.
return default;
}
///
public ValueTask CreateMonitoredItemsAsync(
OperationContext context,
uint subscriptionId,
double publishingInterval,
TimestampsToReturn timestampsToReturn,
ArrayOf itemsToCreate,
IList errors,
IList filterErrors,
IList monitoredItems,
bool createDurable,
MonitoredItemIdFactory monitoredItemIdFactory,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is ICreateMonitoredItemsAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.CreateMonitoredItemsAsync(
context,
subscriptionId,
publishingInterval,
timestampsToReturn,
itemsToCreate,
errors,
filterErrors,
monitoredItems,
createDurable,
monitoredItemIdFactory,
cancellationToken);
}
SyncNodeManager.CreateMonitoredItems(
context,
subscriptionId,
publishingInterval,
timestampsToReturn,
itemsToCreate,
errors,
filterErrors,
monitoredItems,
createDurable,
monitoredItemIdFactory);
// Return a completed ValueTask since the underlying call is synchronous.
return default;
}
///
public ValueTask DeleteAddressSpaceAsync(CancellationToken cancellationToken = default)
{
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.DeleteAddressSpaceAsync(cancellationToken);
}
SyncNodeManager.DeleteAddressSpace();
// Return a completed ValueTask since the underlying call is synchronous.
return default;
}
///
public ValueTask DeleteMonitoredItemsAsync(
OperationContext context,
IList monitoredItems,
IList processedItems,
IList errors,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is IDeleteMonitoredItemsAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.DeleteMonitoredItemsAsync(
context,
monitoredItems,
processedItems,
errors,
cancellationToken);
}
SyncNodeManager.DeleteMonitoredItems(context, monitoredItems, processedItems, errors);
// Return a completed ValueTask since the underlying call is synchronous.
return default;
}
///
public ValueTask DeleteReferenceAsync(
object sourceHandle,
NodeId referenceTypeId,
bool isInverse,
ExpandedNodeId targetId,
bool deleteBidirectional,
CancellationToken cancellationToken = default)
{
if (SyncNodeManager is IAsyncNodeManager asyncNodeManager)
{
return asyncNodeManager.DeleteReferenceAsync(sourceHandle,
referenceTypeId,
isInverse,
targetId,
deleteBidirectional,
cancellationToken);
}
ServiceResult result = SyncNodeManager.DeleteReference(sourceHandle,
referenceTypeId,
isInverse,
targetId,
deleteBidirectional);
// Return a completed ValueTask since the underlying call is synchronous.
return new ValueTask(result);
}
///
public ValueTask