Browse Source

refactor(attributes): address review — share scope helpers, fix param docs, expand tests

- 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
pull/15865/head
dshvaika 4 weeks ago
parent
commit
7898cf3fb9
  1. 27
      application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
  2. 15
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java
  3. 20
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java
  4. 20
      application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/request/MqttAttributesRequestJsonIntegrationTest.java
  5. 47
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java
  6. 49
      common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterAttributeRequestTest.java
  7. 17
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapAdaptorUtils.java
  8. 16
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  9. 29
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  10. 4
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java

27
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<String, String> 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",

15
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<String> clientKeysList = List.of(clientKeysStr.split(","));
List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> 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<EntityKey> 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<String> clientKeysList = List.of(clientKeysStr.split(","));
List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE);

20
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<String> clientKeysList = List.of(clientKeysStr.split(","));
List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> 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<String> clientKeysList = List.of(clientKeysStr.split(","));
List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> 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<String> clientKeysList = List.of(clientKeysStr.split(","));
List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> 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<String> 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<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE);
List<EntityKey> shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE);

20
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}");
}
}

47
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):
* <ul>
* <li>field absent / null =&gt; the scope is excluded (neither names nor "all" is set);</li>
* <li>field present + empty (or blank) string =&gt; every key in that scope ({@code setAll});</li>
* <li>field present + a comma-separated string of names =&gt; only those keys ({@code setNames}).</li>
* </ul>
*/
public static void parseAttributeScope(JsonObject json, String field,
Consumer<List<String>> 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<String> 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<String> 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();

49
common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptorTest.java → 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<String> 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");
}
}

17
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<String> clientKeys = toKeys(queryElements, "clientKeys");
Set<String> 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<String> clientKeys = allClient ? null : toKeys(queryElements, "clientKeys");
Set<String> sharedKeys = allShared ? null : toKeys(queryElements, "sharedKeys");
JsonConverter.applyClientScope(result, allClient, clientKeys);
JsonConverter.applySharedScope(result, allShared, sharedKeys);
}
result.setOnlyShared(false);
return result.build();

16
common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -141,9 +141,9 @@ public class DeviceApiController implements TbTransportService {
public DeferredResult<ResponseEntity> 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<String> clientKeySet = !StringUtils.isEmpty(clientKeys) ? Arrays.asList(clientKeys.split(",")) : null;
List<String> 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),

29
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:
* <ul>
* <li>field absent / null =&gt; the scope is excluded (neither names nor "all" is set);</li>
* <li>field present + empty (or blank) string =&gt; every key in that scope ({@code setAll});</li>
* <li>field present + a comma-separated string of names =&gt; only those keys ({@code setNames}).</li>
* </ul>
*/
public static void parseAttributeScope(JsonObject json, String field,
Consumer<List<String>> 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) {

4
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/AbstractGatewaySessionHandler.java

@ -658,8 +658,8 @@ public abstract class AbstractGatewaySessionHandler<T extends AbstractGatewayDev
// new unified format: clientKeys/sharedKeys + empty-value="all"; emit the separated response
TransportProtos.GetAttributeRequestMsg.Builder b = TransportProtos.GetAttributeRequestMsg.newBuilder()
.setRequestId(requestId).setSeparateScopesResponse(true);
JsonMqttAdaptor.parseAttributeScope(jsonObj, "clientKeys", b::addAllClientAttributeNames, () -> 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

Loading…
Cancel
Save