From 7898cf3fb9461aef06ba5716c87ff6a3d83039b6 Mon Sep 17 00:00:00 2001 From: dshvaika Date: Tue, 30 Jun 2026 17:04:44 +0300 Subject: [PATCH] =?UTF-8?q?refactor(attributes):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20share=20scope=20helpers,=20fix=20param=20docs,=20ex?= =?UTF-8?q?pand=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - move parseAttributeScope to JsonConverter; add applyClientScope/applySharedScope shared by HTTP/CoAP - CoAP: skip key parsing when allClientKeys/allSharedKeys set (precedence structural) - HTTP: correct @Parameter required flag on clientKeys/sharedKeys - relocate parseAttributeScope unit tests to common/proto; add apply*Scope coverage - gateway JSON tests: all-client-only + specific shared keys; unique device names + subscription await - migrate attribute-key literals to shared constants (MQTT + CoAP test bases) - HTTP: add allSharedKeys integration test --- .../server/system/BaseHttpDeviceApiTest.java | 27 ++++++++++ ...AbstractCoapAttributesIntegrationTest.java | 15 +++--- ...AbstractMqttAttributesIntegrationTest.java | 20 ++++---- ...tAttributesRequestJsonIntegrationTest.java | 20 ++++++++ .../server/common/adaptor/JsonConverter.java | 47 ++++++++++++++++++ .../JsonConverterAttributeRequestTest.java} | 49 +++++++++++++++++-- .../coap/adaptors/CoapAdaptorUtils.java | 17 ++----- .../transport/http/DeviceApiController.java | 16 ++---- .../mqtt/adaptors/JsonMqttAdaptor.java | 29 +---------- .../AbstractGatewaySessionHandler.java | 4 +- 10 files changed, 172 insertions(+), 72 deletions(-) rename common/{transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptorTest.java => proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterAttributeRequestTest.java} (53%) diff --git a/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java index 89825a1e58..9dff237eee 100644 --- a/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java +++ b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java @@ -105,6 +105,33 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { assertThat(resp.has("shared")).isFalse(); } + @Test + public void testGetAllSharedAttributesViaAllSharedKeysParam() throws Exception { + String token = deviceCredentials.getCredentialsId(); + Map sharedAttrs = new HashMap<>(); + sharedAttrs.put("sharedA", "valueA"); + sharedAttrs.put("sharedB", "valueB"); + mockMvc.perform( + asyncDispatch(doPost("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/attributes/SHARED_SCOPE", sharedAttrs).andReturn())) + .andExpect(status().isOk()); + String allSharedKeysUrl = "/api/v1/" + token + "/attributes?allSharedKeys=true"; + Awaitility.await("shared attributes are persisted and returned via allSharedKeys=true") + .atMost(30, TimeUnit.SECONDS) + .pollInterval(Duration.ofMillis(100)) + .ignoreExceptions() + .until(() -> { + JsonNode r = JacksonUtil.toJsonNode(doGetAsync(allSharedKeysUrl).andReturn().getResponse().getContentAsString()); + return r.has("shared") && sharedAttrs.keySet().stream().allMatch(r.get("shared")::has); + }); + String body = doGetAsync(allSharedKeysUrl) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + JsonNode resp = JacksonUtil.toJsonNode(body); + assertThat(resp.has("shared")).isTrue(); + JsonNode shared = resp.get("shared"); + sharedAttrs.forEach((key, value) -> assertThat(shared.get(key).asText()).isEqualTo(value)); + assertThat(resp.has("client")).isFalse(); + } + @Test public void testReplyToCommandWithLargeResponse() throws Exception { String errorResponse = doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/rpc/5", diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java index 60183ad029..f5b04049b2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java @@ -95,6 +95,9 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap protected static final String SHARED_ATTRIBUTES_PAYLOAD = "{\"sharedStr\":\"value1\",\"sharedBool\":true,\"sharedDbl\":42.0,\"sharedLong\":73," + "\"sharedJson\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; + protected static final String CLIENT_ATTRIBUTE_KEYS = "clientStr,clientBool,clientDbl,clientLong,clientJson"; + protected static final String SHARED_ATTRIBUTE_KEYS = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + protected static final String SHARED_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION = "{\"sharedStr\":\"value\",\"sharedBool\":false,\"sharedDbl\":41.0,\"sharedLong\":72," + "\"sharedJson\":{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}}"; @@ -172,8 +175,8 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); SingleEntityFilter dtf = new SingleEntityFilter(); dtf.setSingleEntity(AliasEntityId.fromEntityId(savedDevice.getId())); - String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; - String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + String clientKeysStr = CLIENT_ATTRIBUTE_KEYS; + String sharedKeysStr = SHARED_ATTRIBUTE_KEYS; List clientKeysList = List.of(clientKeysStr.split(",")); List sharedKeysList = List.of(sharedKeysStr.split(",")); List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); @@ -202,8 +205,8 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); SingleEntityFilter dtf = new SingleEntityFilter(); dtf.setSingleEntity(AliasEntityId.fromEntityId(savedDevice.getId())); - String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; - String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + String clientKeysStr = CLIENT_ATTRIBUTE_KEYS; + String sharedKeysStr = SHARED_ATTRIBUTE_KEYS; List keys = new ArrayList<>(); keys.addAll(getEntityKeys(List.of(clientKeysStr.split(",")), CLIENT_ATTRIBUTE)); keys.addAll(getEntityKeys(List.of(sharedKeysStr.split(",")), SHARED_ATTRIBUTE)); @@ -230,8 +233,8 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); SingleEntityFilter dtf = new SingleEntityFilter(); dtf.setSingleEntity(AliasEntityId.fromEntityId(savedDevice.getId())); - String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; - String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + String clientKeysStr = CLIENT_ATTRIBUTE_KEYS; + String sharedKeysStr = SHARED_ATTRIBUTE_KEYS; List clientKeysList = List.of(clientKeysStr.split(",")); List sharedKeysList = List.of(sharedKeysStr.split(",")); List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java index 23cef0b49d..8d4cc6a6ae 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java @@ -344,8 +344,8 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt client.connectAndWait(accessToken); SingleEntityFilter dtf = new SingleEntityFilter(); dtf.setSingleEntity(AliasEntityId.fromEntityId(savedDevice.getId())); - String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; - String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + String clientKeysStr = CLIENT_ATTRIBUTE_KEYS; + String sharedKeysStr = SHARED_ATTRIBUTE_KEYS; List clientKeysList = List.of(clientKeysStr.split(",")); List sharedKeysList = List.of(sharedKeysStr.split(",")); List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); @@ -398,8 +398,8 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt MqttTestClient client = new MqttTestClient(); client.connectAndWait(accessToken); DeviceTypeFilter dtf = new DeviceTypeFilter(List.of(savedDevice.getType()), savedDevice.getName()); - String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; - String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + String clientKeysStr = CLIENT_ATTRIBUTE_KEYS; + String sharedKeysStr = SHARED_ATTRIBUTE_KEYS; List clientKeysList = List.of(clientKeysStr.split(",")); List sharedKeysList = List.of(sharedKeysStr.split(",")); List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); @@ -439,7 +439,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt 100); assertNotNull(device); - String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; + String clientKeysStr = CLIENT_ATTRIBUTE_KEYS; String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/CLIENT_SCOPE?keys=" + clientKeysStr; @@ -454,7 +454,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt SingleEntityFilter dtf = new SingleEntityFilter(); dtf.setSingleEntity(AliasEntityId.fromEntityId(device.getId())); - String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + String sharedKeysStr = SHARED_ATTRIBUTE_KEYS; List clientKeysList = List.of(clientKeysStr.split(",")); List sharedKeysList = List.of(sharedKeysStr.split(",")); List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); @@ -493,10 +493,9 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt client.disconnect(); } - protected void processJsonTestGatewayRequestAttributesSeparated(String requestPayloadSuffix, String expectedBody) throws Exception { + protected void processJsonTestGatewayRequestAttributesSeparated(String deviceName, String requestPayloadSuffix, String expectedBody) throws Exception { MqttTestClient client = new MqttTestClient(); client.connectAndWait(gatewayAccessToken); - String deviceName = "Gateway Device Request Attributes Separated"; String postClientAttributes = "{\"" + deviceName + "\":" + CLIENT_ATTRIBUTES_PAYLOAD + "}"; client.publishAndWait(GATEWAY_ATTRIBUTES_TOPIC, postClientAttributes.getBytes()); @@ -527,6 +526,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt assertThat(update).as("ws update received").isNotBlank(); client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE); + awaitForDeviceActorToReceiveSubscription(device.getId(), FeatureType.ATTRIBUTES, 1); MqttTestCallback callback = new MqttTestSubscribeOnTopicCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC); client.setCallback(callback); String requestPayloadStr = "{\"id\": 1, \"device\": \"" + deviceName + "\"" + requestPayloadSuffix + "}"; @@ -544,7 +544,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt client.connectAndWait(gatewayAccessToken); String deviceName = "Gateway Device Request Attributes"; - String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; + String clientKeysStr = CLIENT_ATTRIBUTE_KEYS; List clientKeysList = List.of(clientKeysStr.split(",")); client.publishAndWait(GATEWAY_ATTRIBUTES_TOPIC, getProtoGatewayDeviceClientAttributesPayload(deviceName, clientKeysList)); @@ -555,7 +555,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt SingleEntityFilter dtf = new SingleEntityFilter(); dtf.setSingleEntity(AliasEntityId.fromEntityId(device.getId())); - String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + String sharedKeysStr = SHARED_ATTRIBUTE_KEYS; List sharedKeysList = List.of(sharedKeysStr.split(",")); List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); List shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/request/MqttAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/request/MqttAttributesRequestJsonIntegrationTest.java index 77e2df7b5c..df5ed3c1ea 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/request/MqttAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/request/MqttAttributesRequestJsonIntegrationTest.java @@ -89,6 +89,7 @@ public class MqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttrib public void testGatewayRequestAllAttributesSeparated() throws Exception { // new format: empty clientKeys + empty sharedKeys => all both, scope-separated response processJsonTestGatewayRequestAttributesSeparated( + "Gateway Device Request All Both Separated Json", ", \"clientKeys\": \"\", \"sharedKeys\": \"\"", ",\"client\":" + CLIENT_ATTRIBUTES_PAYLOAD + ",\"shared\":" + SHARED_ATTRIBUTES_PAYLOAD); } @@ -97,7 +98,26 @@ public class MqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttrib public void testGatewayRequestAllSharedSeparated() throws Exception { // new format: empty sharedKeys only => all shared, no client in the separated response processJsonTestGatewayRequestAttributesSeparated( + "Gateway Device Request All Shared Separated Json", ", \"sharedKeys\": \"\"", ",\"shared\":" + SHARED_ATTRIBUTES_PAYLOAD); } + + @Test + public void testGatewayRequestAllClientSeparated() throws Exception { + // new format: empty clientKeys only => all client, no shared in the separated response + processJsonTestGatewayRequestAttributesSeparated( + "Gateway Device Request All Client Separated Json", + ", \"clientKeys\": \"\"", + ",\"client\":" + CLIENT_ATTRIBUTES_PAYLOAD); + } + + @Test + public void testGatewayRequestSpecificSharedKeysSeparated() throws Exception { + // new format: specific shared key list => only those shared keys, scope-separated response + processJsonTestGatewayRequestAttributesSeparated( + "Gateway Device Request Specific Shared Keys Json", + ", \"sharedKeys\": \"sharedStr,sharedBool\"", + ",\"shared\":{\"sharedStr\":\"value1\",\"sharedBool\":true}"); + } } diff --git a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java index 7284020aee..47f288c0c9 100644 --- a/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java +++ b/common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java @@ -55,6 +55,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -380,6 +382,51 @@ public class JsonConverter { } } + /** + * Three-state per-scope attribute selection for the JSON {@code clientKeys}/{@code sharedKeys} format + * (device and gateway MQTT APIs): + *
    + *
  • field absent / null => the scope is excluded (neither names nor "all" is set);
  • + *
  • field present + empty (or blank) string => every key in that scope ({@code setAll});
  • + *
  • field present + a comma-separated string of names => only those keys ({@code setNames}).
  • + *
+ */ + public static void parseAttributeScope(JsonObject json, String field, + Consumer> setNames, Runnable setAll) { + if (!json.has(field) || json.get(field).isJsonNull()) { + return; + } + String value = json.get(field).getAsString(); + if (value.trim().isEmpty()) { + setAll.run(); + } else { + setNames.accept(Arrays.asList(value.split(","))); + } + } + + /** + * Populate the client scope of an attribute request: "all" wins over a specific key list; a missing/empty + * list leaves the scope unset. Shared by the HTTP and CoAP query-parameter parsers. + */ + public static void applyClientScope(TransportProtos.GetAttributeRequestMsg.Builder builder, boolean all, Collection names) { + if (all) { + builder.setAllClientAttributes(true); + } else if (names != null && !names.isEmpty()) { + builder.addAllClientAttributeNames(names); + } + } + + /** + * Shared-scope counterpart of {@link #applyClientScope}. + */ + public static void applySharedScope(TransportProtos.GetAttributeRequestMsg.Builder builder, boolean all, Collection names) { + if (all) { + builder.setAllSharedAttributes(true); + } else if (names != null && !names.isEmpty()) { + builder.addAllSharedAttributeNames(names); + } + } + public static JsonObject getJsonObjectForGateway(String deviceName, AttributeUpdateNotificationMsg notificationMsg) { JsonObject result = new JsonObject(); diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptorTest.java b/common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterAttributeRequestTest.java similarity index 53% rename from common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptorTest.java rename to common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterAttributeRequestTest.java index ca5059fc63..7329a3fc3d 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptorTest.java +++ b/common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterAttributeRequestTest.java @@ -13,27 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.mqtt.adaptors; +package org.thingsboard.server.common.adaptor; import com.google.gson.JsonNull; import com.google.gson.JsonObject; import org.junit.jupiter.api.Test; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class JsonMqttAdaptorTest { +public class JsonConverterAttributeRequestTest { private final List names = new ArrayList<>(); private final AtomicBoolean allInvoked = new AtomicBoolean(false); private void parseClientKeys(JsonObject json) { - JsonMqttAdaptor.parseAttributeScope(json, "clientKeys", names::addAll, () -> allInvoked.set(true)); + JsonConverter.parseAttributeScope(json, "clientKeys", names::addAll, () -> allInvoked.set(true)); } @Test @@ -78,4 +80,45 @@ public class JsonMqttAdaptorTest { assertFalse(allInvoked.get()); assertEquals(List.of("a", "b", "c"), names); } + + @Test + public void applyClientScope_all_winsOverNames() { + GetAttributeRequestMsg.Builder b = GetAttributeRequestMsg.newBuilder(); + JsonConverter.applyClientScope(b, true, List.of("ignored")); + assertThat(b.getAllClientAttributes()).isTrue(); + assertThat(b.getClientAttributeNamesList()).isEmpty(); + } + + @Test + public void applyClientScope_names_addsNames() { + GetAttributeRequestMsg.Builder b = GetAttributeRequestMsg.newBuilder(); + JsonConverter.applyClientScope(b, false, List.of("a", "b")); + assertThat(b.getAllClientAttributes()).isFalse(); + assertThat(b.getClientAttributeNamesList()).containsExactly("a", "b"); + } + + @Test + public void applyClientScope_nullOrEmpty_setsNothing() { + GetAttributeRequestMsg.Builder b = GetAttributeRequestMsg.newBuilder(); + JsonConverter.applyClientScope(b, false, null); + JsonConverter.applyClientScope(b, false, List.of()); + assertThat(b.getAllClientAttributes()).isFalse(); + assertThat(b.getClientAttributeNamesList()).isEmpty(); + } + + @Test + public void applySharedScope_all_winsOverNames() { + GetAttributeRequestMsg.Builder b = GetAttributeRequestMsg.newBuilder(); + JsonConverter.applySharedScope(b, true, null); + assertThat(b.getAllSharedAttributes()).isTrue(); + assertThat(b.getSharedAttributeNamesList()).isEmpty(); + } + + @Test + public void applySharedScope_names_addsNames() { + GetAttributeRequestMsg.Builder b = GetAttributeRequestMsg.newBuilder(); + JsonConverter.applySharedScope(b, false, List.of("s1")); + assertThat(b.getAllSharedAttributes()).isFalse(); + assertThat(b.getSharedAttributeNamesList()).containsExactly("s1"); + } } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java index 2ec44e4976..6878b4b29b 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.coap.adaptors; import org.eclipse.californium.core.coap.Request; import org.thingsboard.server.common.adaptor.AdaptorException; +import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.gen.transport.TransportProtos; @@ -33,18 +34,10 @@ public class CoapAdaptorUtils { if (queryElements != null && queryElements.size() > 0) { boolean allClient = "true".equalsIgnoreCase(getQueryValue(queryElements, "allClientKeys")); boolean allShared = "true".equalsIgnoreCase(getQueryValue(queryElements, "allSharedKeys")); - Set clientKeys = toKeys(queryElements, "clientKeys"); - Set sharedKeys = toKeys(queryElements, "sharedKeys"); - if (allClient) { - result.setAllClientAttributes(true); - } else if (clientKeys != null) { - result.addAllClientAttributeNames(clientKeys); - } - if (allShared) { - result.setAllSharedAttributes(true); - } else if (sharedKeys != null) { - result.addAllSharedAttributeNames(sharedKeys); - } + Set clientKeys = allClient ? null : toKeys(queryElements, "clientKeys"); + Set sharedKeys = allShared ? null : toKeys(queryElements, "sharedKeys"); + JsonConverter.applyClientScope(result, allClient, clientKeys); + JsonConverter.applySharedScope(result, allShared, sharedKeys); } result.setOnlyShared(false); return result.build(); diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index b933d97b97..c61e7c1d2a 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -141,9 +141,9 @@ public class DeviceApiController implements TbTransportService { public DeferredResult getDeviceAttributes( @Parameter(description = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")) @PathVariable("deviceToken") String deviceToken, - @Parameter(description = "Comma separated key names for attribute with client scope", required = true , schema = @Schema(defaultValue = "state")) + @Parameter(description = "Comma separated key names for attribute with client scope", required = false, schema = @Schema(defaultValue = "state")) @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, - @Parameter(description = "Comma separated key names for attribute with shared scope", required = true , schema = @Schema(defaultValue = "configuration")) + @Parameter(description = "Comma separated key names for attribute with shared scope", required = false, schema = @Schema(defaultValue = "configuration")) @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys, @Parameter(description = "Set to true to return ALL client-scope attributes (ignores clientKeys)") @RequestParam(value = "allClientKeys", required = false, defaultValue = "false") boolean allClientKeys, @@ -155,16 +155,8 @@ public class DeviceApiController implements TbTransportService { GetAttributeRequestMsg.Builder request = GetAttributeRequestMsg.newBuilder().setRequestId(0); List clientKeySet = !StringUtils.isEmpty(clientKeys) ? Arrays.asList(clientKeys.split(",")) : null; List sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? Arrays.asList(sharedKeys.split(",")) : null; - if (allClientKeys) { - request.setAllClientAttributes(true); - } else if (clientKeySet != null) { - request.addAllClientAttributeNames(clientKeySet); - } - if (allSharedKeys) { - request.setAllSharedAttributes(true); - } else if (sharedKeySet != null) { - request.addAllSharedAttributeNames(sharedKeySet); - } + JsonConverter.applyClientScope(request, allClientKeys, clientKeySet); + JsonConverter.applySharedScope(request, allSharedKeys, sharedKeySet); TransportService transportService = transportContext.getTransportService(); transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo), diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 934acbb2d6..42c2632f07 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -37,11 +37,8 @@ import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionConte import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.function.Consumer; import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT; @@ -177,8 +174,8 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { result.setRequestId(getRequestId(topicName, topicBase)); String payload = inbound.payload().toString(UTF8); JsonObject json = JsonParser.parseString(payload).getAsJsonObject(); - parseAttributeScope(json, "clientKeys", result::addAllClientAttributeNames, () -> result.setAllClientAttributes(true)); - parseAttributeScope(json, "sharedKeys", result::addAllSharedAttributeNames, () -> result.setAllSharedAttributes(true)); + JsonConverter.parseAttributeScope(json, "clientKeys", result::addAllClientAttributeNames, () -> result.setAllClientAttributes(true)); + JsonConverter.parseAttributeScope(json, "sharedKeys", result::addAllSharedAttributeNames, () -> result.setAllSharedAttributes(true)); return result.build(); } catch (RuntimeException e) { log.debug("Failed to decode get attributes request", e); @@ -242,28 +239,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { return new MqttPublishMessage(mqttFixedHeader, header, payload); } - /** - * Three-state per-scope attribute selection shared by the device and gateway JSON request parsers, - * matching the comma-separated {@code clientKeys}/{@code sharedKeys} format of the device MQTT API: - *
    - *
  • field absent / null => the scope is excluded (neither names nor "all" is set);
  • - *
  • field present + empty (or blank) string => every key in that scope ({@code setAll});
  • - *
  • field present + a comma-separated string of names => only those keys ({@code setNames}).
  • - *
- */ - public static void parseAttributeScope(JsonObject json, String field, - Consumer> setNames, Runnable setAll) { - if (!json.has(field) || json.get(field).isJsonNull()) { - return; - } - String value = json.get(field).getAsString(); - if (value.trim().isEmpty()) { - setAll.run(); - } else { - setNames.accept(Arrays.asList(value.split(","))); - } - } - private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException { String payload = payloadData.toString(UTF8); if (payload == null) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java index 660eeaf954..4f00328873 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java @@ -658,8 +658,8 @@ public abstract class AbstractGatewaySessionHandler b.setAllClientAttributes(true)); - JsonMqttAdaptor.parseAttributeScope(jsonObj, "sharedKeys", b::addAllSharedAttributeNames, () -> b.setAllSharedAttributes(true)); + JsonConverter.parseAttributeScope(jsonObj, "clientKeys", b::addAllClientAttributeNames, () -> b.setAllClientAttributes(true)); + JsonConverter.parseAttributeScope(jsonObj, "sharedKeys", b::addAllSharedAttributeNames, () -> b.setAllSharedAttributes(true)); requestMsg = b.build(); } else if (jsonObj.has("client")) { // legacy format: client boolean + key/keys; keep the legacy value/values response