BaseRuleService.java

240 lines | 9.957 kB Blame History Raw Download
/**
 * Copyright © 2016-2018 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.rule;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DatabaseException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;

@Service
@Slf4j
public class BaseRuleService extends AbstractEntityService implements RuleService {

    private final TenantId systemTenantId = new TenantId(NULL_UUID);

    @Autowired
    public RuleDao ruleDao;

    @Autowired
    public PluginService pluginService;

    @Autowired
    private ComponentDescriptorService componentDescriptorService;

    @Override
    public RuleMetaData saveRule(RuleMetaData rule) {
        throw new RuntimeException("Not supported since v1.5!");
    }

    private void validateRuleAndPluginState(RuleMetaData rule) {
        if (org.springframework.util.StringUtils.isEmpty(rule.getPluginToken())) {
            return;
        }
        PluginMetaData pluginMd = pluginService.findPluginByApiToken(rule.getPluginToken());
        if (pluginMd == null) {
            throw new IncorrectParameterException("Rule points to non-existent plugin!");
        }
        if (!pluginMd.getTenantId().equals(systemTenantId) && !pluginMd.getTenantId().equals(rule.getTenantId())) {
            throw new IncorrectParameterException("Rule access plugin that belongs to different tenant!");
        }
        if (rule.getState() == ComponentLifecycleState.ACTIVE && pluginMd.getState() != ComponentLifecycleState.ACTIVE) {
            throw new IncorrectParameterException("Can't save active rule that points to inactive plugin!");
        }
        ComponentDescriptor pluginDescriptor = componentDescriptorService.findByClazz(pluginMd.getClazz());
        String actionClazz = getIfValid(ComponentType.ACTION.name(), rule.getAction(), "clazz", JsonNode::isTextual, JsonNode::asText);
        if (!Arrays.asList(pluginDescriptor.getActions().split(",")).contains(actionClazz)) {
            throw new IncorrectParameterException("Rule's action is not supported by plugin with token " + rule.getPluginToken() + "!");
        }
    }

    private static <T> T getIfValid(String parentName, JsonNode node, String name, Function<JsonNode, Boolean> validator, Function<JsonNode, T> extractor) {
        if (!node.has(name)) {
            throw new IncorrectParameterException(parentName + "'s " + name + " is not set!");
        } else {
            JsonNode value = node.get(name);
            if (validator.apply(value)) {
                return extractor.apply(value);
            } else {
                throw new IncorrectParameterException(parentName + "'s " + name + " is not valid!");
            }
        }
    }

    @Override
    public RuleMetaData findRuleById(RuleId ruleId) {
        validateId(ruleId, "Incorrect rule id for search rule request.");
        return ruleDao.findById(ruleId.getId());
    }

    @Override
    public ListenableFuture<RuleMetaData> findRuleByIdAsync(RuleId ruleId) {
        validateId(ruleId, "Incorrect rule id for search rule request.");
        return ruleDao.findByIdAsync(ruleId.getId());
    }

    @Override
    public List<RuleMetaData> findPluginRules(String pluginToken) {
        return ruleDao.findRulesByPlugin(pluginToken);
    }

    @Override
    public TextPageData<RuleMetaData> findSystemRules(TextPageLink pageLink) {
        validatePageLink(pageLink, "Incorrect PageLink object for search rule request.");
        List<RuleMetaData> rules = ruleDao.findByTenantIdAndPageLink(systemTenantId, pageLink);
        return new TextPageData<>(rules, pageLink);
    }

    @Override
    public TextPageData<RuleMetaData> findTenantRules(TenantId tenantId, TextPageLink pageLink) {
        validateId(tenantId, "Incorrect tenant id for search rule request.");
        validatePageLink(pageLink, "Incorrect PageLink object for search rule request.");
        List<RuleMetaData> rules = ruleDao.findByTenantIdAndPageLink(tenantId, pageLink);
        return new TextPageData<>(rules, pageLink);
    }

    @Override
    public List<RuleMetaData> findSystemRules() {
        log.trace("Executing findSystemRules");
        List<RuleMetaData> rules = new ArrayList<>();
        TextPageLink pageLink = new TextPageLink(300);
        TextPageData<RuleMetaData> pageData = null;
        do {
            pageData = findSystemRules(pageLink);
            rules.addAll(pageData.getData());
            if (pageData.hasNext()) {
                pageLink = pageData.getNextPageLink();
            }
        } while (pageData.hasNext());
        return rules;
    }

    @Override
    public TextPageData<RuleMetaData> findAllTenantRulesByTenantIdAndPageLink(TenantId tenantId, TextPageLink pageLink) {
        log.trace("Executing findAllTenantRulesByTenantIdAndPageLink, tenantId [{}], pageLink [{}]", tenantId, pageLink);
        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
        Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
        List<RuleMetaData> rules = ruleDao.findAllTenantRulesByTenantId(tenantId.getId(), pageLink);
        return new TextPageData<>(rules, pageLink);
    }

    @Override
    public List<RuleMetaData> findAllTenantRulesByTenantId(TenantId tenantId) {
        log.trace("Executing findAllTenantRulesByTenantId, tenantId [{}]", tenantId);
        Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
        List<RuleMetaData> rules = new ArrayList<>();
        TextPageLink pageLink = new TextPageLink(300);
        TextPageData<RuleMetaData> pageData = null;
        do {
            pageData = findAllTenantRulesByTenantIdAndPageLink(tenantId, pageLink);
            rules.addAll(pageData.getData());
            if (pageData.hasNext()) {
                pageLink = pageData.getNextPageLink();
            }
        } while (pageData.hasNext());
        return rules;
    }

    @Override
    public void deleteRuleById(RuleId ruleId) {
        validateId(ruleId, "Incorrect rule id for delete rule request.");
        deleteEntityRelations(ruleId);
        ruleDao.deleteById(ruleId);
    }

    @Override
    public void activateRuleById(RuleId ruleId) {
        updateLifeCycleState(ruleId, ComponentLifecycleState.ACTIVE);
    }

    @Override
    public void suspendRuleById(RuleId ruleId) {
        updateLifeCycleState(ruleId, ComponentLifecycleState.SUSPENDED);
    }

    private void updateLifeCycleState(RuleId ruleId, ComponentLifecycleState state) {
        Validator.validateId(ruleId, "Incorrect rule id for state change request.");
        RuleMetaData rule = ruleDao.findById(ruleId);
        if (rule != null) {
            rule.setState(state);
            validateRuleAndPluginState(rule);
            ruleDao.save(rule);
        } else {
            throw new DatabaseException("Plugin not found!");
        }
    }

    @Override
    public void deleteRulesByTenantId(TenantId tenantId) {
        validateId(tenantId, "Incorrect tenant id for delete rules request.");
        tenantRulesRemover.removeEntities(tenantId);
    }

    private DataValidator<RuleMetaData> ruleValidator =
            new DataValidator<RuleMetaData>() {
                @Override
                protected void validateDataImpl(RuleMetaData rule) {
                    if (StringUtils.isEmpty(rule.getName())) {
                        throw new DataValidationException("Rule name should be specified!.");
                    }
                }
            };

    private PaginatedRemover<TenantId, RuleMetaData> tenantRulesRemover =
            new PaginatedRemover<TenantId, RuleMetaData>() {

                @Override
                protected List<RuleMetaData> findEntities(TenantId id, TextPageLink pageLink) {
                    return ruleDao.findByTenantIdAndPageLink(id, pageLink);
                }

                @Override
                protected void removeEntity(RuleMetaData entity) {
                    ruleDao.deleteById(entity.getUuidId());
                }
            };

}