From 444d856fb878d3fdc002d75ba56048248d4c6688 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Thu, 14 May 2026 14:34:44 +0300 Subject: [PATCH] lwm2m - refactoring review - 02 and tests --- .../sql/RpcLwm2mIntegrationExecuteTest.java | 37 +++++++++---- .../DefaultLwM2mDownlinkMsgHandler.java | 52 ++++++++----------- .../rpc/DefaultLwM2MRpcRequestHandler.java | 12 ++--- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java index 7999081bd6..92e25a793e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java @@ -119,7 +119,8 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT } /** - * execute_resource_with_parameters (execute Factory Reset after connect with link on device) + * execute_resource_with_parameters (execute Factory Reset with two arguments: + * digit 2 without a value and digit 0 with the link value on device) * Execute {"id":"3/0/5","value":"2,0='https://thingsboard.io/docs/reference/lwm2m-api/'"} */ @Test @@ -132,7 +133,8 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT } /** - * execute_resource_with_parameters (execute Factory Reset after connect with link on device) + * execute_resource_with_parameters (execute Factory Reset with multiple arguments without values) + * According to the OMA LwM2M execute arguments format, this represents ten arguments (digits 0-9), all without values. * Execute {"id":"3/0/5","value":"0,1,2,3,4,5,6,7,8,9"} */ @Test @@ -148,10 +150,9 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT /** * execute_resource_with_parameters (execute Factory Reset after 60 seconds on device) * Execute {"id":"3/0/5","value":"'60'"} - */ @Test - public void testExecuteResourceWithParametersSingleDigitValueInvalidById_BAD_REQUEST_Error_UintegerBetween_0_And_9_Expected() throws Exception { + public void testExecuteResourceWithParametersSingleDigitValueInvalidById_Result_BAD_REQUEST_Error_IntegerBetween_0_And_9_Expected() throws Exception { String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_5; Object expectedValue = "'60'"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); @@ -159,7 +160,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); String expected = "Unable to parse Arguments [" + expectedValue + "] : Invalid digit ['] (an integer between 0 and 9 is expected)"; String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); + assertTrue(actual.contains(expected)); } /** @@ -175,7 +176,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); String expected = "Unable to parse Arguments [" + expectedValue + "] : [,] separator expected at index 21 after [0,1,2,3,4,5,6,7,8,9,6]"; String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); + assertTrue(actual.contains(expected)); } /** @@ -234,7 +235,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT * {"result":"BAD_REQUEST","error":"Specified object id 0 absent in the list supported objects of the client or is security object!"} */ @Test - public void testExecuteSecurityObjectById_Result_BAD_REQUEST_Error_InvalidDigit() throws Exception { + public void testExecuteSecurityObjectById_Result_BAD_REQUEST_Error_SpecifiedObjectAbsent() throws Exception { String expectedPath = objectIdVer_0 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3; String actualResult = sendRPCExecuteById(expectedPath); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -253,8 +254,26 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT } private String sendRPCExecuteWithValueById(String path, Object value) throws Exception { - String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\", \"value\": \"" + value + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), setRpcRequest, String.class, status().isOk()); + ObjectNode params = JacksonUtil.newObjectNode(); + params.put("id", path); + + // Jackson сам вирішить: ставити лапки (рядок) чи ні (число/boolean/null) + if (value instanceof String) { + params.put("value", (String) value); + } else if (value instanceof Integer) { + params.put("value", (Integer) value); + } else if (value instanceof Boolean) { + params.put("value", (Boolean) value); + } else { + params.set("value", JacksonUtil.valueToTree(value)); + } + + ObjectNode setRpcRequest = JacksonUtil.newObjectNode(); + setRpcRequest.put("method", "Execute"); + setRpcRequest.set("params", params); + + return doPostAsync("/api/plugins/rpc/twoway/" + lwM2MTestClient.getDeviceIdStr(), + JacksonUtil.toString(setRpcRequest), String.class, status().isOk()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 768008f65f..c5b8779a3f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -271,40 +271,34 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im validateVersionedId(client, request); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(request.getVersionedId())); ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), modelProvider); - if (resourceModelExecute == null) { - LwM2mModel model = createModelsDefault(); - if (pathIds.isResource()) { - resourceModelExecute = model.getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); - } + if (resourceModelExecute == null && pathIds.isResource()) { + resourceModelExecute = createModelsDefault().getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); } if (resourceModelExecute == null) { - callback.onValidationError(request.toString(), "ResourceModel with " + request.getVersionedId() + - " is absent in system. Need to add Model with id=" + pathIds.getObjectId() + " ver=" + - getVerFromPathIdVerOrId(request.getVersionedId()) + " to profile."); - } else if (resourceModelExecute.operations.isExecutable()) { - ExecuteRequest downlink; - if (request.getParams() != null && !resourceModelExecute.multiple) { - Object params = request.getParams(); - ResourceModel.Type resourceModel = equalsResourceTypeGetSimpleName(params); - if (resourceModel == null) { - throw new InvalidArgumentException("Unsupported parameter type: " + params.getClass().getSimpleName() + - ". Only simple types (String, Integer, Boolean, etc.) are allowed for Execute arguments."); - } - String args = (String) this.converter.convertValue(params, resourceModel, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId())); - try { - Arguments arguments = Arguments.parse(args); - downlink = new ExecuteRequest(request.getObjectId(), arguments); - } catch (IllegalArgumentException e) { - downlink = new ExecuteRequest(request.getObjectId(), args); - } - } else { - downlink = new ExecuteRequest(request.getObjectId()); + throw new InvalidArgumentException(String.format("ResourceModel with %s is absent in the system. Need to add Model with id= %s ver=%s to profile.", + request.getVersionedId(), pathIds.getObjectId(), getVerFromPathIdVerOrId(request.getVersionedId()))); + } + if (!resourceModelExecute.operations.isExecutable()) { + throw new InvalidArgumentException(String.format("Resource with %s is not executable.", request.getVersionedId())); + } + + ExecuteRequest downlink; + Object params = request.getParams(); + // 4. Handle parameters if they exist and the resource is not a multiple-instance resource + if (params != null && !resourceModelExecute.multiple) { + ResourceModel.Type resourceModelType = equalsResourceTypeGetSimpleName(params); + if (resourceModelType == null) { + throw new InvalidArgumentException(String.format("Unsupported parameter type: %s. Only simple types (String, Integer, Boolean, etc.) are allowed for Execute arguments.", + params.getClass().getSimpleName())); } - sendSimpleRequest(client, downlink, request.getTimeout(), callback); + String args = (String) this.converter.convertValue(params, resourceModelType, ResourceModel.Type.STRING, pathIds); + downlink = new ExecuteRequest(request.getObjectId(), args); } else { - callback.onValidationError(request.toString(), "Resource with " + request.getVersionedId() + " is not executable."); + downlink = new ExecuteRequest(request.getObjectId()); } - } catch (InvalidRequestException | InvalidArgumentException e) { + sendSimpleRequest(client, downlink, request.getTimeout(), callback); + } catch (Exception e) { + log.error("[{}] Validation failed for Execute request: {}", client.getEndpoint(), e.getMessage()); callback.onValidationError(request.toString(), e.getMessage()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index e3bf6e8daa..a0cbd4f598 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -242,12 +242,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendExecuteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { RpcExecuteRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcExecuteRequest.class); - TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId).timeout(clientContext.getRequestTimeout(client)).build(); - if (!requestMsg.getParams().isEmpty()) { - downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId) - .params(requestBody.getValue()) - .timeout(clientContext.getRequestTimeout(client)).build(); - } + Object value = requestBody != null ? requestBody.getValue() : null; + TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder() + .versionedId(versionedId) + .params(value) + .timeout(clientContext.getRequestTimeout(client)) + .build(); var mainCallback = new TbLwM2MExecuteCallback(logService, client, versionedId); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); downlinkHandler.sendExecuteRequest(client, downlink, rpcCallback);