Browse Source

feat(attributes): per-scope fetch-all + scope-separated gateway response

Actor getAttributesKvEntries: explicit per-scope all/specific/none with all-wins;
both-absent keeps the 'fetch everything' backward-compat. Echo separateScopesResponse
request->response. JsonConverter.getJsonObjectForGateway emits {client,shared} when
separateScopesResponse, else legacy value/values. Unit test for the converter.
pull/15865/head
dshvaika 4 months ago
parent
commit
f0575b66df
  1. 33
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  2. 24
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java
  3. 74
      common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterGatewayResponseTest.java

33
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -518,6 +518,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId);
}
@SuppressWarnings("deprecation") // isMultipleAttributesRequest retained for the legacy gateway value/values response
private void handleGetAttributesRequest(SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
int requestId = request.getRequestId();
if (request.getOnlyShared()) {
@ -548,6 +549,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {
GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
.setRequestId(requestId)
.setSeparateScopesResponse(request.getSeparateScopesResponse())
.addAllClientAttributeList(KvProtoUtil.attrToTsKvProtos(result.get(0)))
.addAllSharedAttributeList(KvProtoUtil.attrToTsKvProtos(result.get(1)))
.setIsMultipleAttributesRequest(
@ -568,20 +570,33 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) {
boolean clientAll = request.getAllClientAttributes();
boolean sharedAll = request.getAllSharedAttributes();
boolean clientSpecific = !CollectionUtils.isEmpty(request.getClientAttributeNamesList());
boolean sharedSpecific = !CollectionUtils.isEmpty(request.getSharedAttributeNamesList());
boolean noClientSignal = !clientAll && !clientSpecific;
boolean noSharedSignal = !sharedAll && !sharedSpecific;
ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture;
ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture;
if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
if (noClientSignal && noSharedSignal) {
// backward-compat: empty request => fetch everything from both scopes
clientAttributesFuture = findAllAttributesByScope(AttributeScope.CLIENT_SCOPE);
sharedAttributesFuture = findAllAttributesByScope(AttributeScope.SHARED_SCOPE);
} else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), AttributeScope.CLIENT_SCOPE);
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), AttributeScope.SHARED_SCOPE);
} else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = Futures.immediateFuture(Collections.emptyList());
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), AttributeScope.SHARED_SCOPE);
} else {
sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList());
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), AttributeScope.CLIENT_SCOPE);
// "all <scope>" wins over a specific key list for the same scope
clientAttributesFuture = clientAll
? findAllAttributesByScope(AttributeScope.CLIENT_SCOPE)
: (clientSpecific
? findAttributesByScope(toSet(request.getClientAttributeNamesList()), AttributeScope.CLIENT_SCOPE)
: Futures.immediateFuture(Collections.emptyList()));
sharedAttributesFuture = sharedAll
? findAllAttributesByScope(AttributeScope.SHARED_SCOPE)
: (sharedSpecific
? findAttributesByScope(toSet(request.getSharedAttributeNamesList()), AttributeScope.SHARED_SCOPE)
: Futures.immediateFuture(Collections.emptyList()));
}
return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture));
}

24
common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java

@ -354,6 +354,7 @@ public class JsonConverter {
return result;
}
@SuppressWarnings("deprecation") // isMultipleAttributesRequest retained for the legacy gateway value/values response
public static JsonObject getJsonObjectForGateway(
String deviceName,
TransportProtos.GetAttributeResponseMsg responseMsg
@ -361,11 +362,24 @@ public class JsonConverter {
JsonObject result = new JsonObject();
result.addProperty("id", responseMsg.getRequestId());
result.addProperty(DEVICE_PROPERTY, deviceName);
if (responseMsg.getClientAttributeListCount() > 0) {
addValues(result, responseMsg.getClientAttributeListList(), responseMsg.getIsMultipleAttributesRequest());
}
if (responseMsg.getSharedAttributeListCount() > 0) {
addValues(result, responseMsg.getSharedAttributeListList(), responseMsg.getIsMultipleAttributesRequest());
if (responseMsg.getSeparateScopesResponse()) {
if (responseMsg.getClientAttributeListCount() > 0) {
JsonObject client = new JsonObject();
responseMsg.getClientAttributeListList().forEach(addToObjectFromProto(client));
result.add("client", client);
}
if (responseMsg.getSharedAttributeListCount() > 0) {
JsonObject shared = new JsonObject();
responseMsg.getSharedAttributeListList().forEach(addToObjectFromProto(shared));
result.add("shared", shared);
}
} else {
if (responseMsg.getClientAttributeListCount() > 0) {
addValues(result, responseMsg.getClientAttributeListList(), responseMsg.getIsMultipleAttributesRequest());
}
if (responseMsg.getSharedAttributeListCount() > 0) {
addValues(result, responseMsg.getSharedAttributeListList(), responseMsg.getIsMultipleAttributesRequest());
}
}
return result;
}

74
common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterGatewayResponseTest.java

@ -0,0 +1,74 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.adaptor;
import com.google.gson.JsonObject;
import org.junit.Test;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import static org.assertj.core.api.Assertions.assertThat;
public class JsonConverterGatewayResponseTest {
private TsKvProto kv(String key, long v) {
return TsKvProto.newBuilder().setTs(1L).setKv(
KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V).setLongV(v).build()).build();
}
@Test
public void separateScopes_keepsScopeLabels_keyNames_andOverlap() {
GetAttributeResponseMsg msg = GetAttributeResponseMsg.newBuilder()
.setRequestId(1)
.setSeparateScopesResponse(true)
.addClientAttributeList(kv("test", 27))
.addSharedAttributeList(kv("test", 99))
.build();
JsonObject out = JsonConverter.getJsonObjectForGateway("DeviceA", msg);
assertThat(out.get("id").getAsInt()).isEqualTo(1);
assertThat(out.get("device").getAsString()).isEqualTo("DeviceA");
assertThat(out.getAsJsonObject("client").get("test").getAsLong()).isEqualTo(27);
assertThat(out.getAsJsonObject("shared").get("test").getAsLong()).isEqualTo(99);
assertThat(out.has("value")).isFalse();
assertThat(out.has("values")).isFalse();
}
@Test
public void separateScopes_omitsEmptyScope() {
GetAttributeResponseMsg msg = GetAttributeResponseMsg.newBuilder()
.setRequestId(2).setSeparateScopesResponse(true)
.addSharedAttributeList(kv("s", 1)).build();
JsonObject out = JsonConverter.getJsonObjectForGateway("DeviceA", msg);
assertThat(out.has("client")).isFalse();
assertThat(out.getAsJsonObject("shared").get("s").getAsLong()).isEqualTo(1);
}
@Test
@SuppressWarnings("deprecation")
public void legacy_singleValue_unchanged() {
GetAttributeResponseMsg msg = GetAttributeResponseMsg.newBuilder()
.setRequestId(3).setSeparateScopesResponse(false)
.setIsMultipleAttributesRequest(false)
.addSharedAttributeList(kv("s", 5)).build();
JsonObject out = JsonConverter.getJsonObjectForGateway("DeviceA", msg);
assertThat(out.get("value").getAsLong()).isEqualTo(5);
assertThat(out.has("shared")).isFalse();
}
}
Loading…
Cancel
Save