org.eclipse.milo.opcua.sdk.client.AddressSpace Maven / Gradle / Ivy
/*
* Copyright (c) 2021 the Eclipse Milo Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.milo.opcua.sdk.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import org.eclipse.milo.opcua.sdk.client.ObjectTypeManager.ObjectNodeConstructor;
import org.eclipse.milo.opcua.sdk.client.nodes.UaDataTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaMethodNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaObjectNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaObjectTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaReferenceTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaViewNode;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.BuiltinReferenceType;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseDirection;
import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadResponse;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.l;
import static org.eclipse.milo.opcua.stack.core.util.FutureUtils.failedFuture;
import static org.eclipse.milo.opcua.stack.core.util.FutureUtils.failedUaFuture;
public class AddressSpace {
private final Logger logger = LoggerFactory.getLogger(getClass());
private NodeCache nodeCache = new NodeCache();
private BrowseOptions browseOptions = new BrowseOptions();
private final OpcUaClient client;
public AddressSpace(OpcUaClient client) {
this.client = client;
}
/**
* Get a {@link UaNode} instance for the Node identified by {@code nodeId}.
*
* @param nodeId the {@link NodeId} identifying the Node to get.
* @return a {@link UaNode} instance for the Node identified by {@code nodeId}.
* @throws UaException if an error occurs while creating the Node.
*/
public UaNode getNode(NodeId nodeId) throws UaException {
try {
return getNodeAsync(nodeId).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Get a {@link UaNode} instance for the Node identified by {@code nodeId}.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} identifying the Node to get.
* @return a CompletableFuture that completes successfully with the UaNode instance or
* completes exceptionally if a service-level error occurs.
*/
public CompletableFuture extends UaNode> getNodeAsync(NodeId nodeId) {
UaNode cachedNode = nodeCache.getIfPresent(nodeId);
if (cachedNode != null) {
return completedFuture(cachedNode);
} else {
return createNode(nodeId).whenComplete((node, ex) -> {
if (node != null) {
nodeCache.put(nodeId, node);
}
});
}
}
/**
* Get a {@link UaObjectNode} instance for the ObjectNode identified by {@code nodeId}.
*
* The type definition will be read when the instance is created. If this type definition is
* registered with the {@link ObjectTypeManager} a {@link UaObjectNode} of the appropriate
* subclass will be returned.
*
* @param nodeId the {@link NodeId} identifying the ObjectNode to get.
* @return a {@link UaObjectNode} instance for the ObjectNode identified by {@code nodeId}.
* @throws UaException if an error occurs while creating the ObjectNode.
*/
public UaObjectNode getObjectNode(NodeId nodeId) throws UaException {
try {
return getObjectNodeAsync(nodeId).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Get a {@link UaObjectNode} instance for the ObjectNode identified by {@code nodeId},
* assuming the type definition identified by {@code typeDefinitionId}.
*
* If this type definition is registered with the {@link ObjectTypeManager} a
* {@link UaObjectNode} of the appropriate subclass will be returned.
*
* @param nodeId the {@link NodeId} identifying the ObjectNode to get.
* @param typeDefinitionId the {@link NodeId} identifying the type definition.
* @return a {@link UaObjectNode} instance for the ObjectNode identified by {@code nodeId}.
* @throws UaException if an error occurs while creating the ObjectNode.
*/
public UaObjectNode getObjectNode(NodeId nodeId, NodeId typeDefinitionId) throws UaException {
try {
return getObjectNodeAsync(nodeId, typeDefinitionId).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Get a {@link UaObjectNode} instance for the ObjectNode identified by {@code nodeId}.
*
* The type definition will be read when the instance is created. If this type definition is
* registered with the {@link ObjectTypeManager} a {@link UaObjectNode} of the appropriate
* subclass will be returned.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} identifying the ObjectNode to get.
* @return a CompletableFuture that completes successfully with a {@link UaObjectNode} instance
* for the ObjectNode identified by {@code nodeId} or completes exceptionally if an error
* occurs creating the ObjectNode.
*/
public CompletableFuture getObjectNodeAsync(NodeId nodeId) {
UaNode cachedNode = nodeCache.getIfPresent(nodeId);
if (cachedNode instanceof UaObjectNode) {
return completedFuture((UaObjectNode) cachedNode);
} else {
CompletableFuture typeDefinitionFuture = readTypeDefinition(nodeId);
return typeDefinitionFuture.thenCompose(typeDefinitionId -> getObjectNodeAsync(nodeId, typeDefinitionId));
}
}
/**
* Get a {@link UaObjectNode} instance for the ObjectNode identified by {@code nodeId},
* assuming the type definition identified by {@code typeDefinitionId}.
*
* If this type definition is registered with the {@link ObjectTypeManager} a
* {@link UaObjectNode} of the appropriate subclass will be returned.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} identifying the ObjectNode to get.
* @param typeDefinitionId the {@link NodeId} identifying the type definition.
* @return a CompletableFuture that completes successfully with a {@link UaObjectNode} instance
* for the ObjectNode identified by {@code nodeId} or completes exceptionally if an error
* occurs creating the ObjectNode.
*/
public CompletableFuture getObjectNodeAsync(NodeId nodeId, NodeId typeDefinitionId) {
UaNode cachedNode = nodeCache.getIfPresent(nodeId);
if (cachedNode instanceof UaObjectNode) {
return completedFuture((UaObjectNode) cachedNode);
} else {
CompletableFuture future = readAttributes(nodeId, AttributeId.OBJECT_ATTRIBUTES);
return future.thenCompose(response -> {
List attributeValues = l(response.getResults());
try {
UaObjectNode node = newObjectNode(nodeId, typeDefinitionId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
}
/**
* Get a {@link UaVariableNode} instance for the VariableNode identified by {@code nodeId}.
*
* The type definition will be read when the instance is created. If this type definition is
* registered with the {@link VariableTypeManager} a {@link UaVariableNode} of the appropriate
* subclass will be returned.
*
* @param nodeId the {@link NodeId} identifying the VariableNode to get.
* @return a {@link UaVariableNode} instance for the VariableNode identified by {@code nodeId}.
* @throws UaException if an error occurs while creating the VariableNode.
*/
public UaVariableNode getVariableNode(NodeId nodeId) throws UaException {
try {
return getVariableNodeAsync(nodeId).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Get a {@link UaVariableNode} instance for the VariableNode identified by {@code nodeId},
* assuming the type definition identified by {@code typeDefinitionId}.
*
* If this type definition is registered with the {@link VariableTypeManager} a
* {@link UaVariableNode} of the appropriate subclass will be returned.
*
* @param nodeId the {@link NodeId} identifying the VariableNode to get.
* @param typeDefinitionId the {@link NodeId} identifying the type definition.
* @return a {@link UaVariableNode} instance for the VariableNode identified by {@code nodeId}.
* @throws UaException if an error occurs while creating the VariableNode.
*/
public UaVariableNode getVariableNode(NodeId nodeId, NodeId typeDefinitionId) throws UaException {
try {
return getVariableNodeAsync(nodeId, typeDefinitionId).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Get a {@link UaVariableNode} instance for the VariableNode identified by {@code nodeId}.
*
* The type definition will be read when the instance is created. If this type definition is
* registered with the {@link VariableTypeManager} a {@link UaVariableNode} of the appropriate
* subclass will be returned.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} identifying the VariableNode to get.
* @return a CompletableFuture that completes successfully with a {@link UaVariableNode}
* instance for the VariableNode identified by {@code nodeId} or completes exceptionally if an
* error occurs while creating the VariableNode.
*/
public CompletableFuture getVariableNodeAsync(NodeId nodeId) {
UaNode cachedNode = nodeCache.getIfPresent(nodeId);
if (cachedNode instanceof UaVariableNode) {
return completedFuture((UaVariableNode) cachedNode);
} else {
CompletableFuture typeDefinitionFuture = readTypeDefinition(nodeId);
return typeDefinitionFuture.thenCompose(typeDefinitionId -> getVariableNodeAsync(nodeId, typeDefinitionId));
}
}
/**
* Get a {@link UaVariableNode} instance for the VariableNode identified by {@code nodeId},
* assuming the type definition identified by {@code typeDefinitionId}.
*
* If this type definition is registered with the {@link VariableTypeManager} a
* {@link UaVariableNode} of the appropriate subclass will be returned.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} identifying the VariableNode to get.
* @param typeDefinitionId the {@link NodeId} identifying the type definition.
* @return a CompletableFuture that completes successfully with a {@link UaVariableNode}
* instance for the VariableNode identified by {@code nodeId} or completes exceptionally if an
* error occurs while creating the VariableNode.
*/
public CompletableFuture getVariableNodeAsync(NodeId nodeId, NodeId typeDefinitionId) {
UaNode cachedNode = nodeCache.getIfPresent(nodeId);
if (cachedNode instanceof UaVariableNode) {
return completedFuture((UaVariableNode) cachedNode);
} else {
CompletableFuture future = readAttributes(nodeId, AttributeId.VARIABLE_ATTRIBUTES);
return future.thenCompose(response -> {
List attributeValues = l(response.getResults());
try {
UaVariableNode node = newVariableNode(nodeId, typeDefinitionId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
}
/**
* Call the Browse service to get a {@link UaNode}'s references using the currently configured
* {@link BrowseOptions}.
*
* @param node the {@link UaNode} to browse.
* @return a List of {@link ReferenceDescription}s.
* @throws UaException if a service-level error occurs.
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public List browse(UaNode node) throws UaException {
return browse(node, getBrowseOptions());
}
/**
* Call the Browse service to get a {@link UaNode}'s references.
*
* @param node the {@link UaNode} to browse.
* @param browseOptions the {@link BrowseOptions} to browse with.
* @return a List of {@link ReferenceDescription}s.
* @throws UaException if a service-level error occurs.
*/
public List browse(UaNode node, BrowseOptions browseOptions) throws UaException {
try {
return browseAsync(node, browseOptions).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Call the Browse service to get a Node's references using the currently configured
* {@link BrowseOptions}.
*
* @param nodeId the {@link NodeId} of the Node to browse.
* @return a List of {@link ReferenceDescription}s.
* @throws UaException if a service-level error occurs.
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public List browse(NodeId nodeId) throws UaException {
return browse(nodeId, getBrowseOptions());
}
/**
* Call the Browse service to get a Node's references.
*
* @param nodeId the {@link NodeId} of the Node to browse.
* @param browseOptions the {@link BrowseOptions} to browse with.
* @return a List of {@link ReferenceDescription}s.
* @throws UaException if a service-level error occurs.
*/
public List browse(NodeId nodeId, BrowseOptions browseOptions) throws UaException {
try {
return browseAsync(nodeId, browseOptions).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Call the Browse service to get a {@link UaNode}'s references using the currently configured
* {@link BrowseOptions}.
*
* This call completes asynchronously.
*
* @param node the {@link UaNode} to browse.
* @return a CompletableFuture that completes successfully with the List of references or
* completes exceptionally if a service-level error occurs.
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public CompletableFuture> browseAsync(UaNode node) {
return browseAsync(node.getNodeId(), getBrowseOptions());
}
/**
* Call the Browse service to get a {@link UaNode}'s references.
*
* This call completes asynchronously.
*
* @param node the {@link UaNode} to browse.
* @param browseOptions the {@link BrowseOptions} to browse with.
* @return a CompletableFuture that completes successfully with the List of references or
* completes exceptionally if a service-level error occurs.
*/
public CompletableFuture> browseAsync(UaNode node, BrowseOptions browseOptions) {
return browseAsync(node.getNodeId(), browseOptions);
}
/**
* Call the Browse service to get a Node's references using the currently configured
* {@link BrowseOptions}.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} of the Node to browse.
* @return a CompletableFuture that completes successfully with the List of references or
* completes exceptionally if a service-level error occurs.
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public CompletableFuture> browseAsync(NodeId nodeId) {
return browseAsync(nodeId, getBrowseOptions());
}
/**
* Call the Browse service to get a Node's references.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} of the Node to browse.
* @param browseOptions the {@link BrowseOptions} to browse with.
* @return a CompletableFuture that completes successfully with the List of references or
* completes exceptionally if a service-level error occurs.
*/
public CompletableFuture> browseAsync(NodeId nodeId, BrowseOptions browseOptions) {
BrowseDescription browseDescription = new BrowseDescription(
nodeId,
browseOptions.getBrowseDirection(),
browseOptions.getReferenceTypeId(),
browseOptions.isIncludeSubtypes(),
browseOptions.getNodeClassMask(),
uint(BrowseResultMask.All.getValue())
);
return BrowseHelper.browse(client, browseDescription, browseOptions.getMaxReferencesPerNode());
}
/**
* Browse from {@code node} using the currently configured {@link BrowseOptions}.
*
* @param node the {@link UaNode} to start the browse from.
* @return a List of {@link UaNode}s referenced by {@code node} given the currently configured
* {@link BrowseOptions}.
* @throws UaException if an error occurs while browsing or creating Nodes.
* @see #browseNodes(UaNode, BrowseOptions)
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public List extends UaNode> browseNodes(UaNode node) throws UaException {
return browseNodes(node, getBrowseOptions());
}
/**
* Browse from {@code node} using {@code browseOptions}.
*
* @param node the {@link UaNode} to start the browse from.
* @param browseOptions the {@link BrowseOptions} to use.
* @return a List of {@link UaNode}s referenced by {@code node} given {@code browseOptions}.
* @throws UaException if an error occurs while browsing or creating Nodes.
*/
public List extends UaNode> browseNodes(UaNode node, BrowseOptions browseOptions) throws UaException {
try {
return browseNodesAsync(node, browseOptions).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Browse from {@code nodeId} using the currently configured {@link BrowseOptions}.
*
* @param nodeId the {@link NodeId} to start the browse from.
* @return a List of {@link UaNode}s referenced by {@code nodeId} given the currently configured
* {@link BrowseOptions}.
* @throws UaException if an error occurs while browsing or creating Nodes.
* @see #browseNodes(UaNode, BrowseOptions)
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public List extends UaNode> browseNodes(NodeId nodeId) throws UaException {
return browseNodes(nodeId, getBrowseOptions());
}
/**
* Browse from {@code nodeId} using {@code browseOptions}.
*
* @param nodeId the {@link NodeId} to start the browse from.
* @param browseOptions the {@link BrowseOptions} to use.
* @return a List of {@link UaNode}s referenced by {@code nodeId} given {@code browseOptions}.
* @throws UaException if an error occurs while browsing or creating Nodes.
*/
public List extends UaNode> browseNodes(NodeId nodeId, BrowseOptions browseOptions) throws UaException {
try {
return browseNodesAsync(nodeId, browseOptions).get();
} catch (ExecutionException | InterruptedException e) {
throw UaException.extract(e)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, e));
}
}
/**
* Browse from {@code node} using the currently configured {@link BrowseOptions}.
*
* This call completes asynchronously.
*
* @param node the {@link UaNode} to start the browse from.
* @return a CompletableFuture that completes successfully with a List of {@link UaNode}s
* referenced by {@code node} given the currently configured {@link BrowseOptions} or completes
* exceptionally if a service-level error occurs.
* @see #browseNodesAsync(UaNode, BrowseOptions)
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public CompletableFuture> browseNodesAsync(UaNode node) {
return browseNodesAsync(node.getNodeId());
}
/**
* Browse from {@code node} using {@code browseOptions}.
*
* This call completes asynchronously.
*
* @param node the {@link UaNode} to start the browse from.
* @param browseOptions the {@link BrowseOptions} to use.
* @return a CompletableFuture that completes successfully with a List of {@link UaNode}s
* referenced by {@code node} given the currently configured {@link BrowseOptions} or completes
* exceptionally if a service-level error occurs.
*/
public CompletableFuture> browseNodesAsync(UaNode node, BrowseOptions browseOptions) {
return browseNodesAsync(node.getNodeId(), browseOptions);
}
/**
* Browse from {@code nodeId} using the currently configured {@link BrowseOptions}.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} to start the browse from.
* @return a CompletableFuture that completes successfully with a List of {@link UaNode}s
* referenced by {@code node} given the currently configured {@link BrowseOptions} or completes
* exceptionally if a service-level error occurs.
* @see #browseNodesAsync(UaNode, BrowseOptions)
* @see #getBrowseOptions()
* @see #modifyBrowseOptions(Consumer)
* @see #setBrowseOptions(BrowseOptions)
*/
public CompletableFuture> browseNodesAsync(NodeId nodeId) {
return browseNodesAsync(nodeId, getBrowseOptions());
}
/**
* Browse from {@code nodeId} using {@code browseOptions}.
*
* This call completes asynchronously.
*
* @param nodeId the {@link NodeId} to start the browse from.
* @param browseOptions the {@link BrowseOptions} to use.
* @return a CompletableFuture that completes successfully with a List of {@link UaNode}s
* referenced by {@code node} given the currently configured {@link BrowseOptions} or completes
* exceptionally if a service-level error occurs.
*/
public CompletableFuture> browseNodesAsync(NodeId nodeId, BrowseOptions browseOptions) {
BrowseDescription browseDescription = new BrowseDescription(
nodeId,
browseOptions.getBrowseDirection(),
browseOptions.getReferenceTypeId(),
browseOptions.isIncludeSubtypes(),
browseOptions.getNodeClassMask(),
uint(BrowseResultMask.All.getValue())
);
CompletableFuture> browse = BrowseHelper.browse(
client,
browseDescription,
browseOptions.getMaxReferencesPerNode()
);
return browse.thenCompose(references -> {
List> cfs = references.stream()
.map(reference -> {
NodeClass nodeClass = reference.getNodeClass();
ExpandedNodeId xNodeId = reference.getNodeId();
ExpandedNodeId xTypeDefinitionId = reference.getTypeDefinition();
switch (nodeClass) {
case Object:
case Variable: {
CompletableFuture> ff =
toNodeIdAsync(xNodeId).thenCombine(
toNodeIdAsync(xTypeDefinitionId),
(targetNodeId, typeDefinitionId) -> {
if (nodeClass == NodeClass.Object) {
return getObjectNodeAsync(targetNodeId, typeDefinitionId);
} else {
return getVariableNodeAsync(targetNodeId, typeDefinitionId);
}
}
);
return unwrap(ff).exceptionally(ex -> {
logger.warn("Failed to create Node from Reference to {}", reference.getNodeId(), ex);
return null;
});
}
default: {
// TODO specialized getNode for other NodeClasses?
return toNodeIdAsync(xNodeId).thenCompose(this::getNodeAsync).exceptionally(ex -> {
logger.warn("Failed to create Node from Reference to {}", reference.getNodeId(), ex);
return null;
});
}
}
})
.collect(Collectors.toList());
return sequence(cfs);
});
}
private static CompletableFuture> sequence(
List> cfs
) {
if (cfs.isEmpty()) {
return completedFuture(Collections.emptyList());
}
@SuppressWarnings("rawtypes")
CompletableFuture[] fa = cfs.toArray(new CompletableFuture[0]);
return CompletableFuture.allOf(fa).thenApply(v -> {
List results = new ArrayList<>(cfs.size());
for (CompletableFuture extends UaNode> cf : cfs) {
UaNode node = cf.join();
if (node != null) {
results.add(node);
}
}
return results;
});
}
private static CompletableFuture extends UaNode> unwrap(
CompletableFuture> future
) {
return future.thenCompose(node -> node);
}
/**
* Convert {@code xni} to a {@link NodeId} in the server, reading the namespace table from the
* server if necessary.
*
* Returns {@link NodeId#NULL_VALUE} if the conversion could not be completed for any reason.
*
* @param xni the {@link ExpandedNodeId} to convert to a {@link NodeId}.
* @return a {@link NodeId} local to the server, or {@link NodeId#NULL_VALUE} if conversion
* could not be completed for any reason.
*/
public NodeId toNodeId(ExpandedNodeId xni) {
try {
return toNodeIdAsync(xni).get();
} catch (ExecutionException | InterruptedException e) {
return NodeId.NULL_VALUE;
}
}
/**
* Convert {@code xni} to a {@link NodeId} in the server, reading the namespace table from the
* server if necessary.
*
* Returns {@link NodeId#NULL_VALUE} if the conversion could not be completed for any reason.
*
* @param xni the {@link ExpandedNodeId} to convert to a {@link NodeId}.
* @return a {@link NodeId} local to the server, or {@link NodeId#NULL_VALUE} if conversion
* could not be completed for any reason.
*/
public CompletableFuture toNodeIdAsync(ExpandedNodeId xni) {
// TODO should this fail with Bad_NodeIdUnknown instead of returning NodeId.NULL_VALUE?
if (xni.isLocal()) {
Optional local = xni.toNodeId(client.getNamespaceTable());
return local.map(CompletableFuture::completedFuture).orElse(
client.readNamespaceTableAsync()
.thenCompose(
namespaceTable ->
completedFuture(xni.toNodeId(namespaceTable).orElse(NodeId.NULL_VALUE))
)
.exceptionally(e -> NodeId.NULL_VALUE)
);
} else {
return completedFuture(NodeId.NULL_VALUE);
}
}
/**
* Get the default {@link BrowseOptions} used during browse calls that don't have an explicit
* {@link BrowseOptions} parameter.
*
* @return the default {@link BrowseOptions}.
*/
public synchronized BrowseOptions getBrowseOptions() {
return browseOptions;
}
/**
* Modify the default {@link BrowseOptions} used during browse calls that don't have an
* explicit {@link BrowseOptions} parameter.
*
* @param builderConsumer a {@link Consumer} that receives a {@link BrowseOptions.Builder}.
*/
public synchronized void modifyBrowseOptions(Consumer builderConsumer) {
BrowseOptions.Builder builder = new BrowseOptions.Builder(browseOptions);
builderConsumer.accept(builder);
setBrowseOptions(builder.build());
}
/**
* Set a new default {@link BrowseOptions} used during browse calls that don't have an explicit
* {@link BrowseOptions} parameter.
*
* @param browseOptions the new default {@link BrowseOptions}.
*/
public synchronized void setBrowseOptions(BrowseOptions browseOptions) {
this.browseOptions = browseOptions;
}
/**
* Get the current {@link NodeCache}.
*
* @return the current {@link NodeCache}.
*/
public synchronized NodeCache getNodeCache() {
return nodeCache;
}
/**
* Set a new {@link NodeCache}.
*
* @param nodeCache a new {@link NodeCache}.
*/
public synchronized void setNodeCache(NodeCache nodeCache) {
this.nodeCache = nodeCache;
}
private CompletableFuture readTypeDefinition(NodeId nodeId) {
CompletableFuture browseFuture = client.browse(new BrowseDescription(
nodeId,
BrowseDirection.Forward,
Identifiers.HasTypeDefinition,
false,
uint(NodeClass.ObjectType.getValue() | NodeClass.VariableType.getValue()),
uint(BrowseResultMask.All.getValue())
));
return browseFuture.thenCompose(result -> {
if (result.getStatusCode().isGood()) {
Optional typeDefinitionId = l(result.getReferences())
.stream()
.filter(r -> Objects.equals(Identifiers.HasTypeDefinition, r.getReferenceTypeId()))
.map(ReferenceDescription::getNodeId)
.findFirst();
return typeDefinitionId.map(this::toNodeIdAsync)
.orElse(completedFuture(NodeId.NULL_VALUE));
} else {
return completedFuture(NodeId.NULL_VALUE);
}
});
}
/**
* Create a {@link UaNode} instance without prior knowledge of the {@link NodeClass} or type
* definition, if applicable.
*
* @param nodeId the {@link NodeId} of the Node to create.
* @return a {@link UaNode} instance for the Node identified by {@code nodeId}.
*/
private CompletableFuture extends UaNode> createNode(NodeId nodeId) {
CompletableFuture future = readAttributes(nodeId, AttributeId.BASE_ATTRIBUTES);
return future.thenCompose(response -> {
List results = l(response.getResults());
return createNodeFromBaseAttributes(nodeId, results);
});
}
private CompletableFuture extends UaNode> createNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
StatusCode nodeIdStatusCode = baseAttributeValues.get(0).getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
return failedUaFuture(nodeIdStatusCode);
}
Integer nodeClassValue = (Integer) baseAttributeValues.get(1).getValue().getValue();
if (nodeClassValue == null) {
return failedUaFuture(StatusCodes.Bad_NodeClassInvalid);
}
NodeClass nodeClass = NodeClass.from(nodeClassValue);
if (nodeClass == null) {
return failedUaFuture(StatusCodes.Bad_NodeClassInvalid);
}
switch (nodeClass) {
case DataType:
return createDataTypeNodeFromBaseAttributes(nodeId, baseAttributeValues);
case Method:
return createMethodNodeFromBaseAttributes(nodeId, baseAttributeValues);
case Object:
return createObjectNodeFromBaseAttributes(nodeId, baseAttributeValues);
case ObjectType:
return createObjectTypeNodeFromBaseAttributes(nodeId, baseAttributeValues);
case ReferenceType:
return createReferenceTypeNodeFromBaseAttributes(nodeId, baseAttributeValues);
case Variable:
return createVariableNodeFromBaseAttributes(nodeId, baseAttributeValues);
case VariableType:
return createVariableTypeNodeFromBaseAttributes(nodeId, baseAttributeValues);
case View:
return createViewNodeFromBaseAttributes(nodeId, baseAttributeValues);
default:
throw new IllegalArgumentException("NodeClass: " + nodeClass);
}
}
private CompletableFuture createDataTypeNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.DATA_TYPE_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
return attributesFuture.thenCompose(response -> {
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaDataTypeNode node = newDataTypeNode(nodeId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture createMethodNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.METHOD_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
return attributesFuture.thenCompose(response -> {
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaMethodNode node = newMethodNode(nodeId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture createObjectNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.OBJECT_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
CompletableFuture typeDefinitionFuture = readTypeDefinition(nodeId);
return CompletableFuture.allOf(attributesFuture, typeDefinitionFuture)
.thenCompose(ignored -> {
ReadResponse response = attributesFuture.join();
NodeId typeDefinitionId = typeDefinitionFuture.join();
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaObjectNode node = newObjectNode(nodeId, typeDefinitionId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture createObjectTypeNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.OBJECT_TYPE_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
return attributesFuture.thenCompose(response -> {
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaObjectTypeNode node = newObjectTypeNode(nodeId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture createReferenceTypeNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.REFERENCE_TYPE_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
return attributesFuture.thenCompose(response -> {
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaReferenceTypeNode node = newReferenceTypeNode(nodeId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture createVariableNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.VARIABLE_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
CompletableFuture typeDefinitionFuture = readTypeDefinition(nodeId);
return CompletableFuture.allOf(attributesFuture, typeDefinitionFuture)
.thenCompose(ignored -> {
ReadResponse response = attributesFuture.join();
NodeId typeDefinitionId = typeDefinitionFuture.join();
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaVariableNode node = newVariableNode(nodeId, typeDefinitionId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture createVariableTypeNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.VARIABLE_TYPE_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
return attributesFuture.thenCompose(response -> {
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaVariableTypeNode node = newVariableTypeNode(nodeId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture createViewNodeFromBaseAttributes(
NodeId nodeId,
List baseAttributeValues
) {
Set remainingAttributes = Sets.difference(
AttributeId.VIEW_ATTRIBUTES,
AttributeId.BASE_ATTRIBUTES
);
CompletableFuture attributesFuture = readAttributes(nodeId, remainingAttributes);
return attributesFuture.thenCompose(response -> {
List attributeValues = new ArrayList<>(baseAttributeValues);
Collections.addAll(attributeValues, response.getResults());
try {
UaViewNode node = newViewNode(nodeId, attributeValues);
nodeCache.put(node.getNodeId(), node);
return completedFuture(node);
} catch (UaException e) {
return failedFuture(e);
}
});
}
private CompletableFuture readAttributes(NodeId nodeId, Set attributeIds) {
List readValueIds = attributeIds.stream()
.map(id ->
new ReadValueId(
nodeId,
id.uid(),
null,
QualifiedName.NULL_VALUE
)
)
.collect(Collectors.toList());
return client.read(0.0, TimestampsToReturn.Neither, readValueIds);
}
private UaDataTypeNode newDataTypeNode(NodeId nodeId, List attributeValues) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.DataType,
"expected NodeClass.DataType, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
Boolean isAbstract = (Boolean) attributeValues.get(7).getValue().getValue();
return new UaDataTypeNode(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
isAbstract
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
private UaMethodNode newMethodNode(NodeId nodeId, List attributeValues) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.Method,
"expected NodeClass.Method, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
Boolean executable = (Boolean) attributeValues.get(7).getValue().getValue();
Boolean userExecutable = (Boolean) attributeValues.get(8).getValue().getValue();
return new UaMethodNode(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
executable,
userExecutable
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
private UaObjectNode newObjectNode(
NodeId nodeId,
NodeId typeDefinitionId,
List attributeValues
) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.Object,
"expected NodeClass.Object, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
UByte eventNotifier = (UByte) attributeValues.get(7).getValue().getValue();
ObjectNodeConstructor constructor = client.getObjectTypeManager()
.getNodeConstructor(typeDefinitionId)
.orElse(UaObjectNode::new);
return constructor.apply(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
eventNotifier
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
private UaObjectTypeNode newObjectTypeNode(NodeId nodeId, List attributeValues) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.ObjectType,
"expected NodeClass.ObjectType, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
Boolean isAbstract = (Boolean) attributeValues.get(7).getValue().getValue();
return new UaObjectTypeNode(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
isAbstract
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
private UaReferenceTypeNode newReferenceTypeNode(
NodeId nodeId,
List attributeValues
) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.ReferenceType,
"expected NodeClass.ReferenceType, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
Boolean isAbstract = (Boolean) attributeValues.get(7).getValue().getValue();
Boolean symmetric = (Boolean) attributeValues.get(8).getValue().getValue();
LocalizedText inverseName = getAttributeOrNull(attributeValues.get(9), LocalizedText.class);
return new UaReferenceTypeNode(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
isAbstract,
symmetric,
inverseName
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
private UaVariableNode newVariableNode(
NodeId nodeId,
NodeId typeDefinitionId,
List attributeValues
) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.Variable,
"expected NodeClass.Variable, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
DataValue value = attributeValues.get(7);
NodeId dataType = (NodeId) attributeValues.get(8).getValue().getValue();
Integer valueRank = (Integer) attributeValues.get(9).getValue().getValue();
UInteger[] arrayDimensions = getAttributeOrNull(attributeValues.get(10), UInteger[].class);
UByte accessLevel = (UByte) attributeValues.get(11).getValue().getValue();
UByte userAccessLevel = (UByte) attributeValues.get(12).getValue().getValue();
Double minimumSamplingInterval = getAttributeOrNull(attributeValues.get(13), Double.class);
Boolean historizing = (Boolean) attributeValues.get(14).getValue().getValue();
VariableTypeManager.VariableNodeConstructor constructor = client.getVariableTypeManager()
.getNodeConstructor(typeDefinitionId)
.orElse(UaVariableNode::new);
return constructor.apply(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
value,
dataType,
valueRank,
arrayDimensions,
accessLevel,
userAccessLevel,
minimumSamplingInterval,
historizing
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
private UaVariableTypeNode newVariableTypeNode(NodeId nodeId, List attributeValues) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.VariableType,
"expected NodeClass.VariableType, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
DataValue value = attributeValues.get(7);
NodeId dataType = (NodeId) attributeValues.get(8).getValue().getValue();
Integer valueRank = (Integer) attributeValues.get(9).getValue().getValue();
UInteger[] arrayDimensions = getAttributeOrNull(attributeValues.get(10), UInteger[].class);
Boolean isAbstract = (Boolean) attributeValues.get(11).getValue().getValue();
return new UaVariableTypeNode(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
value,
dataType,
valueRank,
arrayDimensions,
isAbstract
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
private UaViewNode newViewNode(NodeId nodeId, List attributeValues) throws UaException {
DataValue nodeIdDataValue = attributeValues.get(0);
StatusCode nodeIdStatusCode = nodeIdDataValue.getStatusCode();
if (nodeIdStatusCode != null && nodeIdStatusCode.isBad()) {
throw new UaException(nodeIdStatusCode);
}
try {
NodeClass nodeClass = NodeClass.from((Integer) attributeValues.get(1).getValue().getValue());
Preconditions.checkArgument(
nodeClass == NodeClass.View,
"expected NodeClass.View, got NodeClass." + nodeClass
);
QualifiedName browseName = (QualifiedName) attributeValues.get(2).getValue().getValue();
LocalizedText displayName = (LocalizedText) attributeValues.get(3).getValue().getValue();
LocalizedText description = getAttributeOrNull(attributeValues.get(4), LocalizedText.class);
UInteger writeMask = getAttributeOrNull(attributeValues.get(5), UInteger.class);
UInteger userWriteMask = getAttributeOrNull(attributeValues.get(6), UInteger.class);
Boolean containsNoLoops = (Boolean) attributeValues.get(7).getValue().getValue();
UByte eventNotifier = (UByte) attributeValues.get(8).getValue().getValue();
return new UaViewNode(
client,
nodeId,
nodeClass,
browseName,
displayName,
description,
writeMask,
userWriteMask,
containsNoLoops,
eventNotifier
);
} catch (Throwable t) {
throw UaException.extract(t)
.orElse(new UaException(StatusCodes.Bad_UnexpectedError, t));
}
}
@Nullable
private static T getAttributeOrNull(DataValue value, Class attributeClazz) {
StatusCode statusCode = value.getStatusCode();
if (statusCode != null && statusCode.isBad()) {
return null;
} else {
Object attributeValue = value.getValue().getValue();
try {
return attributeClazz.cast(attributeValue);
} catch (ClassCastException e) {
return null;
}
}
}
public static class BrowseOptions {
private final BrowseDirection browseDirection;
private final NodeId referenceTypeId;
private final boolean includeSubtypes;
private final UInteger nodeClassMask;
private final UInteger maxReferencesPerNode;
public BrowseOptions() {
this(BrowseDirection.Forward, Identifiers.HierarchicalReferences, true, uint(0xFF), uint(0));
}
public BrowseOptions(
BrowseDirection browseDirection,
NodeId referenceTypeId,
boolean includeSubtypes,
UInteger nodeClassMask,
UInteger maxReferencesPerNode
) {
this.browseDirection = browseDirection;
this.referenceTypeId = referenceTypeId;
this.includeSubtypes = includeSubtypes;
this.nodeClassMask = nodeClassMask;
this.maxReferencesPerNode = maxReferencesPerNode;
}
public BrowseDirection getBrowseDirection() {
return browseDirection;
}
public NodeId getReferenceTypeId() {
return referenceTypeId;
}
public boolean isIncludeSubtypes() {
return includeSubtypes;
}
public UInteger getNodeClassMask() {
return nodeClassMask;
}
public UInteger getMaxReferencesPerNode() {
return maxReferencesPerNode;
}
public BrowseOptions copy(Consumer builderConsumer) {
Builder builder = new Builder(this);
builderConsumer.accept(builder);
return builder.build();
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private BrowseDirection browseDirection = BrowseDirection.Forward;
private NodeId referenceTypeId = Identifiers.HierarchicalReferences;
private boolean includeSubtypes = true;
private UInteger nodeClassMask = uint(0xFF);
private UInteger maxReferencesPerNode = uint(0);
private Builder() {}
private Builder(BrowseOptions browseOptions) {
this.browseDirection = browseOptions.getBrowseDirection();
this.referenceTypeId = browseOptions.getReferenceTypeId();
this.includeSubtypes = browseOptions.isIncludeSubtypes();
this.nodeClassMask = browseOptions.getNodeClassMask();
this.maxReferencesPerNode = browseOptions.getMaxReferencesPerNode();
}
public Builder setBrowseDirection(BrowseDirection browseDirection) {
this.browseDirection = browseDirection;
return this;
}
public Builder setReferenceType(BuiltinReferenceType referenceType) {
return setReferenceType(referenceType.getNodeId());
}
public Builder setReferenceType(NodeId referenceTypeId) {
this.referenceTypeId = referenceTypeId;
return this;
}
public Builder setIncludeSubtypes(boolean includeSubtypes) {
this.includeSubtypes = includeSubtypes;
return this;
}
public Builder setNodeClassMask(UInteger nodeClassMask) {
this.nodeClassMask = nodeClassMask;
return this;
}
public Builder setNodeClassMask(Set nodeClasses) {
int mask = 0;
for (NodeClass nodeClass : nodeClasses) {
mask |= nodeClass.getValue();
}
return setNodeClassMask(uint(mask));
}
public Builder setMaxReferencesPerNode(UInteger maxReferencesPerNode) {
this.maxReferencesPerNode = maxReferencesPerNode;
return this;
}
public BrowseOptions build() {
return new BrowseOptions(
browseDirection,
referenceTypeId,
includeSubtypes,
nodeClassMask,
maxReferencesPerNode
);
}
}
}
}