24 changed files with 408 additions and 351 deletions
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.service.cf; |
||||
|
|
||||
|
import com.google.common.util.concurrent.FutureCallback; |
||||
|
import org.thingsboard.rule.engine.api.AttributesSaveRequest; |
||||
|
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; |
||||
|
import org.thingsboard.server.common.data.kv.TimeseriesSaveResult; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public interface CalculatedFieldQueueService { |
||||
|
|
||||
|
/** |
||||
|
* Filter CFs based on the request entity. Push to the queue if any matching CF exist; |
||||
|
* |
||||
|
* @param request - telemetry save request; |
||||
|
* @param callback |
||||
|
*/ |
||||
|
void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result, FutureCallback<Void> callback); |
||||
|
|
||||
|
void pushRequestToQueue(AttributesSaveRequest request, List<Long> result, FutureCallback<Void> callback); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,238 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.service.cf; |
||||
|
|
||||
|
import com.google.common.util.concurrent.FutureCallback; |
||||
|
import lombok.Getter; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.rule.engine.api.AttributesSaveRequest; |
||||
|
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest; |
||||
|
import org.thingsboard.server.cluster.TbClusterService; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedFieldLink; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
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.TimeseriesSaveResult; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.common.data.msg.TbMsgType; |
||||
|
import org.thingsboard.server.common.util.ProtoUtils; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.AttributeScopeProto; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.AttributeValueProto; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldIdProto; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; |
||||
|
import org.thingsboard.server.queue.TbQueueCallback; |
||||
|
import org.thingsboard.server.queue.TbQueueMsgMetadata; |
||||
|
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx; |
||||
|
import org.thingsboard.server.service.profile.TbAssetProfileCache; |
||||
|
import org.thingsboard.server.service.profile.TbDeviceProfileCache; |
||||
|
|
||||
|
import java.util.EnumSet; |
||||
|
import java.util.List; |
||||
|
import java.util.Set; |
||||
|
import java.util.UUID; |
||||
|
import java.util.function.Predicate; |
||||
|
import java.util.function.Supplier; |
||||
|
|
||||
|
import static org.thingsboard.server.common.util.ProtoUtils.toTsKvProto; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
@RequiredArgsConstructor |
||||
|
public class DefaultCalculatedFieldQueueService implements CalculatedFieldQueueService { |
||||
|
|
||||
|
public static final TbQueueCallback DUMMY_TB_QUEUE_CALLBACK = new TbQueueCallback() { |
||||
|
@Override |
||||
|
public void onSuccess(TbQueueMsgMetadata metadata) { |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable t) { |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
private final TbAssetProfileCache assetProfileCache; |
||||
|
private final TbDeviceProfileCache deviceProfileCache; |
||||
|
private final CalculatedFieldCache calculatedFieldCache; |
||||
|
private final TbClusterService clusterService; |
||||
|
|
||||
|
private static final Set<EntityType> supportedReferencedEntities = EnumSet.of( |
||||
|
EntityType.DEVICE, EntityType.ASSET, EntityType.CUSTOMER, EntityType.TENANT |
||||
|
); |
||||
|
|
||||
|
@Value("${calculatedField.initFetchPackSize:50000}") |
||||
|
@Getter |
||||
|
private int initFetchPackSize; |
||||
|
|
||||
|
@Override |
||||
|
public void pushRequestToQueue(TimeseriesSaveRequest request, TimeseriesSaveResult result, FutureCallback<Void> callback) { |
||||
|
var tenantId = request.getTenantId(); |
||||
|
var entityId = request.getEntityId(); |
||||
|
//TODO: 1. check that request entity has calculated fields for entity or profile. If yes - push to corresponding partitions;
|
||||
|
//TODO: 2. check that request entity has calculated field links. If yes - push to corresponding partitions;
|
||||
|
//TODO: in 1 and 2 we should do the check as quick as possible. Should we also check the field/link keys?;
|
||||
|
checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matches(request.getEntries()), cf -> cf.linkMatches(entityId, request.getEntries()), |
||||
|
() -> toCalculatedFieldTelemetryMsgProto(request, result), callback); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void pushRequestToQueue(AttributesSaveRequest request, List<Long> result, FutureCallback<Void> callback) { |
||||
|
var tenantId = request.getTenantId(); |
||||
|
var entityId = request.getEntityId(); |
||||
|
checkEntityAndPushToQueue(tenantId, entityId, cf -> cf.matches(request.getEntries(), request.getScope()), cf -> cf.linkMatches(entityId, request.getEntries(), request.getScope()), |
||||
|
() -> toCalculatedFieldTelemetryMsgProto(request, result), callback); |
||||
|
} |
||||
|
|
||||
|
private void checkEntityAndPushToQueue(TenantId tenantId, EntityId entityId, |
||||
|
Predicate<CalculatedFieldCtx> mainEntityFilter, Predicate<CalculatedFieldCtx> linkedEntityFilter, |
||||
|
Supplier<ToCalculatedFieldMsg> msg, FutureCallback<Void> callback) { |
||||
|
boolean send = checkEntityForCalculatedFields(tenantId, entityId, mainEntityFilter, linkedEntityFilter); |
||||
|
if (send) { |
||||
|
clusterService.pushMsgToCalculatedFields(tenantId, entityId, msg.get(), wrap(callback)); |
||||
|
} else { |
||||
|
if (callback != null) { |
||||
|
callback.onSuccess(null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean checkEntityForCalculatedFields(TenantId tenantId, EntityId entityId, Predicate<CalculatedFieldCtx> filter, Predicate<CalculatedFieldCtx> linkedEntityFilter) { |
||||
|
boolean send = false; |
||||
|
if (supportedReferencedEntities.contains(entityId.getEntityType())) { |
||||
|
send = calculatedFieldCache.getCalculatedFieldCtxsByEntityId(entityId).stream().anyMatch(filter); |
||||
|
if (!send) { |
||||
|
send = calculatedFieldCache.getCalculatedFieldCtxsByEntityId(getProfileId(tenantId, entityId)).stream().anyMatch(filter); |
||||
|
} |
||||
|
if (!send) { |
||||
|
send = calculatedFieldCache.getCalculatedFieldLinksByEntityId(entityId).stream() |
||||
|
.map(CalculatedFieldLink::getCalculatedFieldId) |
||||
|
.map(calculatedFieldCache::getCalculatedFieldCtx) |
||||
|
.anyMatch(linkedEntityFilter); |
||||
|
} |
||||
|
} |
||||
|
return send; |
||||
|
} |
||||
|
|
||||
|
private EntityId getProfileId(TenantId tenantId, EntityId entityId) { |
||||
|
return switch (entityId.getEntityType()) { |
||||
|
case ASSET -> assetProfileCache.get(tenantId, (AssetId) entityId).getId(); |
||||
|
case DEVICE -> deviceProfileCache.get(tenantId, (DeviceId) entityId).getId(); |
||||
|
default -> null; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private ToCalculatedFieldMsg toCalculatedFieldTelemetryMsgProto(TimeseriesSaveRequest request, TimeseriesSaveResult result) { |
||||
|
ToCalculatedFieldMsg.Builder msg = ToCalculatedFieldMsg.newBuilder(); |
||||
|
|
||||
|
CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = buildTelemetryMsgProto(request.getTenantId(), request.getEntityId(), request.getPreviousCalculatedFieldIds(), request.getTbMsgId(), request.getTbMsgType()); |
||||
|
List<TsKvEntry> entries = request.getEntries(); |
||||
|
List<Long> versions = result.getVersions(); |
||||
|
for (int i = 0; i < entries.size(); i++) { |
||||
|
long tsVersion = versions.get(i); |
||||
|
TsKvProto tsProto = toTsKvProto(entries.get(i)).toBuilder().setVersion(tsVersion).build(); |
||||
|
telemetryMsg.addTsData(tsProto); |
||||
|
} |
||||
|
msg.setTelemetryMsg(telemetryMsg.build()); |
||||
|
|
||||
|
return msg.build(); |
||||
|
} |
||||
|
|
||||
|
private ToCalculatedFieldMsg toCalculatedFieldTelemetryMsgProto(AttributesSaveRequest request, List<Long> versions) { |
||||
|
ToCalculatedFieldMsg.Builder msg = ToCalculatedFieldMsg.newBuilder(); |
||||
|
|
||||
|
CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = buildTelemetryMsgProto(request.getTenantId(), request.getEntityId(), request.getPreviousCalculatedFieldIds(), request.getTbMsgId(), request.getTbMsgType()); |
||||
|
telemetryMsg.setScope(AttributeScopeProto.valueOf(request.getScope().name())); |
||||
|
List<AttributeKvEntry> entries = request.getEntries(); |
||||
|
for (int i = 0; i < entries.size(); i++) { |
||||
|
long attrVersion = versions.get(i); |
||||
|
AttributeValueProto attrProto = ProtoUtils.toProto(entries.get(i)).toBuilder().setVersion(attrVersion).build(); |
||||
|
telemetryMsg.addAttrData(attrProto); |
||||
|
} |
||||
|
msg.setTelemetryMsg(telemetryMsg.build()); |
||||
|
|
||||
|
return msg.build(); |
||||
|
} |
||||
|
|
||||
|
private CalculatedFieldTelemetryMsgProto.Builder buildTelemetryMsgProto(TenantId tenantId, EntityId entityId, List<CalculatedFieldId> calculatedFieldIds, UUID tbMsgId, TbMsgType tbMsgType) { |
||||
|
CalculatedFieldTelemetryMsgProto.Builder telemetryMsg = CalculatedFieldTelemetryMsgProto.newBuilder(); |
||||
|
|
||||
|
telemetryMsg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); |
||||
|
telemetryMsg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); |
||||
|
|
||||
|
telemetryMsg.setEntityType(entityId.getEntityType().name()); |
||||
|
telemetryMsg.setEntityIdMSB(entityId.getId().getMostSignificantBits()); |
||||
|
telemetryMsg.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); |
||||
|
|
||||
|
if (calculatedFieldIds != null) { |
||||
|
for (CalculatedFieldId cfId : calculatedFieldIds) { |
||||
|
telemetryMsg.addPreviousCalculatedFields(toProto(cfId)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (tbMsgId != null) { |
||||
|
telemetryMsg.setTbMsgIdMSB(tbMsgId.getMostSignificantBits()); |
||||
|
telemetryMsg.setTbMsgIdLSB(tbMsgId.getLeastSignificantBits()); |
||||
|
} |
||||
|
|
||||
|
if (tbMsgType != null) { |
||||
|
telemetryMsg.setTbMsgType(tbMsgType.name()); |
||||
|
} |
||||
|
|
||||
|
return telemetryMsg; |
||||
|
} |
||||
|
|
||||
|
private CalculatedFieldIdProto toProto(CalculatedFieldId cfId) { |
||||
|
return CalculatedFieldIdProto.newBuilder() |
||||
|
.setCalculatedFieldIdMSB(cfId.getId().getMostSignificantBits()) |
||||
|
.setCalculatedFieldIdLSB(cfId.getId().getLeastSignificantBits()) |
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
private static TbQueueCallback wrap(FutureCallback<Void> callback) { |
||||
|
if (callback != null) { |
||||
|
return new FutureCallbackWrapper(callback); |
||||
|
} else { |
||||
|
return DUMMY_TB_QUEUE_CALLBACK; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class FutureCallbackWrapper implements TbQueueCallback { |
||||
|
private final FutureCallback<Void> callback; |
||||
|
|
||||
|
public FutureCallbackWrapper(FutureCallback<Void> callback) { |
||||
|
this.callback = callback; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onSuccess(TbQueueMsgMetadata metadata) { |
||||
|
callback.onSuccess(null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable t) { |
||||
|
callback.onFailure(t); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 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.msg.cf; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.msg.MsgType; |
||||
|
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg; |
||||
|
|
||||
|
import java.util.Set; |
||||
|
|
||||
|
@Data |
||||
|
public class CalculatedFieldPartitionChangeMsg implements ToCalculatedFieldSystemMsg { |
||||
|
|
||||
|
private final boolean[] partitions; |
||||
|
|
||||
|
@Override |
||||
|
public TenantId getTenantId() { |
||||
|
return TenantId.SYS_TENANT_ID; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public MsgType getMsgType() { |
||||
|
return MsgType.CF_PARTITIONS_CHANGE_MSG; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue