Merge branch 'master' into feature/jdk11
This commit is contained in:
		
						commit
						2bd28cb021
					
				@ -203,6 +203,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
 | 
			
		||||
        } catch (Exception t) {
 | 
			
		||||
            ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
 | 
			
		||||
            if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
 | 
			
		||||
                // remove device from cache in case null value cached in the distributed redis.
 | 
			
		||||
                removeDeviceFromCache(device.getTenantId(), device.getName());
 | 
			
		||||
                throw new DataValidationException("Device with such name already exists!");
 | 
			
		||||
            } else {
 | 
			
		||||
                throw t;
 | 
			
		||||
@ -281,15 +283,19 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
 | 
			
		||||
        }
 | 
			
		||||
        deleteEntityRelations(tenantId, deviceId);
 | 
			
		||||
 | 
			
		||||
        List<Object> list = new ArrayList<>();
 | 
			
		||||
        list.add(device.getTenantId());
 | 
			
		||||
        list.add(device.getName());
 | 
			
		||||
        Cache cache = cacheManager.getCache(DEVICE_CACHE);
 | 
			
		||||
        cache.evict(list);
 | 
			
		||||
        removeDeviceFromCache(tenantId, device.getName());
 | 
			
		||||
 | 
			
		||||
        deviceDao.removeById(tenantId, deviceId.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void removeDeviceFromCache(TenantId tenantId, String name) {
 | 
			
		||||
        List<Object> list = new ArrayList<>();
 | 
			
		||||
        list.add(tenantId);
 | 
			
		||||
        list.add(name);
 | 
			
		||||
        Cache cache = cacheManager.getCache(DEVICE_CACHE);
 | 
			
		||||
        cache.evict(list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink) {
 | 
			
		||||
        log.trace("Executing findDevicesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,175 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2021 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.rule.engine.metadata;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
			
		||||
import com.google.common.util.concurrent.Futures;
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.thingsboard.common.util.DonAsynchron;
 | 
			
		||||
import org.thingsboard.rule.engine.api.RuleNode;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbContext;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbNode;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbNodeException;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbRelationTypes;
 | 
			
		||||
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
 | 
			
		||||
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.plugin.ComponentType;
 | 
			
		||||
import org.thingsboard.server.common.msg.TbMsg;
 | 
			
		||||
import org.thingsboard.server.dao.timeseries.TimeseriesService;
 | 
			
		||||
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
import java.math.RoundingMode;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(type = ComponentType.ENRICHMENT,
 | 
			
		||||
        name = "calculate delta",
 | 
			
		||||
        configClazz = CalculateDeltaNodeConfiguration.class,
 | 
			
		||||
        nodeDescription = "Calculates and adds 'delta' value into message based on the incoming and previous value",
 | 
			
		||||
        nodeDetails = "Calculates delta and period based on the previous time-series reading and current data. " +
 | 
			
		||||
                "Delta calculation is done in scope of the message originator, e.g. device, asset or customer.",
 | 
			
		||||
        uiResources = {"static/rulenode/rulenode-core-config.js"},
 | 
			
		||||
        configDirective = "tbEnrichmentNodeCalculateDeltaConfig")
 | 
			
		||||
public class CalculateDeltaNode implements TbNode {
 | 
			
		||||
    private Map<EntityId, ValueWithTs> cache;
 | 
			
		||||
    private CalculateDeltaNodeConfiguration config;
 | 
			
		||||
    private TbContext ctx;
 | 
			
		||||
    private TimeseriesService timeseriesService;
 | 
			
		||||
    private boolean useCache;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
 | 
			
		||||
        this.config = TbNodeUtils.convert(configuration, CalculateDeltaNodeConfiguration.class);
 | 
			
		||||
        this.ctx = ctx;
 | 
			
		||||
        this.timeseriesService = ctx.getTimeseriesService();
 | 
			
		||||
        this.useCache = config.isUseCache();
 | 
			
		||||
 | 
			
		||||
        if (useCache) {
 | 
			
		||||
            cache = new ConcurrentHashMap<>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onMsg(TbContext ctx, TbMsg msg) {
 | 
			
		||||
        JsonNode json = JacksonUtil.toJsonNode(msg.getData());
 | 
			
		||||
        String inputKey = config.getInputValueKey();
 | 
			
		||||
        if (json.has(inputKey)) {
 | 
			
		||||
            DonAsynchron.withCallback(getLastValue(msg.getOriginator()),
 | 
			
		||||
                    previousData -> {
 | 
			
		||||
                        double currentValue = json.get(inputKey).asDouble();
 | 
			
		||||
                        long currentTs = TbMsgTimeseriesNode.getTs(msg);
 | 
			
		||||
 | 
			
		||||
                        if (useCache) {
 | 
			
		||||
                            cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue));
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        BigDecimal delta = BigDecimal.valueOf(previousData != null ? currentValue - previousData.value : 0.0);
 | 
			
		||||
 | 
			
		||||
                        if (config.isTellFailureIfDeltaIsNegative() && delta.doubleValue() < 0) {
 | 
			
		||||
                            ctx.tellNext(msg, TbRelationTypes.FAILURE);
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (config.getRound() != null) {
 | 
			
		||||
                            delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        ObjectNode result = (ObjectNode) json;
 | 
			
		||||
                        result.put(config.getOutputValueKey(), delta);
 | 
			
		||||
 | 
			
		||||
                        if (config.isAddPeriodBetweenMsgs()) {
 | 
			
		||||
                            long period = previousData != null ? currentTs - previousData.ts : 0;
 | 
			
		||||
                            result.put(config.getPeriodValueKey(), period);
 | 
			
		||||
                        }
 | 
			
		||||
                        ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result)));
 | 
			
		||||
                    },
 | 
			
		||||
                    t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
 | 
			
		||||
        } else if (config.isTellFailureIfInputValueKeyIsAbsent()) {
 | 
			
		||||
            ctx.tellNext(msg, TbRelationTypes.FAILURE);
 | 
			
		||||
        } else {
 | 
			
		||||
            ctx.tellSuccess(msg);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void destroy() {
 | 
			
		||||
        if (useCache) {
 | 
			
		||||
            cache.clear();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ListenableFuture<ValueWithTs> fetchLatestValue(EntityId entityId) {
 | 
			
		||||
        return Futures.transform(timeseriesService.findLatest(ctx.getTenantId(), entityId, Collections.singletonList(config.getInputValueKey())),
 | 
			
		||||
                list -> extractValue(list.get(0))
 | 
			
		||||
                , ctx.getDbCallbackExecutor());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ListenableFuture<ValueWithTs> getLastValue(EntityId entityId) {
 | 
			
		||||
        ValueWithTs latestValue;
 | 
			
		||||
        if (useCache && (latestValue = cache.get(entityId)) != null) {
 | 
			
		||||
            return Futures.immediateFuture(latestValue);
 | 
			
		||||
        } else {
 | 
			
		||||
            return fetchLatestValue(entityId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ValueWithTs extractValue(TsKvEntry kvEntry) {
 | 
			
		||||
        if (kvEntry == null || kvEntry.getValue() == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        double result = 0.0;
 | 
			
		||||
        long ts = kvEntry.getTs();
 | 
			
		||||
        switch (kvEntry.getDataType()) {
 | 
			
		||||
            case LONG:
 | 
			
		||||
                result = kvEntry.getLongValue().get();
 | 
			
		||||
                break;
 | 
			
		||||
            case DOUBLE:
 | 
			
		||||
                result = kvEntry.getDoubleValue().get();
 | 
			
		||||
                break;
 | 
			
		||||
            case STRING:
 | 
			
		||||
                try {
 | 
			
		||||
                    result = Double.parseDouble(kvEntry.getStrValue().get());
 | 
			
		||||
                } catch (NumberFormatException e) {
 | 
			
		||||
                    throw new IllegalArgumentException("Calculation failed. Unable to parse value [" + kvEntry.getStrValue().get() + "]" +
 | 
			
		||||
                            " of telemetry [" + kvEntry.getKey() + "] to Double");
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case BOOLEAN:
 | 
			
		||||
                throw new IllegalArgumentException("Calculation failed. Boolean values are not supported!");
 | 
			
		||||
            case JSON:
 | 
			
		||||
                throw new IllegalArgumentException("Calculation failed. JSON values are not supported!");
 | 
			
		||||
        }
 | 
			
		||||
        return new ValueWithTs(ts, result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class ValueWithTs {
 | 
			
		||||
        private final long ts;
 | 
			
		||||
        private final double value;
 | 
			
		||||
 | 
			
		||||
        private ValueWithTs(long ts, double value) {
 | 
			
		||||
            this.ts = ts;
 | 
			
		||||
            this.value = value;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2021 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.rule.engine.metadata;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.thingsboard.rule.engine.api.NodeConfiguration;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class CalculateDeltaNodeConfiguration implements NodeConfiguration<CalculateDeltaNodeConfiguration> {
 | 
			
		||||
    private String inputValueKey;
 | 
			
		||||
    private String outputValueKey;
 | 
			
		||||
    private boolean useCache;
 | 
			
		||||
    private boolean addPeriodBetweenMsgs;
 | 
			
		||||
    private String periodValueKey;
 | 
			
		||||
    private Integer round;
 | 
			
		||||
    private boolean tellFailureIfInputValueKeyIsAbsent;
 | 
			
		||||
    private boolean tellFailureIfDeltaIsNegative;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CalculateDeltaNodeConfiguration defaultConfiguration() {
 | 
			
		||||
        CalculateDeltaNodeConfiguration configuration = new CalculateDeltaNodeConfiguration();
 | 
			
		||||
        configuration.setInputValueKey("pulseCounter");
 | 
			
		||||
        configuration.setOutputValueKey("delta");
 | 
			
		||||
        configuration.setUseCache(true);
 | 
			
		||||
        configuration.setAddPeriodBetweenMsgs(false);
 | 
			
		||||
        configuration.setPeriodValueKey("periodInMs");
 | 
			
		||||
        configuration.setTellFailureIfInputValueKeyIsAbsent(true);
 | 
			
		||||
        configuration.setTellFailureIfDeltaIsNegative(true);
 | 
			
		||||
        return configuration;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -223,7 +223,11 @@ class AlarmState {
 | 
			
		||||
            currentAlarm.setType(alarmDefinition.getAlarmType());
 | 
			
		||||
            currentAlarm.setStatus(AlarmStatus.ACTIVE_UNACK);
 | 
			
		||||
            currentAlarm.setSeverity(severity);
 | 
			
		||||
            currentAlarm.setStartTs(System.currentTimeMillis());
 | 
			
		||||
            long startTs = dataSnapshot.getTs();
 | 
			
		||||
            if (startTs == 0L) {
 | 
			
		||||
                startTs = System.currentTimeMillis();
 | 
			
		||||
            }
 | 
			
		||||
            currentAlarm.setStartTs(startTs);
 | 
			
		||||
            currentAlarm.setEndTs(currentAlarm.getStartTs());
 | 
			
		||||
            currentAlarm.setDetails(createDetails(ruleState));
 | 
			
		||||
            currentAlarm.setOriginator(originator);
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user