Skip to content

Commit

Permalink
Allow AbstractMethodInvocationHandler implementations to validate inp…
Browse files Browse the repository at this point in the history
…uts (#826)

fixes #823
fixes #824
  • Loading branch information
kevinherron committed Apr 19, 2021
1 parent d51125f commit 86412a2
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 31 deletions.
@@ -0,0 +1,67 @@
/*
* 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.server.api.methods;

import java.util.Arrays;
import java.util.concurrent.ExecutionException;

import org.eclipse.milo.opcua.sdk.client.AddressSpace;
import org.eclipse.milo.opcua.sdk.client.methods.UaMethodException;
import org.eclipse.milo.opcua.sdk.client.nodes.UaObjectNode;
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.UaException;
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.structured.CallMethodRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.CallMethodResult;
import org.junit.jupiter.api.Test;

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

public class AbstractMethodInvocationHandlerTest extends AbstractClientServerTest {

@Test
public void inputArgumentResultsIsEmptyOnSuccess() throws ExecutionException, InterruptedException {
CallMethodResult result = client.call(new CallMethodRequest(
Identifiers.ObjectsFolder,
NodeId.parse("ns=2;s=onlyAcceptsPositiveInputs()"),
new Variant[]{new Variant(1)}
)).get();

assertEquals(StatusCode.GOOD, result.getStatusCode());
assertEquals(0, result.getInputArgumentResults().length);
}

@Test
public void implementationCanValidateArguments() throws UaException {
AddressSpace addressSpace = client.getAddressSpace();

UaObjectNode objectsNode = addressSpace.getObjectNode(Identifiers.ObjectsFolder);

try {
objectsNode.callMethod(
new QualifiedName(2, "onlyAcceptsPositiveInputs()"),
new Variant[]{new Variant(-1)}
);
} catch (UaMethodException e) {
System.out.println("result: " + e.getStatusCode());
System.out.println("inputArgumentResults: " + Arrays.toString(e.getInputArgumentResults()));

assertEquals(StatusCodes.Bad_InvalidArgument, e.getStatusCode().getValue());
assertEquals(StatusCodes.Bad_OutOfRange, e.getInputArgumentResults()[0].getValue());
}
}

}
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle;
import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem;
import org.eclipse.milo.opcua.sdk.server.api.methods.AbstractMethodInvocationHandler;
import org.eclipse.milo.opcua.sdk.server.api.methods.InvalidArgumentException;
import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.BaseEventTypeNode;
import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.ServerTypeNode;
import org.eclipse.milo.opcua.sdk.server.model.nodes.variables.AnalogItemTypeNode;
Expand All @@ -41,6 +42,7 @@
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.structured.Argument;
import org.eclipse.milo.opcua.stack.core.types.structured.Range;
Expand Down Expand Up @@ -200,7 +202,7 @@ public Argument[] getOutputArguments() {
}

@Override
protected Variant[] invoke(InvocationContext invocationContext, Variant[] inputValues) throws UaException {
protected Variant[] invoke(InvocationContext invocationContext, Variant[] inputValues) {
return new Variant[0];
}
});
Expand Down Expand Up @@ -241,15 +243,23 @@ public Argument[] getOutputArguments() {
}

@Override
protected Variant[] invoke(
InvocationContext invocationContext,
Variant[] inputValues
) throws UaException {
protected void validateInputArgumentValues(
Variant[] inputArgumentValues
) throws InvalidArgumentException {

int i = (int) inputArgumentValues[0].getValue();

int i = (int) inputValues[0].getValue();
if (i < 0) {
throw new UaException(StatusCodes.Bad_InvalidArgument, "invalid argument: i");
StatusCode[] inputArgumentResults = {
new StatusCode(StatusCodes.Bad_OutOfRange)
};

throw new InvalidArgumentException(inputArgumentResults);
}
}

@Override
protected Variant[] invoke(InvocationContext invocationContext, Variant[] inputValues) {
return new Variant[0];
}
});
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 the Eclipse Milo Authors
* 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
Expand Down Expand Up @@ -58,24 +58,22 @@ public UaMethodNode getNode() {

@Override
public final CallMethodResult invoke(AccessContext accessContext, CallMethodRequest request) {
StatusCode[] inputArgumentResults = new StatusCode[0];

try {
checkExecutableAttributes(accessContext);

Variant[] inputValues = request.getInputArguments();
if (inputValues == null) inputValues = new Variant[0];
Variant[] inputArgumentValues = request.getInputArguments();
if (inputArgumentValues == null) inputArgumentValues = new Variant[0];

if (inputValues.length != getInputArguments().length) {
if (inputArgumentValues.length != getInputArguments().length) {
throw new UaException(StatusCodes.Bad_ArgumentsMissing);
}

inputArgumentResults = new StatusCode[inputValues.length];
StatusCode[] inputDataTypeCheckResults = new StatusCode[inputArgumentValues.length];

for (int i = 0; i < inputValues.length; i++) {
for (int i = 0; i < inputArgumentValues.length; i++) {
Argument argument = getInputArguments()[i];

Variant variant = inputValues[i];
Variant variant = inputArgumentValues[i];
Object value = variant.getValue();

// TODO this needs to be able to match when argument DataType is an alias type
Expand Down Expand Up @@ -127,16 +125,18 @@ public final CallMethodResult invoke(AccessContext accessContext, CallMethodRequ
}

if (dataTypeMatch) {
inputArgumentResults[i] = StatusCode.GOOD;
inputDataTypeCheckResults[i] = StatusCode.GOOD;
} else {
inputArgumentResults[i] = new StatusCode(StatusCodes.Bad_TypeMismatch);
inputDataTypeCheckResults[i] = new StatusCode(StatusCodes.Bad_TypeMismatch);
}
}

if (Arrays.stream(inputArgumentResults).anyMatch(StatusCode::isBad)) {
throw new UaException(StatusCodes.Bad_InvalidArgument);
if (Arrays.stream(inputDataTypeCheckResults).anyMatch(StatusCode::isBad)) {
throw new InvalidArgumentException(inputDataTypeCheckResults);
}

validateInputArgumentValues(inputArgumentValues);

InvocationContext invocationContext = new InvocationContext() {
@Override
public OpcUaServer getServer() {
Expand All @@ -159,21 +159,18 @@ public Optional<Session> getSession() {
}
};

Variant[] outputValues = invoke(invocationContext, inputValues);
Variant[] outputValues = invoke(invocationContext, inputArgumentValues);

return new CallMethodResult(
StatusCode.GOOD,
inputArgumentResults,
new DiagnosticInfo[0],
outputValues
);
} catch (UaException e) {
return new CallMethodResult(StatusCode.GOOD, new StatusCode[0], new DiagnosticInfo[0], outputValues);
} catch (InvalidArgumentException e) {
return new CallMethodResult(
e.getStatusCode(),
inputArgumentResults,
new DiagnosticInfo[0],
e.getInputArgumentResults(),
e.getInputArgumentDiagnosticInfos(),
new Variant[0]
);
} catch (UaException e) {
return new CallMethodResult(e.getStatusCode(), new StatusCode[0], new DiagnosticInfo[0], new Variant[0]);
}
}

Expand Down Expand Up @@ -238,7 +235,22 @@ protected void checkExecutableAttributes(AccessContext accessContext) throws UaE
* @return this output values matching this Method's output arguments, if any.
* @throws UaException if invocation has failed for some reason.
*/
protected abstract Variant[] invoke(InvocationContext invocationContext, Variant[] inputValues) throws UaException;
protected abstract Variant[] invoke(
InvocationContext invocationContext,
Variant[] inputValues
) throws UaException;

/**
* Validate the input values against the expected input arguments.
* <p>
* The DataType of each input value has already been verified; implementations need only verify
* the value is "valid", if applicable, and throw InvalidArgumentException with a StatusCode of
* Bad_OutOfRange for any invalid input values.
*
* @param inputArgumentValues the input values provided by the client for the current method call.
* @throws InvalidArgumentException if one or more input argument values are invalid.
*/
protected void validateInputArgumentValues(Variant[] inputArgumentValues) throws InvalidArgumentException {}

/**
* Extends {@link AccessContext} to provide additional context to implementations of
Expand Down
@@ -0,0 +1,56 @@
/*
* 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.server.api.methods;

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.DiagnosticInfo;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;

public class InvalidArgumentException extends UaException {

private final StatusCode[] inputArgumentResults;
private final DiagnosticInfo[] inputArgumentDiagnosticInfos;

public InvalidArgumentException(StatusCode[] inputArgumentResults) {
this(inputArgumentResults, new DiagnosticInfo[0]);
}

public InvalidArgumentException(
StatusCode[] inputArgumentResults,
DiagnosticInfo[] inputArgumentDiagnosticInfos
) {

super(StatusCodes.Bad_InvalidArgument, "one or more of the provided arguments is invalid");

this.inputArgumentResults = inputArgumentResults;
this.inputArgumentDiagnosticInfos = inputArgumentDiagnosticInfos;
}

/**
* Get the corresponding {@link StatusCode}s for each input argument value.
*
* @return the corresponding {@link StatusCode}s for each input argument value.
*/
public StatusCode[] getInputArgumentResults() {
return inputArgumentResults;
}

/**
* Get the corresponding {@link DiagnosticInfo}s for each input argument value, if supported.
*
* @return the corresponding {@link DiagnosticInfo}s for each input argument value, if supported.
*/
public DiagnosticInfo[] getInputArgumentDiagnosticInfos() {
return inputArgumentDiagnosticInfos;
}

}

0 comments on commit 86412a2

Please sign in to comment.