Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow writing null when "AllowNulls" Property is true #1255

Merged
merged 2 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.eclipse.milo.opcua.sdk.server.api.util;

import org.eclipse.milo.opcua.sdk.core.AccessLevel;
import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;
import org.eclipse.milo.opcua.sdk.test.AbstractClientServerTest;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
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.Variant;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class AttributeWriterTest extends AbstractClientServerTest {

@Test
void writeNullAllowed() throws Exception {
StatusCode statusCode = client.writeValue(
new NodeId(2, "AllowNulls"),
DataValue.valueOnly(Variant.NULL_VALUE)
).get();

assertEquals(StatusCode.GOOD, statusCode);
}

@Test
void writeNullDisallowed() throws Exception {
StatusCode statusCode = client.writeValue(
new NodeId(2, "DisallowNulls"),
DataValue.valueOnly(Variant.NULL_VALUE)
).get();

assertEquals(new StatusCode(StatusCodes.Bad_TypeMismatch), statusCode);
}

@Test
void writeNullNotConfigured() throws Exception {
// Default behavior when AllowNulls property is not configured is to reject null values.
StatusCode statusCode = client.writeValue(
new NodeId(2, "AllowNullsNotConfigured"),
DataValue.valueOnly(Variant.NULL_VALUE)
).get();

assertEquals(new StatusCode(StatusCodes.Bad_TypeMismatch), statusCode);
}

@Test
void writeByteStringToUByteArray() throws Exception {
StatusCode statusCode = client.writeValue(
new NodeId(2, "UByteArray"),
DataValue.valueOnly(new Variant(ByteString.of(new byte[]{1, 2, 3})))
).get();

assertEquals(StatusCode.GOOD, statusCode);
}

@BeforeAll
void configure() {
testNamespace.configureNode((context, nodeManager) -> {
UaVariableNode allowNulls = UaVariableNode.build(
context,
b -> {
b.setNodeId(new NodeId(2, "AllowNulls"));
b.setBrowseName(new QualifiedName(2, "AllowNulls"));
b.setDisplayName(LocalizedText.english("AllowNulls"));
b.setDataType(Identifiers.String);
b.setAccessLevel(AccessLevel.READ_WRITE);
b.setUserAccessLevel(AccessLevel.READ_WRITE);
return b.buildAndAdd();
}
);

allowNulls.setAllowNulls(true);

UaVariableNode disallowNulls = UaVariableNode.build(
context,
b -> {
b.setNodeId(new NodeId(2, "DisallowNulls"));
b.setBrowseName(new QualifiedName(2, "DisallowNulls"));
b.setDisplayName(LocalizedText.english("DisallowNulls"));
b.setDataType(Identifiers.String);
b.setAccessLevel(AccessLevel.READ_WRITE);
b.setUserAccessLevel(AccessLevel.READ_WRITE);
return b.buildAndAdd();
}
);

disallowNulls.setAllowNulls(false);

UaVariableNode.build(
context,
b -> {
b.setNodeId(new NodeId(2, "AllowNullsNotConfigured"));
b.setBrowseName(new QualifiedName(2, "AllowNullsNotConfigured"));
b.setDisplayName(LocalizedText.english("AllowNullsNotConfigured"));
b.setDataType(Identifiers.String);
b.setAccessLevel(AccessLevel.READ_WRITE);
b.setUserAccessLevel(AccessLevel.READ_WRITE);
return b.buildAndAdd();
}
);

UaVariableNode.build(
context,
b -> {
b.setNodeId(new NodeId(2, "UByteArray"));
b.setBrowseName(new QualifiedName(2, "UByteArray"));
b.setDisplayName(LocalizedText.english("UByteArray"));
b.setDataType(Identifiers.Byte);
b.setArrayDimensions(new UInteger[]{UInteger.valueOf(0)});
b.setAccessLevel(AccessLevel.READ_WRITE);
b.setUserAccessLevel(AccessLevel.READ_WRITE);
return b.buildAndAdd();
}
);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext;
import org.eclipse.milo.opcua.sdk.server.nodes.UaNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaServerNode;
import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.StatusCodes;
Expand Down Expand Up @@ -119,14 +120,20 @@ public static void writeAttribute(AttributeContext context,
);

if (attributeId == AttributeId.Value) {
boolean allowNulls = false;
if (node instanceof UaVariableNode) {
Boolean b = ((UaVariableNode) node).getAllowNulls();
allowNulls = b != null ? b : false;
}

NodeId dataType = extract(
node.getAttribute(
internalContext,
AttributeId.DataType)
);

if (dataType != null) {
value = validateDataType(context.getServer(), dataType, value);
value = validateDataType(context.getServer(), dataType, value, allowNulls);
}

Integer valueRank = extract(
Expand Down Expand Up @@ -206,13 +213,20 @@ private static WriteMask writeMaskForAttribute(AttributeId attributeId) {
private static DataValue validateDataType(
OpcUaServer server,
NodeId dataType,
DataValue value) throws UaException {
DataValue value,
boolean allowNulls
) throws UaException {

Variant variant = value.getValue();
if (variant == null) return value;

Object o = variant.getValue();
if (o == null) throw new UaException(StatusCodes.Bad_TypeMismatch);
if (o == null) {
if (allowNulls) {
return value;
} else {
throw new UaException(StatusCodes.Bad_TypeMismatch);
}
}

Class<?> valueClass = o.getClass().isArray() ?
ArrayUtil.getType(o) : o.getClass();
Expand Down

This file was deleted.