Investigating the cache issues
This commit is contained in:
parent
23c774610a
commit
15104028ea
@ -210,7 +210,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService {
|
||||
}
|
||||
}
|
||||
|
||||
private ListenableFuture<List<Void>> saveProvisionStateAttribute(Device device) {
|
||||
private ListenableFuture<List<String>> saveProvisionStateAttribute(Device device) {
|
||||
return attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
|
||||
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(DEVICE_PROVISION_STATE, PROVISIONED_STATE),
|
||||
System.currentTimeMillis())));
|
||||
|
||||
@ -309,10 +309,10 @@ public final class EdgeGrpcSession implements Closeable {
|
||||
public void onSuccess(@Nullable UUID ifOffset) {
|
||||
if (ifOffset != null) {
|
||||
Long newStartTs = Uuids.unixTimestamp(ifOffset);
|
||||
ListenableFuture<List<Void>> updateFuture = updateQueueStartTs(newStartTs);
|
||||
ListenableFuture<List<String>> updateFuture = updateQueueStartTs(newStartTs);
|
||||
Futures.addCallback(updateFuture, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable List<Void> list) {
|
||||
public void onSuccess(@Nullable List<String> list) {
|
||||
log.debug("[{}] queue offset was updated [{}][{}]", sessionId, ifOffset, newStartTs);
|
||||
result.set(null);
|
||||
}
|
||||
@ -496,7 +496,7 @@ public final class EdgeGrpcSession implements Closeable {
|
||||
}, ctx.getGrpcCallbackExecutorService());
|
||||
}
|
||||
|
||||
private ListenableFuture<List<Void>> updateQueueStartTs(Long newStartTs) {
|
||||
private ListenableFuture<List<String>> updateQueueStartTs(Long newStartTs) {
|
||||
log.trace("[{}] updating QueueStartTs [{}][{}]", this.sessionId, edge.getId(), newStartTs);
|
||||
List<AttributeKvEntry> attributes = Collections.singletonList(
|
||||
new BaseAttributeKvEntry(
|
||||
|
||||
@ -206,10 +206,10 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
|
||||
SettableFuture<Void> futureToSet = SettableFuture.create();
|
||||
JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
|
||||
Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(json);
|
||||
ListenableFuture<List<Void>> future = attributesService.save(tenantId, entityId, metaData.getValue("scope"), new ArrayList<>(attributes));
|
||||
Futures.addCallback(future, new FutureCallback<List<Void>>() {
|
||||
ListenableFuture<List<String>> future = attributesService.save(tenantId, entityId, metaData.getValue("scope"), new ArrayList<>(attributes));
|
||||
Futures.addCallback(future, new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable List<Void> voids) {
|
||||
public void onSuccess(@Nullable List<String> keys) {
|
||||
Pair<String, RuleChainId> defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
|
||||
String queueName = defaultQueueAndRuleChain.getKey();
|
||||
RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue();
|
||||
|
||||
@ -532,7 +532,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
|
||||
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), 0L);
|
||||
addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value));
|
||||
} else {
|
||||
ListenableFuture<List<Void>> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE,
|
||||
ListenableFuture<List<String>> saveFuture = attributesService.save(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE,
|
||||
Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
|
||||
, System.currentTimeMillis())));
|
||||
addTsCallback(saveFuture, new TelemetrySaveCallback<>(deviceId, key, value));
|
||||
|
||||
@ -243,7 +243,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
|
||||
|
||||
@Override
|
||||
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
|
||||
ListenableFuture<List<Void>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
|
||||
ListenableFuture<List<String>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
|
||||
addVoidCallback(saveFuture, callback);
|
||||
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
|
||||
}
|
||||
@ -269,7 +269,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
|
||||
|
||||
@Override
|
||||
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
|
||||
ListenableFuture<List<Void>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
|
||||
ListenableFuture<List<String>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
|
||||
addVoidCallback(deleteFuture, callback);
|
||||
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys));
|
||||
}
|
||||
|
||||
@ -57,12 +57,18 @@ public abstract class AbstractRuleEngineControllerTest extends AbstractControlle
|
||||
}
|
||||
|
||||
protected PageData<Event> getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception {
|
||||
return getEvents(tenantId, entityId, DataConstants.DEBUG_RULE_NODE, limit);
|
||||
}
|
||||
|
||||
protected PageData<Event> getEvents(TenantId tenantId, EntityId entityId, String eventType, int limit) throws Exception {
|
||||
TimePageLink pageLink = new TimePageLink(limit);
|
||||
return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&",
|
||||
new TypeReference<PageData<Event>>() {
|
||||
}, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId());
|
||||
}, pageLink, entityId.getEntityType(), entityId.getId(), eventType, tenantId.getId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected JsonNode getMetadata(Event outEvent) {
|
||||
String metaDataStr = outEvent.getBody().get("metadata").asText();
|
||||
try {
|
||||
|
||||
@ -105,17 +105,20 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
|
||||
Assert.assertNotNull(ruleChainFinal.getFirstRuleNodeId());
|
||||
|
||||
//TODO find out why RULE_NODE update event did not appear all the time
|
||||
// List<Event> rcEvents = Awaitility.await("get debug by rule chain")
|
||||
// .pollInterval(10, MILLISECONDS)
|
||||
// .atMost(TIMEOUT, TimeUnit.SECONDS)
|
||||
// .until(() -> {
|
||||
// List<Event> debugEvents = getDebugEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), 1000)
|
||||
// .getData().stream().filter(x->true).collect(Collectors.toList());
|
||||
// log.warn("filtered debug events [{}]", debugEvents.size());
|
||||
// debugEvents.forEach((e) -> log.warn("event: {}", e));
|
||||
// return debugEvents;
|
||||
// },
|
||||
// x -> x.size() >= 2);
|
||||
List<Event> rcEvents = Awaitility.await("Rule Node started successfully")
|
||||
.pollInterval(10, MILLISECONDS)
|
||||
.atMost(TIMEOUT, TimeUnit.SECONDS)
|
||||
.until(() -> {
|
||||
List<Event> debugEvents = getEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), DataConstants.LC_EVENT, 1000)
|
||||
.getData().stream().filter(e-> {
|
||||
var body = e.getBody();
|
||||
return body.has("event") && body.get("event").asText().equals("STARTED")
|
||||
&& body.has("success") && body.get("success").asBoolean();
|
||||
}).collect(Collectors.toList());
|
||||
debugEvents.forEach((e) -> log.trace("event: {}", e));
|
||||
return debugEvents;
|
||||
},
|
||||
x -> x.size() == 1);
|
||||
|
||||
// Saving the device
|
||||
Device device = new Device();
|
||||
@ -133,6 +136,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
|
||||
TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback);
|
||||
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(tenantId, tbMsg, null, null);
|
||||
// Pushing Message to the system
|
||||
log.warn("before tell tbMsgCallback");
|
||||
actorSystem.tell(qMsg);
|
||||
log.warn("awaiting tbMsgCallback");
|
||||
Mockito.verify(tbMsgCallback, Mockito.timeout(TimeUnit.SECONDS.toMillis(TIMEOUT))).onSuccess();
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
|
||||
<!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>-->
|
||||
<logger name="org.thingsboard.server.controller.TbTestWebSocketClient" level="INFO"/>
|
||||
<logger name="org.thingsboard.server" level="WARN"/>
|
||||
<logger name="org.thingsboard.server.queue" level="INFO"/>
|
||||
<logger name="org.thingsboard.server" level="TRACE"/>
|
||||
<logger name="org.springframework" level="WARN"/>
|
||||
<logger name="org.springframework.boot.test" level="WARN"/>
|
||||
<logger name="org.apache.cassandra" level="WARN"/>
|
||||
|
||||
@ -37,9 +37,9 @@ public interface AttributesService {
|
||||
|
||||
ListenableFuture<List<AttributeKvEntry>> findAll(TenantId tenantId, EntityId entityId, String scope);
|
||||
|
||||
ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes);
|
||||
ListenableFuture<List<String>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes);
|
||||
|
||||
ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys);
|
||||
ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys);
|
||||
|
||||
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
|
||||
|
||||
|
||||
@ -1,63 +1,15 @@
|
||||
/**
|
||||
* Copyright © 2016-2022 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.dao.attributes;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
|
||||
import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE;
|
||||
public interface AttributesCacheWrapper {
|
||||
|
||||
@Service
|
||||
@ConditionalOnProperty(prefix = "cache.attributes", value = "enabled", havingValue = "true")
|
||||
@Primary
|
||||
@Slf4j
|
||||
public class AttributesCacheWrapper {
|
||||
private final Cache attributesCache;
|
||||
Cache.ValueWrapper get(AttributeCacheKey attributeCacheKey);
|
||||
|
||||
public AttributesCacheWrapper(CacheManager cacheManager) {
|
||||
this.attributesCache = cacheManager.getCache(ATTRIBUTES_CACHE);
|
||||
}
|
||||
void put(AttributeCacheKey attributeCacheKey, AttributeKvEntry attributeKvEntry);
|
||||
|
||||
public Cache.ValueWrapper get(AttributeCacheKey attributeCacheKey) {
|
||||
try {
|
||||
return attributesCache.get(attributeCacheKey);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to retrieve element from cache for key {}. Reason - {}.", attributeCacheKey, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
void putIfAbsent(AttributeCacheKey attributeCacheKey, AttributeKvEntry attributeKvEntry);
|
||||
|
||||
public void put(AttributeCacheKey attributeCacheKey, AttributeKvEntry attributeKvEntry) {
|
||||
try {
|
||||
attributesCache.put(attributeCacheKey, attributeKvEntry);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to put element from cache for key {}. Reason - {}.", attributeCacheKey, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void evict(AttributeCacheKey attributeCacheKey) {
|
||||
try {
|
||||
attributesCache.evict(attributeCacheKey);
|
||||
} catch (Exception e) {
|
||||
log.debug("Failed to evict element from cache for key {}. Reason - {}.", attributeCacheKey, e.getMessage());
|
||||
}
|
||||
}
|
||||
void evict(AttributeCacheKey attributeCacheKey);
|
||||
}
|
||||
|
||||
@ -37,9 +37,9 @@ public interface AttributesDao {
|
||||
|
||||
ListenableFuture<List<AttributeKvEntry>> findAll(TenantId tenantId, EntityId entityId, String attributeType);
|
||||
|
||||
ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute);
|
||||
ListenableFuture<String> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute);
|
||||
|
||||
ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List<String> keys);
|
||||
List<ListenableFuture<String>> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List<String> keys);
|
||||
|
||||
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@ -80,17 +80,16 @@ public class BaseAttributesService implements AttributesService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
|
||||
public ListenableFuture<List<String>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
|
||||
validate(entityId, scope);
|
||||
attributes.forEach(attribute -> validate(attribute));
|
||||
|
||||
List<ListenableFuture<Void>> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList());
|
||||
attributes.forEach(AttributeUtils::validate);
|
||||
List<ListenableFuture<String>> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList());
|
||||
return Futures.allAsList(saveFutures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
|
||||
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
|
||||
validate(entityId, scope);
|
||||
return attributesDao.removeAll(tenantId, entityId, scope, attributeKeys);
|
||||
return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, scope, attributeKeys));
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
import org.thingsboard.server.common.data.kv.KvEntry;
|
||||
import org.thingsboard.server.common.stats.DefaultCounter;
|
||||
import org.thingsboard.server.common.stats.StatsFactory;
|
||||
import org.thingsboard.server.dao.cache.CacheExecutorService;
|
||||
@ -88,9 +87,9 @@ public class CachedAttributesService implements AttributesService {
|
||||
|
||||
/**
|
||||
* Will return:
|
||||
* - for the <b>local</b> cache type (cache.type="coffeine"): directExecutor (run callback immediately in the same thread)
|
||||
* - for the <b>remote</b> cache: dedicated thread pool for the cache IO calls to unblock any caller thread
|
||||
* */
|
||||
* - for the <b>local</b> cache type (cache.type="coffeine"): directExecutor (run callback immediately in the same thread)
|
||||
* - for the <b>remote</b> cache: dedicated thread pool for the cache IO calls to unblock any caller thread
|
||||
*/
|
||||
Executor getExecutor(String cacheType, CacheExecutorService cacheExecutorService) {
|
||||
if (StringUtils.isEmpty(cacheType) || LOCAL_CACHE_TYPE.equals(cacheType)) {
|
||||
log.info("Going to use directExecutor for the local cache type {}", cacheType);
|
||||
@ -116,7 +115,7 @@ public class CachedAttributesService implements AttributesService {
|
||||
ListenableFuture<Optional<AttributeKvEntry>> result = attributesDao.find(tenantId, entityId, scope, attributeKey);
|
||||
return Futures.transform(result, foundAttrKvEntry -> {
|
||||
// TODO: think if it's a good idea to store 'empty' attributes
|
||||
cacheWrapper.put(attributeCacheKey, foundAttrKvEntry.orElse(null));
|
||||
cacheWrapper.putIfAbsent(attributeCacheKey, foundAttrKvEntry.orElse(null));
|
||||
return foundAttrKvEntry;
|
||||
}, cacheExecutor);
|
||||
}
|
||||
@ -162,12 +161,12 @@ public class CachedAttributesService implements AttributesService {
|
||||
private List<AttributeKvEntry> mergeDbAndCacheAttributes(EntityId entityId, String scope, List<AttributeKvEntry> cachedAttributes, Set<String> notFoundAttributeKeys, List<AttributeKvEntry> foundInDbAttributes) {
|
||||
for (AttributeKvEntry foundInDbAttribute : foundInDbAttributes) {
|
||||
AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey());
|
||||
cacheWrapper.put(attributeCacheKey, foundInDbAttribute);
|
||||
cacheWrapper.putIfAbsent(attributeCacheKey, foundInDbAttribute);
|
||||
notFoundAttributeKeys.remove(foundInDbAttribute.getKey());
|
||||
}
|
||||
for (String key : notFoundAttributeKeys){
|
||||
cacheWrapper.put(new AttributeCacheKey(scope, entityId, key), null);
|
||||
}
|
||||
// for (String key : notFoundAttributeKeys) {
|
||||
// cacheWrapper.putIfAbsent(new AttributeCacheKey(scope, entityId, key), null);
|
||||
// }
|
||||
List<AttributeKvEntry> mergedAttributes = new ArrayList<>(cachedAttributes);
|
||||
mergedAttributes.addAll(foundInDbAttributes);
|
||||
return mergedAttributes;
|
||||
@ -190,34 +189,30 @@ public class CachedAttributesService implements AttributesService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
|
||||
public ListenableFuture<List<String>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
|
||||
validate(entityId, scope);
|
||||
attributes.forEach(AttributeUtils::validate);
|
||||
|
||||
List<ListenableFuture<Void>> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList());
|
||||
ListenableFuture<List<Void>> future = Futures.allAsList(saveFutures);
|
||||
List<ListenableFuture<String>> futures = new ArrayList<>(attributes.size());
|
||||
for (var attribute : attributes) {
|
||||
ListenableFuture<String> future = attributesDao.save(tenantId, entityId, scope, attribute);
|
||||
futures.add(Futures.transform(future, key -> {
|
||||
cacheWrapper.evict(new AttributeCacheKey(scope, entityId, key));
|
||||
return key;
|
||||
}, cacheExecutor));
|
||||
}
|
||||
|
||||
// TODO: can do if (attributesCache.get() != null) attributesCache.put() instead, but will be more twice more requests to cache
|
||||
List<String> attributeKeys = attributes.stream().map(KvEntry::getKey).collect(Collectors.toList());
|
||||
future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutor);
|
||||
return future;
|
||||
return Futures.allAsList(futures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
|
||||
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
|
||||
validate(entityId, scope);
|
||||
ListenableFuture<List<Void>> future = attributesDao.removeAll(tenantId, entityId, scope, attributeKeys);
|
||||
future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutor);
|
||||
return future;
|
||||
List<ListenableFuture<String>> futures = attributesDao.removeAll(tenantId, entityId, scope, attributeKeys);
|
||||
return Futures.allAsList(futures.stream().map(future -> Futures.transform(future, key -> {
|
||||
cacheWrapper.evict(new AttributeCacheKey(scope, entityId, key));
|
||||
return key;
|
||||
}, cacheExecutor)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private void evictAttributesFromCache(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
|
||||
try {
|
||||
for (String attributeKey : attributeKeys) {
|
||||
cacheWrapper.evict(new AttributeCacheKey(scope, entityId, attributeKey));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[{}][{}] Failed to remove values from cache.", tenantId, entityId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright © 2016-2022 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.dao.attributes;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||
|
||||
import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE;
|
||||
|
||||
@Service
|
||||
@ConditionalOnProperty(prefix = "cache.attributes", value = "enabled", havingValue = "true")
|
||||
@Primary
|
||||
@Slf4j
|
||||
public class DefaultAttributesCacheWrapper implements AttributesCacheWrapper {
|
||||
private final Cache attributesCache;
|
||||
|
||||
public DefaultAttributesCacheWrapper(CacheManager cacheManager) {
|
||||
this.attributesCache = cacheManager.getCache(ATTRIBUTES_CACHE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cache.ValueWrapper get(AttributeCacheKey attributeCacheKey) {
|
||||
var result = attributesCache.get(attributeCacheKey);
|
||||
log.warn("[{}] Get = {}", attributeCacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(AttributeCacheKey attributeCacheKey, AttributeKvEntry attributeKvEntry) {
|
||||
log.warn("[{}] Put = {}", attributeCacheKey, attributeKvEntry);
|
||||
attributesCache.put(attributeCacheKey, attributeKvEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIfAbsent(AttributeCacheKey attributeCacheKey, AttributeKvEntry attributeKvEntry) {
|
||||
var result = attributesCache.putIfAbsent(attributeCacheKey, attributeKvEntry);
|
||||
log.warn("[{}] Put if absent = {}, result = {}", attributeCacheKey, attributeKvEntry, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(AttributeCacheKey attributeCacheKey) {
|
||||
log.warn("[{}] Evict", attributeCacheKey);
|
||||
attributesCache.evict(attributeCacheKey);
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@ -29,6 +29,7 @@ import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.cache.annotation.Caching;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
@ -48,6 +49,8 @@ import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.service.ConstraintValidator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@ -86,11 +89,7 @@ public class BaseRelationService implements RelationService {
|
||||
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}")
|
||||
@Override
|
||||
public EntityRelation getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
|
||||
try {
|
||||
return getRelationAsync(tenantId, from, to, relationType, typeGroup).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return getRelation(tenantId, from, to, relationType, typeGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,6 +107,7 @@ public class BaseRelationService implements RelationService {
|
||||
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
|
||||
})
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean saveRelation(TenantId tenantId, EntityRelation relation) {
|
||||
log.trace("Executing saveRelation [{}]", relation);
|
||||
validate(relation);
|
||||
@ -181,6 +181,7 @@ public class BaseRelationService implements RelationService {
|
||||
public ListenableFuture<Boolean> deleteRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
|
||||
log.trace("Executing deleteRelationAsync [{}][{}][{}][{}]", from, to, relationType, typeGroup);
|
||||
validate(from, to, relationType, typeGroup);
|
||||
//TODO: clear cache using transform
|
||||
return relationDao.deleteRelationAsync(tenantId, from, to, relationType, typeGroup);
|
||||
}
|
||||
|
||||
@ -319,11 +320,8 @@ public class BaseRelationService implements RelationService {
|
||||
public List<EntityRelation> findByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) {
|
||||
validate(from);
|
||||
validateTypeGroup(typeGroup);
|
||||
try {
|
||||
return relationDao.findAllByFromAsync(tenantId, from, typeGroup).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
log.trace("[{}] Find by from: [{}][{}]: ", tenantId, from, typeGroup, new RuntimeException());
|
||||
return relationDao.findAllByFrom(tenantId, from, typeGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -345,7 +343,7 @@ public class BaseRelationService implements RelationService {
|
||||
} else {
|
||||
ListenableFuture<List<EntityRelation>> relationsFuture = relationDao.findAllByFromAsync(tenantId, from, typeGroup);
|
||||
Futures.addCallback(relationsFuture,
|
||||
new FutureCallback<List<EntityRelation>>() {
|
||||
new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable List<EntityRelation> result) {
|
||||
cache.putIfAbsent(fromAndTypeGroup, result);
|
||||
@ -401,11 +399,7 @@ public class BaseRelationService implements RelationService {
|
||||
public List<EntityRelation> findByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup) {
|
||||
validate(to);
|
||||
validateTypeGroup(typeGroup);
|
||||
try {
|
||||
return relationDao.findAllByToAsync(tenantId, to, typeGroup).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return relationDao.findAllByTo(tenantId, to, typeGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@ -66,7 +66,10 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
|
||||
}
|
||||
queue.drainTo(entities, batchSize - 1);
|
||||
boolean fullPack = entities.size() == batchSize;
|
||||
log.debug("[{}] Going to save {} entities", logName, entities.size());
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("[{}] Going to save {} entities", logName, entities.size());
|
||||
log.trace("[{}] Going to save entities: {}", logName, entities);
|
||||
}
|
||||
Stream<E> entitiesStream = entities.stream().map(TbSqlQueueElement::getEntity);
|
||||
saveFunction.accept(
|
||||
(params.isBatchSortEnabled() ? entitiesStream.sorted(batchUpdateComparator) : entitiesStream)
|
||||
|
||||
@ -17,7 +17,9 @@ package org.thingsboard.server.dao.sql;
|
||||
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString(exclude = "future")
|
||||
public final class TbSqlQueueElement<E> {
|
||||
@Getter
|
||||
private final SettableFuture<Void> future;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* 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
|
||||
* 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,
|
||||
@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.attributes;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -39,6 +40,7 @@ import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@ -153,7 +155,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) {
|
||||
public ListenableFuture<String> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) {
|
||||
AttributeKvEntity entity = new AttributeKvEntity();
|
||||
entity.setId(new AttributeKvCompositeKey(entityId.getEntityType(), entityId.getId(), attributeType, attribute.getKey()));
|
||||
entity.setLastUpdateTs(attribute.getLastUpdateTs());
|
||||
@ -165,18 +167,20 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
|
||||
return addToQueue(entity);
|
||||
}
|
||||
|
||||
private ListenableFuture<Void> addToQueue(AttributeKvEntity entity) {
|
||||
return queue.add(entity);
|
||||
private ListenableFuture<String> addToQueue(AttributeKvEntity entity) {
|
||||
return Futures.transform(queue.add(entity), v -> entity.getId().getAttributeKey(), MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List<String> keys) {
|
||||
return service.submit(() -> {
|
||||
keys.forEach(key ->
|
||||
attributeKvRepository.delete(entityId.getEntityType(), entityId.getId(), attributeType, key)
|
||||
);
|
||||
return null;
|
||||
});
|
||||
public List<ListenableFuture<String>> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List<String> keys) {
|
||||
List<ListenableFuture<String>> futuresList = new ArrayList<>(keys.size());
|
||||
for (String key : keys) {
|
||||
futuresList.add(service.submit(() -> {
|
||||
attributeKvRepository.delete(entityId.getEntityType(), entityId.getId(), attributeType, key);
|
||||
return key;
|
||||
}));
|
||||
}
|
||||
return futuresList;
|
||||
}
|
||||
|
||||
private AttributeKvCompositeKey getAttributeKvCompositeKey(EntityId entityId, String attributeType, String attributeKey) {
|
||||
|
||||
@ -339,12 +339,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
List<Long> highTemperatures = new ArrayList<>();
|
||||
createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
|
||||
|
||||
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
|
||||
List<ListenableFuture<List<String>>> attributeFutures = new ArrayList<>();
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
Device device = devices.get(i);
|
||||
attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE));
|
||||
}
|
||||
Futures.successfulAsList(attributeFutures).get();
|
||||
Futures.allAsList(attributeFutures).get();
|
||||
|
||||
RelationsQueryFilter filter = new RelationsQueryFilter();
|
||||
filter.setRootEntity(tenantId);
|
||||
@ -518,12 +518,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
List<Long> highTemperatures = new ArrayList<>();
|
||||
createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
|
||||
|
||||
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
|
||||
List<ListenableFuture<List<String>>> attributeFutures = new ArrayList<>();
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
Device device = devices.get(i);
|
||||
attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE));
|
||||
}
|
||||
Futures.successfulAsList(attributeFutures).get();
|
||||
Futures.allAsList(attributeFutures).get();
|
||||
|
||||
DeviceSearchQueryFilter filter = new DeviceSearchQueryFilter();
|
||||
filter.setRootEntity(tenantId);
|
||||
@ -593,12 +593,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
List<Long> highConsumptions = new ArrayList<>();
|
||||
createTestHierarchy(tenantId, assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>());
|
||||
|
||||
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
|
||||
List<ListenableFuture<List<String>>> attributeFutures = new ArrayList<>();
|
||||
for (int i = 0; i < assets.size(); i++) {
|
||||
Asset asset = assets.get(i);
|
||||
attributeFutures.add(saveLongAttribute(asset.getId(), "consumption", consumptions.get(i), DataConstants.SERVER_SCOPE));
|
||||
}
|
||||
Futures.successfulAsList(attributeFutures).get();
|
||||
Futures.allAsList(attributeFutures).get();
|
||||
|
||||
AssetSearchQueryFilter filter = new AssetSearchQueryFilter();
|
||||
filter.setRootEntity(tenantId);
|
||||
@ -1048,14 +1048,14 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
|
||||
List<ListenableFuture<List<String>>> attributeFutures = new ArrayList<>();
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
Device device = devices.get(i);
|
||||
for (String currentScope : DataConstants.allScopes()) {
|
||||
attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), currentScope));
|
||||
}
|
||||
}
|
||||
Futures.successfulAsList(attributeFutures).get();
|
||||
Futures.allAsList(attributeFutures).get();
|
||||
|
||||
DeviceTypeFilter filter = new DeviceTypeFilter();
|
||||
filter.setDeviceType("default");
|
||||
@ -1150,12 +1150,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
|
||||
List<ListenableFuture<List<String>>> attributeFutures = new ArrayList<>();
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
Device device = devices.get(i);
|
||||
attributeFutures.add(saveLongAttribute(device.getId(), "temperature", temperatures.get(i), DataConstants.CLIENT_SCOPE));
|
||||
}
|
||||
Futures.successfulAsList(attributeFutures).get();
|
||||
Futures.allAsList(attributeFutures).get();
|
||||
|
||||
DeviceTypeFilter filter = new DeviceTypeFilter();
|
||||
filter.setDeviceType("default");
|
||||
@ -1301,7 +1301,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
Device device = devices.get(i);
|
||||
timeseriesFutures.add(saveLongTimeseries(device.getId(), "temperature", temperatures.get(i)));
|
||||
}
|
||||
Futures.successfulAsList(timeseriesFutures).get();
|
||||
Futures.allAsList(timeseriesFutures).get();
|
||||
|
||||
DeviceTypeFilter filter = new DeviceTypeFilter();
|
||||
filter.setDeviceType("default");
|
||||
@ -1428,12 +1428,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
}
|
||||
}
|
||||
|
||||
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
|
||||
List<ListenableFuture<List<String>>> attributeFutures = new ArrayList<>();
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
Device device = devices.get(i);
|
||||
attributeFutures.add(saveStringAttribute(device.getId(), "attributeString", attributeStrings.get(i), DataConstants.CLIENT_SCOPE));
|
||||
}
|
||||
Futures.successfulAsList(attributeFutures).get();
|
||||
Futures.allAsList(attributeFutures).get();
|
||||
|
||||
DeviceTypeFilter filter = new DeviceTypeFilter();
|
||||
filter.setDeviceType("default");
|
||||
@ -1764,13 +1764,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
|
||||
return filter;
|
||||
}
|
||||
|
||||
private ListenableFuture<List<Void>> saveLongAttribute(EntityId entityId, String key, long value, String scope) {
|
||||
private ListenableFuture<List<String>> saveLongAttribute(EntityId entityId, String key, long value, String scope) {
|
||||
KvEntry attrValue = new LongDataEntry(key, value);
|
||||
AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L);
|
||||
return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr));
|
||||
}
|
||||
|
||||
private ListenableFuture<List<Void>> saveStringAttribute(EntityId entityId, String key, String value, String scope) {
|
||||
private ListenableFuture<List<String>> saveStringAttribute(EntityId entityId, String key, String value, String scope) {
|
||||
KvEntry attrValue = new StringDataEntry(key, value);
|
||||
AttributeKvEntry attr = new BaseAttributeKvEntry(attrValue, 42L);
|
||||
return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr));
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.thingsboard.rule.engine.metadata;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.json.JsonWriteFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@ -24,6 +23,7 @@ import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.gson.JsonParseException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.thingsboard.rule.engine.api.TbContext;
|
||||
@ -50,6 +50,7 @@ import static org.thingsboard.server.common.data.DataConstants.LATEST_TS;
|
||||
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
|
||||
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
|
||||
|
||||
@Slf4j
|
||||
public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeConfiguration, T extends EntityId> implements TbNode {
|
||||
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
@ -112,6 +113,7 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
|
||||
}
|
||||
ListenableFuture<List<AttributeKvEntry>> attributeKvEntryListFuture = ctx.getAttributesService().find(ctx.getTenantId(), entityId, scope, keys);
|
||||
return Futures.transform(attributeKvEntryListFuture, attributeKvEntryList -> {
|
||||
log.warn("[{}][{}][{}][{}] Lookup attribute result: {}", ctx.getTenantId(), entityId, scope, keys, attributeKvEntryList);
|
||||
if (!CollectionUtils.isEmpty(attributeKvEntryList)) {
|
||||
List<AttributeKvEntry> existingAttributesKvEntry = attributeKvEntryList.stream().filter(attributeKvEntry -> keys.contains(attributeKvEntry.getKey())).collect(Collectors.toList());
|
||||
existingAttributesKvEntry.forEach(kvEntry -> msg.getMetaData().putValue(prefix + kvEntry.getKey(), kvEntry.getValueAsString()));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user