/* ======================================================================== * 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 GetManagerHandleAsync(NodeId nodeId, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.GetManagerHandleAsync(nodeId, cancellationToken); } object? handle = SyncNodeManager.GetManagerHandle(nodeId); // Return a completed ValueTask since the underlying call is synchronous. return new ValueTask(handle!); } /// public ValueTask GetNodeMetadataAsync( OperationContext context, object targetHandle, BrowseResultMask resultMask, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.GetNodeMetadataAsync(context, targetHandle, resultMask, cancellationToken); } NodeMetadata? nodeMetadata = SyncNodeManager.GetNodeMetadata(context, targetHandle, resultMask); // Return a completed ValueTask since the underlying call is synchronous. return new ValueTask(nodeMetadata!); } /// public ValueTask GetPermissionMetadataAsync( OperationContext context, object targetHandle, BrowseResultMask resultMask, Dictionary uniqueNodesServiceAttributesCache, bool permissionsOnly, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.GetPermissionMetadataAsync( context, targetHandle, resultMask, uniqueNodesServiceAttributesCache, permissionsOnly, cancellationToken); } if (SyncNodeManager is INodeManager2 nodeManager2) { NodeMetadata? nodeMetadata = nodeManager2.GetPermissionMetadata( context, targetHandle, resultMask, uniqueNodesServiceAttributesCache, permissionsOnly); return new ValueTask(nodeMetadata); } return new ValueTask((NodeMetadata?)null); } /// public ValueTask HistoryReadAsync( OperationContext context, HistoryReadDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, ArrayOf nodesToRead, IList results, IList errors, CancellationToken cancellationToken = default) { if (SyncNodeManager is IHistoryReadAsyncNodeManager asyncNodeManager) { return asyncNodeManager.HistoryReadAsync( context, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors, cancellationToken); } SyncNodeManager.HistoryRead( context, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask HistoryUpdateAsync( OperationContext context, Type detailsType, ArrayOf nodesToUpdate, IList results, IList errors, CancellationToken cancellationToken = default) { if (SyncNodeManager is IHistoryUpdateAsyncNodeManager asyncNodeManager) { return asyncNodeManager.HistoryUpdateAsync( context, detailsType, nodesToUpdate, results, errors, cancellationToken); } SyncNodeManager.HistoryUpdate( context, detailsType, nodesToUpdate, results, errors); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask IsNodeInViewAsync( OperationContext context, NodeId viewId, object nodeHandle, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.IsNodeInViewAsync(context, viewId, nodeHandle, cancellationToken); } if (SyncNodeManager is INodeManager2 nodeManager2) { bool result = nodeManager2.IsNodeInView(context, viewId, nodeHandle); return new ValueTask(result); } throw new NotImplementedException(); } /// public ValueTask ModifyMonitoredItemsAsync(OperationContext context, TimestampsToReturn timestampsToReturn, IList monitoredItems, ArrayOf itemsToModify, IList errors, IList filterErrors, CancellationToken cancellationToken = default) { if (SyncNodeManager is IModifyMonitoredItemsAsyncNodeManager asyncNodeManager) { return asyncNodeManager.ModifyMonitoredItemsAsync( context, timestampsToReturn, monitoredItems, itemsToModify, errors, filterErrors, cancellationToken); } SyncNodeManager.ModifyMonitoredItems( context, timestampsToReturn, monitoredItems, itemsToModify, errors, filterErrors); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask ReadAsync(OperationContext context, double maxAge, ArrayOf nodesToRead, IList values, IList errors, CancellationToken cancellationToken = default) { if (SyncNodeManager is IReadAsyncNodeManager asyncNodeManager) { return asyncNodeManager.ReadAsync( context, maxAge, nodesToRead, values, errors, cancellationToken); } SyncNodeManager.Read(context, maxAge, nodesToRead, values, errors); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask RestoreMonitoredItemsAsync( IList itemsToRestore, IList monitoredItems, IUserIdentity savedOwnerIdentity, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.RestoreMonitoredItemsAsync(itemsToRestore, monitoredItems, savedOwnerIdentity, cancellationToken); } SyncNodeManager.RestoreMonitoredItems(itemsToRestore, monitoredItems, savedOwnerIdentity); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask SessionClosingAsync( OperationContext context, NodeId sessionId, bool deleteSubscriptions, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.SessionClosingAsync(context, sessionId, deleteSubscriptions, cancellationToken); } if (SyncNodeManager is INodeManager2 nodeManager2) { nodeManager2.SessionClosing(context, sessionId, deleteSubscriptions); } // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask SessionActivatedAsync( OperationContext context, NodeId sessionId, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.SessionActivatedAsync(context, sessionId, cancellationToken); } if (SyncNodeManager is INodeManager3 nodeManager3) { nodeManager3.SessionActivated(context, sessionId); } // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask SetMonitoringModeAsync( OperationContext context, MonitoringMode monitoringMode, IList monitoredItems, IList processedItems, IList errors, CancellationToken cancellationToken = default) { if (SyncNodeManager is ISetMonitoringModeAsyncNodeManager asyncNodeManager) { return asyncNodeManager.SetMonitoringModeAsync( context, monitoringMode, monitoredItems, processedItems, errors, cancellationToken); } SyncNodeManager.SetMonitoringMode( context, monitoringMode, monitoredItems, processedItems, errors); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask SubscribeToAllEventsAsync( OperationContext context, uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.SubscribeToAllEventsAsync( context, subscriptionId, monitoredItem, unsubscribe, cancellationToken); } ServiceResult result = SyncNodeManager.SubscribeToAllEvents( context, subscriptionId, monitoredItem, unsubscribe); // Return a completed ValueTask since the underlying call is synchronous. return new ValueTask(result); } /// public ValueTask SubscribeToEventsAsync( OperationContext context, object sourceId, uint subscriptionId, IEventMonitoredItem monitoredItem, bool unsubscribe, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.SubscribeToEventsAsync( context, sourceId, subscriptionId, monitoredItem, unsubscribe, cancellationToken); } ServiceResult result = SyncNodeManager.SubscribeToEvents( context, sourceId, subscriptionId, monitoredItem, unsubscribe); // Return a completed ValueTask since the underlying call is synchronous. return new ValueTask(result); } /// public ValueTask TransferMonitoredItemsAsync( OperationContext context, bool sendInitialValues, IList monitoredItems, IList processedItems, IList errors, CancellationToken cancellationToken = default) { if (SyncNodeManager is ITransferMonitoredItemsAsyncNodeManager asyncNodeManager) { return asyncNodeManager.TransferMonitoredItemsAsync( context, sendInitialValues, monitoredItems, processedItems, errors, cancellationToken); } SyncNodeManager.TransferMonitoredItems( context, sendInitialValues, monitoredItems, processedItems, errors); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask TranslateBrowsePathAsync( OperationContext context, object sourceHandle, RelativePathElement relativePath, IList targetIds, IList unresolvedTargetIds, CancellationToken cancellationToken = default) { if (SyncNodeManager is ITranslateBrowsePathAsyncNodeManager asyncNodeManager) { return asyncNodeManager.TranslateBrowsePathAsync(context, sourceHandle, relativePath, targetIds, unresolvedTargetIds, cancellationToken); } SyncNodeManager.TranslateBrowsePath(context, sourceHandle, relativePath, targetIds, unresolvedTargetIds); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask WriteAsync( OperationContext context, ArrayOf nodesToWrite, IList errors, CancellationToken cancellationToken = default) { if (SyncNodeManager is IWriteAsyncNodeManager asyncNodeManager) { return asyncNodeManager.WriteAsync(context, nodesToWrite, errors, cancellationToken); } SyncNodeManager.Write(context, nodesToWrite, errors); // Return a completed ValueTask since the underlying call is synchronous. return default; } /// public ValueTask ValidateEventRolePermissionsAsync( IEventMonitoredItem monitoredItem, IFilterTarget filterTarget, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.ValidateEventRolePermissionsAsync(monitoredItem, filterTarget, cancellationToken); } if (SyncNodeManager is INodeManager3 nodeManager2) { return new ValueTask(nodeManager2.ValidateEventRolePermissions(monitoredItem, filterTarget)); } return new ValueTask(ServiceResult.Good); } /// public ValueTask ValidateRolePermissionsAsync( OperationContext operationContext, NodeId nodeId, PermissionType requestedPermission, CancellationToken cancellationToken = default) { if (SyncNodeManager is IAsyncNodeManager asyncNodeManager) { return asyncNodeManager.ValidateRolePermissionsAsync(operationContext, nodeId, requestedPermission, cancellationToken); } if (SyncNodeManager is INodeManager3 nodeManager2) { return new ValueTask(nodeManager2.ValidateRolePermissions(operationContext, nodeId, requestedPermission)); } return new ValueTask(ServiceResult.Good); } /// public bool IsMultipleEventConsumerNode(NodeId nodeId) { return false; } /// /// /// The legacy synchronous contract does not /// expose NodeManagement service hooks. Adapted managers therefore /// opt out by returning false; the master dispatcher rejects /// per-item requests with . /// public bool AllowNodeManagement => SyncNodeManager is INodeManagementAsyncNodeManager nm && nm.AllowNodeManagement; /// public ValueTask<(ServiceResult result, NodeId addedNodeId)> AddNodeAsync( OperationContext context, AddNodesItem item, CancellationToken cancellationToken = default) { if (SyncNodeManager is INodeManagementAsyncNodeManager nm) { return nm.AddNodeAsync(context, item, cancellationToken); } return new ValueTask<(ServiceResult, NodeId)>( (new ServiceResult(StatusCodes.BadServiceUnsupported), NodeId.Null)); } /// public ValueTask DeleteNodeAsync( OperationContext context, DeleteNodesItem item, CancellationToken cancellationToken = default) { if (SyncNodeManager is INodeManagementAsyncNodeManager nm) { return nm.DeleteNodeAsync(context, item, cancellationToken); } return new ValueTask(new ServiceResult(StatusCodes.BadServiceUnsupported)); } /// public ValueTask AddReferenceAsync( OperationContext context, AddReferencesItem item, CancellationToken cancellationToken = default) { if (SyncNodeManager is INodeManagementAsyncNodeManager nm) { return nm.AddReferenceAsync(context, item, cancellationToken); } return new ValueTask(new ServiceResult(StatusCodes.BadServiceUnsupported)); } /// public ValueTask DeleteReferenceAsync( OperationContext context, DeleteReferencesItem item, CancellationToken cancellationToken = default) { if (SyncNodeManager is INodeManagementAsyncNodeManager nm) { return nm.DeleteReferenceAsync(context, item, cancellationToken); } return new ValueTask(new ServiceResult(StatusCodes.BadServiceUnsupported)); } /// /// Frees any unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// An overrideable version of the Dispose. /// protected virtual void Dispose(bool disposing) { if (disposing) { (SyncNodeManager as IDisposable)?.Dispose(); } } } }