Merge branch 'master' of github.com:thingsboard/thingsboard into versions-upgrade
This commit is contained in:
		
						commit
						947c2c2ad6
					
				@ -37,6 +37,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
 | 
			
		||||
import org.springframework.web.bind.annotation.ExceptionHandler;
 | 
			
		||||
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
 | 
			
		||||
import org.springframework.web.context.request.async.DeferredResult;
 | 
			
		||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 | 
			
		||||
import org.thingsboard.common.util.DonAsynchron;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.server.cluster.TbClusterService;
 | 
			
		||||
@ -458,6 +459,8 @@ public abstract class BaseController {
 | 
			
		||||
            return new ThingsboardException(exception, ThingsboardErrorCode.DATABASE);
 | 
			
		||||
        } else if (exception instanceof EntityVersionMismatchException) {
 | 
			
		||||
            return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.VERSION_CONFLICT);
 | 
			
		||||
        } else if (exception instanceof MethodArgumentTypeMismatchException) {
 | 
			
		||||
            return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
 | 
			
		||||
        }
 | 
			
		||||
        return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.GENERAL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -17,19 +17,21 @@ package org.thingsboard.server.controller;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.lang3.ObjectUtils;
 | 
			
		||||
import org.apache.commons.lang3.exception.ExceptionUtils;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
import org.springframework.web.bind.annotation.DeleteMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseStatus;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
@ -41,7 +43,6 @@ import org.thingsboard.script.api.tbel.TbelCfTsRollingArg;
 | 
			
		||||
import org.thingsboard.script.api.tbel.TbelInvokeService;
 | 
			
		||||
import org.thingsboard.server.common.data.EntityType;
 | 
			
		||||
import org.thingsboard.server.common.data.EventInfo;
 | 
			
		||||
import org.thingsboard.server.common.data.HasTenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.event.EventType;
 | 
			
		||||
@ -49,17 +50,14 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
 | 
			
		||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
 | 
			
		||||
import org.thingsboard.server.common.data.id.HasId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.page.PageData;
 | 
			
		||||
import org.thingsboard.server.common.data.page.PageLink;
 | 
			
		||||
import org.thingsboard.server.config.annotations.ApiOperation;
 | 
			
		||||
import org.thingsboard.server.dao.event.EventService;
 | 
			
		||||
import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
			
		||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldScriptEngine;
 | 
			
		||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldTbelScriptEngine;
 | 
			
		||||
import org.thingsboard.server.service.entitiy.cf.TbCalculatedFieldService;
 | 
			
		||||
import org.thingsboard.server.service.security.model.SecurityUser;
 | 
			
		||||
import org.thingsboard.server.service.security.permission.Operation;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
@ -133,13 +131,12 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
                    "Remove 'id', 'tenantId' from the request body example (below) to create new Calculated Field entity. "
 | 
			
		||||
                    + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/calculatedField", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PostMapping("/calculatedField")
 | 
			
		||||
    public CalculatedField saveCalculatedField(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the calculated field.")
 | 
			
		||||
                                               @RequestBody CalculatedField calculatedField) throws Exception {
 | 
			
		||||
        calculatedField.setTenantId(getTenantId());
 | 
			
		||||
        checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD);
 | 
			
		||||
        checkReferencedEntities(calculatedField.getConfiguration(), getCurrentUser());
 | 
			
		||||
        checkReferencedEntities(calculatedField.getConfiguration());
 | 
			
		||||
        return tbCalculatedFieldService.save(calculatedField, getCurrentUser());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -147,8 +144,7 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
            notes = "Fetch the Calculated Field object based on the provided Calculated Field Id."
 | 
			
		||||
    )
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/calculatedField/{calculatedFieldId}", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @GetMapping("/calculatedField/{calculatedFieldId}")
 | 
			
		||||
    public CalculatedField getCalculatedFieldById(@Parameter @PathVariable(CALCULATED_FIELD_ID) String strCalculatedFieldId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(CALCULATED_FIELD_ID, strCalculatedFieldId);
 | 
			
		||||
        CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedFieldId));
 | 
			
		||||
@ -162,8 +158,7 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
            notes = "Fetch the Calculated Fields based on the provided Entity Id."
 | 
			
		||||
    )
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/{entityType}/{entityId}/calculatedFields", params = {"pageSize", "page"}, method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @GetMapping(value = "/{entityType}/{entityId}/calculatedFields", params = {"pageSize", "page"})
 | 
			
		||||
    public PageData<CalculatedField> getCalculatedFieldsByEntityId(
 | 
			
		||||
            @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
 | 
			
		||||
            @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
 | 
			
		||||
@ -182,8 +177,8 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Delete Calculated Field (deleteCalculatedField)",
 | 
			
		||||
            notes = "Deletes the calculated field. Referencing non-existing Calculated Field Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/calculatedField/{calculatedFieldId}", method = RequestMethod.DELETE)
 | 
			
		||||
    @ResponseStatus(value = HttpStatus.OK)
 | 
			
		||||
    @DeleteMapping("/calculatedField/{calculatedFieldId}")
 | 
			
		||||
    @ResponseStatus(HttpStatus.OK)
 | 
			
		||||
    public void deleteCalculatedField(@PathVariable(CALCULATED_FIELD_ID) String strCalculatedFieldId) throws Exception {
 | 
			
		||||
        checkParameter(CALCULATED_FIELD_ID, strCalculatedFieldId);
 | 
			
		||||
        CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedFieldId));
 | 
			
		||||
@ -196,8 +191,7 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
            notes = "Gets latest calculated field debug event for specified calculated field id. " +
 | 
			
		||||
                    "Referencing non-existing calculated field id will cause an error. " + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/calculatedField/{calculatedFieldId}/debug", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @GetMapping("/calculatedField/{calculatedFieldId}/debug")
 | 
			
		||||
    public JsonNode getLatestCalculatedFieldDebugEvent(@Parameter @PathVariable(CALCULATED_FIELD_ID) String strCalculatedFieldId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(CALCULATED_FIELD_ID, strCalculatedFieldId);
 | 
			
		||||
        CalculatedFieldId calculatedFieldId = new CalculatedFieldId(toUUID(strCalculatedFieldId));
 | 
			
		||||
@ -212,15 +206,13 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Test Script expression",
 | 
			
		||||
            notes = TEST_SCRIPT_EXPRESSION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/calculatedField/testScript", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PostMapping("/calculatedField/testScript")
 | 
			
		||||
    public JsonNode testScript(
 | 
			
		||||
            @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Test calculated field TBEL expression.")
 | 
			
		||||
            @RequestBody JsonNode inputParams) {
 | 
			
		||||
        String expression = inputParams.get("expression").asText();
 | 
			
		||||
        Map<String, TbelCfArg> arguments = Objects.requireNonNullElse(
 | 
			
		||||
                JacksonUtil.convertValue(inputParams.get("arguments"), new TypeReference<>() {
 | 
			
		||||
                }),
 | 
			
		||||
                JacksonUtil.convertValue(inputParams.get("arguments"), new TypeReference<>() {}),
 | 
			
		||||
                Collections.emptyMap()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
@ -231,12 +223,13 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
        String output = "";
 | 
			
		||||
        String errorText = "";
 | 
			
		||||
 | 
			
		||||
        CalculatedFieldTbelScriptEngine engine = null;
 | 
			
		||||
        try {
 | 
			
		||||
            if (tbelInvokeService == null) {
 | 
			
		||||
                throw new IllegalArgumentException("TBEL script engine is disabled!");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CalculatedFieldScriptEngine calculatedFieldScriptEngine = new CalculatedFieldTbelScriptEngine(
 | 
			
		||||
            engine = new CalculatedFieldTbelScriptEngine(
 | 
			
		||||
                    getTenantId(),
 | 
			
		||||
                    tbelInvokeService,
 | 
			
		||||
                    expression,
 | 
			
		||||
@ -254,17 +247,20 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            JsonNode json = calculatedFieldScriptEngine.executeJsonAsync(args).get(TIMEOUT, TimeUnit.SECONDS);
 | 
			
		||||
            JsonNode json = engine.executeJsonAsync(args).get(TIMEOUT, TimeUnit.SECONDS);
 | 
			
		||||
            output = JacksonUtil.toString(json);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("Error evaluating expression", e);
 | 
			
		||||
            errorText = e.getMessage();
 | 
			
		||||
            Throwable rootCause = ExceptionUtils.getRootCause(e);
 | 
			
		||||
            errorText = ObjectUtils.firstNonNull(rootCause.getMessage(), e.getMessage(), e.getClass().getSimpleName());
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (engine != null) {
 | 
			
		||||
                engine.destroy();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        ObjectNode result = JacksonUtil.newObjectNode();
 | 
			
		||||
        result.put("output", output);
 | 
			
		||||
        result.put("error", errorText);
 | 
			
		||||
        return result;
 | 
			
		||||
        }
 | 
			
		||||
        return JacksonUtil.newObjectNode()
 | 
			
		||||
                .put("output", output)
 | 
			
		||||
                .put("error", errorText);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long getLatestTimestamp(Map<String, TbelCfArg> arguments) {
 | 
			
		||||
@ -281,7 +277,7 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
        return lastUpdateTimestamp == -1 ? System.currentTimeMillis() : lastUpdateTimestamp;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private <E extends HasId<I> & HasTenantId, I extends EntityId> void checkReferencedEntities(CalculatedFieldConfiguration calculatedFieldConfig, SecurityUser user) throws ThingsboardException {
 | 
			
		||||
    private void checkReferencedEntities(CalculatedFieldConfiguration calculatedFieldConfig) throws ThingsboardException {
 | 
			
		||||
        List<EntityId> referencedEntityIds = calculatedFieldConfig.getReferencedEntities();
 | 
			
		||||
        for (EntityId referencedEntityId : referencedEntityIds) {
 | 
			
		||||
            EntityType entityType = referencedEntityId.getEntityType();
 | 
			
		||||
@ -290,8 +286,7 @@ public class CalculatedFieldController extends BaseController {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                case CUSTOMER, ASSET, DEVICE -> checkEntityId(referencedEntityId, Operation.READ);
 | 
			
		||||
                default ->
 | 
			
		||||
                        throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities.");
 | 
			
		||||
                default -> throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,6 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.controller;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.JsonProcessingException;
 | 
			
		||||
import com.fasterxml.jackson.core.type.TypeReference;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.node.ArrayNode;
 | 
			
		||||
@ -23,16 +22,19 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import io.swagger.v3.oas.annotations.media.Schema;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.lang3.ObjectUtils;
 | 
			
		||||
import org.apache.commons.lang3.exception.ExceptionUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
import org.springframework.web.bind.annotation.DeleteMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMethod;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestParam;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseStatus;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
@ -155,9 +157,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Get Rule Chain (getRuleChainById)",
 | 
			
		||||
            notes = "Fetch the Rule Chain object based on the provided Rule Chain Id. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping("/ruleChain/{ruleChainId}")
 | 
			
		||||
    public RuleChain getRuleChainById(
 | 
			
		||||
            @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
            @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
@ -169,9 +170,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Get Rule Chain output labels (getRuleChainOutputLabels)",
 | 
			
		||||
            notes = "Fetch the unique labels for the \"output\" Rule Nodes that belong to the Rule Chain based on the provided Rule Chain Id. "
 | 
			
		||||
                    + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping("/ruleChain/{ruleChainId}/output/labels")
 | 
			
		||||
    public Set<String> getRuleChainOutputLabels(
 | 
			
		||||
            @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
            @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
@ -184,9 +184,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Get output labels usage (getRuleChainOutputLabelsUsage)",
 | 
			
		||||
            notes = "Fetch the list of rule chains and the relation types (labels) they use to process output of the current rule chain based on the provided Rule Chain Id. "
 | 
			
		||||
                    + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels/usage", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping("/ruleChain/{ruleChainId}/output/labels/usage")
 | 
			
		||||
    public List<RuleChainOutputLabelsUsage> getRuleChainOutputLabelsUsage(
 | 
			
		||||
            @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
            @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
@ -198,9 +197,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Get Rule Chain (getRuleChainById)",
 | 
			
		||||
            notes = "Fetch the Rule Chain Metadata object based on the provided Rule Chain Id. " + RULE_CHAIN_METADATA_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}/metadata", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping("/ruleChain/{ruleChainId}/metadata")
 | 
			
		||||
    public RuleChainMetaData getRuleChainMetaData(
 | 
			
		||||
            @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
            @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
@ -218,9 +216,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
                    "\n\n" + RULE_CHAIN_DESCRIPTION +
 | 
			
		||||
                    "Remove 'id', 'tenantId' from the request body example (below) to create new Rule Chain entity." +
 | 
			
		||||
                    TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @PostMapping("/ruleChain")
 | 
			
		||||
    public RuleChain saveRuleChain(
 | 
			
		||||
            @Parameter(description = "A JSON value representing the rule chain.")
 | 
			
		||||
            @RequestBody RuleChain ruleChain) throws Exception {
 | 
			
		||||
@ -232,9 +229,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Create Default Rule Chain",
 | 
			
		||||
            notes = "Create rule chain from template, based on the specified name in the request. " +
 | 
			
		||||
                    "Creates the rule chain based on the template that is used to create root rule chain. " + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/device/default", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @PostMapping("/ruleChain/device/default")
 | 
			
		||||
    public RuleChain saveRuleChain(
 | 
			
		||||
            @Parameter(description = "A JSON value representing the request.")
 | 
			
		||||
            @RequestBody DefaultRuleChainCreateRequest request) throws Exception {
 | 
			
		||||
@ -245,9 +241,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Set Root Rule Chain (setRootRuleChain)",
 | 
			
		||||
            notes = "Makes the rule chain to be root rule chain. Updates previous root rule chain as well. " + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @PostMapping("/ruleChain/{ruleChainId}/root")
 | 
			
		||||
    public RuleChain setRootRuleChain(
 | 
			
		||||
            @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
            @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
@ -259,9 +254,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Update Rule Chain Metadata",
 | 
			
		||||
            notes = "Updates the rule chain metadata. " + RULE_CHAIN_METADATA_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/metadata", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @PostMapping("/ruleChain/metadata")
 | 
			
		||||
    public RuleChainMetaData saveRuleChainMetaData(
 | 
			
		||||
            @Parameter(description = "A JSON value representing the rule chain metadata.")
 | 
			
		||||
            @RequestBody RuleChainMetaData ruleChainMetaData,
 | 
			
		||||
@ -284,8 +278,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Get Rule Chains (getRuleChains)",
 | 
			
		||||
            notes = "Returns a page of Rule Chains owned by tenant. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @GetMapping(value = "/ruleChains", params = {"pageSize", "page"})
 | 
			
		||||
    public PageData<RuleChain> getRuleChains(
 | 
			
		||||
            @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
 | 
			
		||||
            @RequestParam int pageSize,
 | 
			
		||||
@ -302,7 +295,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
        TenantId tenantId = getCurrentUser().getTenantId();
 | 
			
		||||
        PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
 | 
			
		||||
        RuleChainType type = RuleChainType.CORE;
 | 
			
		||||
        if (typeStr != null && typeStr.trim().length() > 0) {
 | 
			
		||||
        if (StringUtils.isNotBlank(typeStr)) {
 | 
			
		||||
            type = RuleChainType.valueOf(typeStr);
 | 
			
		||||
        }
 | 
			
		||||
        return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink));
 | 
			
		||||
@ -311,9 +304,9 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Delete rule chain (deleteRuleChain)",
 | 
			
		||||
            notes = "Deletes the rule chain. Referencing non-existing rule chain Id will cause an error. " +
 | 
			
		||||
                    "Referencing rule chain that is used in the device profiles will cause an error." + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
 | 
			
		||||
    @ResponseStatus(value = HttpStatus.OK)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @DeleteMapping("/ruleChain/{ruleChainId}")
 | 
			
		||||
    @ResponseStatus(HttpStatus.OK)
 | 
			
		||||
    public void deleteRuleChain(
 | 
			
		||||
            @Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
            @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
@ -326,9 +319,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Get latest input message (getLatestRuleNodeDebugInput)",
 | 
			
		||||
            notes = "Gets the input message from the debug events for specified Rule Chain Id. " +
 | 
			
		||||
                    "Referencing non-existing rule chain Id will cause an error. " + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleNode/{ruleNodeId}/debugIn", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping("/ruleNode/{ruleNodeId}/debugIn")
 | 
			
		||||
    public JsonNode getLatestRuleNodeDebugInput(
 | 
			
		||||
            @Parameter(description = RULE_NODE_ID_PARAM_DESCRIPTION)
 | 
			
		||||
            @PathVariable(RULE_NODE_ID) String strRuleNodeId) throws ThingsboardException {
 | 
			
		||||
@ -343,8 +335,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Is TBEL script executor enabled",
 | 
			
		||||
            notes = "Returns 'True' if the TBEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/tbelEnabled", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @GetMapping("/ruleChain/tbelEnabled")
 | 
			
		||||
    public Boolean isTbelEnabled() {
 | 
			
		||||
        return tbelEnabled;
 | 
			
		||||
    }
 | 
			
		||||
@ -352,13 +343,12 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Test Script function",
 | 
			
		||||
            notes = TEST_SCRIPT_FUNCTION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PostMapping("/ruleChain/testScript")
 | 
			
		||||
    public JsonNode testScript(
 | 
			
		||||
            @Parameter(description = "Script language: JS or TBEL")
 | 
			
		||||
            @RequestParam(required = false) ScriptLanguage scriptLang,
 | 
			
		||||
            @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Test JS request. See API call description above.")
 | 
			
		||||
            @RequestBody JsonNode inputParams) throws ThingsboardException, JsonProcessingException {
 | 
			
		||||
            @RequestBody JsonNode inputParams) {
 | 
			
		||||
        String script = inputParams.get("script").asText();
 | 
			
		||||
        String scriptType = inputParams.get("scriptType").asText();
 | 
			
		||||
        JsonNode argNamesJson = inputParams.get("argNames");
 | 
			
		||||
@ -366,8 +356,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
 | 
			
		||||
        String data = inputParams.get("msg").asText();
 | 
			
		||||
        JsonNode metadataJson = inputParams.get("metadata");
 | 
			
		||||
        Map<String, String> metadata = JacksonUtil.convertValue(metadataJson, new TypeReference<Map<String, String>>() {
 | 
			
		||||
        });
 | 
			
		||||
        Map<String, String> metadata = JacksonUtil.convertValue(metadataJson, new TypeReference<>() {});
 | 
			
		||||
        String msgType = inputParams.get("msgType").asText();
 | 
			
		||||
        String output = "";
 | 
			
		||||
        String errorText = "";
 | 
			
		||||
@ -384,55 +373,40 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
                }
 | 
			
		||||
                engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames);
 | 
			
		||||
            }
 | 
			
		||||
            TbMsg inMsg = TbMsg.newMsg()
 | 
			
		||||
 | 
			
		||||
            var inMsg = TbMsg.newMsg()
 | 
			
		||||
                    .type(msgType)
 | 
			
		||||
                    .copyMetaData(new TbMsgMetaData(metadata))
 | 
			
		||||
                    .dataType(TbMsgDataType.JSON)
 | 
			
		||||
                    .data(data)
 | 
			
		||||
                    .build();
 | 
			
		||||
            switch (scriptType) {
 | 
			
		||||
                case "update":
 | 
			
		||||
                    output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
 | 
			
		||||
                    break;
 | 
			
		||||
                case "generate":
 | 
			
		||||
                    output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
 | 
			
		||||
                    break;
 | 
			
		||||
                case "filter":
 | 
			
		||||
                    boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
 | 
			
		||||
                    output = Boolean.toString(result);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "switch":
 | 
			
		||||
                    Set<String> states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
 | 
			
		||||
                    output = JacksonUtil.toString(states);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "json":
 | 
			
		||||
                    JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
 | 
			
		||||
                    output = JacksonUtil.toString(json);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "string":
 | 
			
		||||
                    output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new IllegalArgumentException("Unsupported script type: " + scriptType);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            output = switch (scriptType) {
 | 
			
		||||
                case "update" -> msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
 | 
			
		||||
                case "generate" -> msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
 | 
			
		||||
                case "filter" -> Boolean.toString(engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
 | 
			
		||||
                case "switch" -> JacksonUtil.toString(engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
 | 
			
		||||
                case "json" -> JacksonUtil.toString(engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
 | 
			
		||||
                case "string" -> engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
 | 
			
		||||
                default -> throw new IllegalArgumentException("Unsupported script type: " + scriptType);
 | 
			
		||||
            };
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("Error evaluating JS function", e);
 | 
			
		||||
            errorText = e.getMessage();
 | 
			
		||||
            Throwable rootCause = ExceptionUtils.getRootCause(e);
 | 
			
		||||
            errorText = ObjectUtils.firstNonNull(rootCause.getMessage(), e.getMessage(), e.getClass().getSimpleName());
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (engine != null) {
 | 
			
		||||
                engine.destroy();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ObjectNode result = JacksonUtil.newObjectNode();
 | 
			
		||||
        result.put("output", output);
 | 
			
		||||
        result.put("error", errorText);
 | 
			
		||||
        return result;
 | 
			
		||||
        return JacksonUtil.newObjectNode()
 | 
			
		||||
                .put("output", output)
 | 
			
		||||
                .put("error", errorText);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Export Rule Chains", notes = "Exports all tenant rule chains as one JSON." + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChains/export", params = {"limit"}, method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @GetMapping(value = "/ruleChains/export", params = {"limit"})
 | 
			
		||||
    public RuleChainData exportRuleChains(
 | 
			
		||||
            @Parameter(description = "A limit of rule chains to export.", required = true)
 | 
			
		||||
            @RequestParam("limit") int limit) throws ThingsboardException {
 | 
			
		||||
@ -443,8 +417,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Import Rule Chains", notes = "Imports all tenant rule chains as one JSON." + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChains/import", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PostMapping("/ruleChains/import")
 | 
			
		||||
    public List<RuleChainImportResult> importRuleChains(
 | 
			
		||||
            @Parameter(description = "A JSON value representing the rule chains.")
 | 
			
		||||
            @RequestBody RuleChainData ruleChainData,
 | 
			
		||||
@ -454,12 +427,12 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
        return ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite, tbRuleChainService::updateRuleNodeConfiguration);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String msgToOutput(TbMsg msg) throws Exception {
 | 
			
		||||
    private String msgToOutput(TbMsg msg) {
 | 
			
		||||
        JsonNode resultNode = convertMsgToOut(msg);
 | 
			
		||||
        return JacksonUtil.toString(resultNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String msgToOutput(List<TbMsg> msgs) throws Exception {
 | 
			
		||||
    private String msgToOutput(List<TbMsg> msgs) {
 | 
			
		||||
        JsonNode resultNode;
 | 
			
		||||
        if (msgs.size() > 1) {
 | 
			
		||||
            resultNode = JacksonUtil.newArrayNode();
 | 
			
		||||
@ -473,7 +446,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
        return JacksonUtil.toString(resultNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private JsonNode convertMsgToOut(TbMsg msg) throws Exception {
 | 
			
		||||
    private JsonNode convertMsgToOut(TbMsg msg) {
 | 
			
		||||
        ObjectNode msgData = JacksonUtil.newObjectNode();
 | 
			
		||||
        if (!StringUtils.isEmpty(msg.getData())) {
 | 
			
		||||
            msgData.set("msg", JacksonUtil.toJsonNode(msg.getData()));
 | 
			
		||||
@ -492,8 +465,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
                    "Third, once rule chain will be delivered to edge service, it's going to start processing messages locally. " +
 | 
			
		||||
                    "\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PostMapping("/edge/{edgeId}/ruleChain/{ruleChainId}")
 | 
			
		||||
    public RuleChain assignRuleChainToEdge(@PathVariable("edgeId") String strEdgeId,
 | 
			
		||||
                                           @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
        checkParameter("edgeId", strEdgeId);
 | 
			
		||||
@ -514,8 +486,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
                    EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
 | 
			
		||||
                    "Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @DeleteMapping("/edge/{edgeId}/ruleChain/{ruleChainId}")
 | 
			
		||||
    public RuleChain unassignRuleChainFromEdge(@PathVariable("edgeId") String strEdgeId,
 | 
			
		||||
                                               @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
        checkParameter("edgeId", strEdgeId);
 | 
			
		||||
@ -530,9 +501,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)",
 | 
			
		||||
            notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"})
 | 
			
		||||
    public PageData<RuleChain> getEdgeRuleChains(
 | 
			
		||||
            @Parameter(description = EDGE_ID_PARAM_DESCRIPTION, required = true)
 | 
			
		||||
            @PathVariable(EDGE_ID) String strEdgeId,
 | 
			
		||||
@ -557,9 +527,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    @ApiOperation(value = "Set Edge Template Root Rule Chain (setEdgeTemplateRootRuleChain)",
 | 
			
		||||
            notes = "Makes the rule chain to be root rule chain for any new edge that will be created. " +
 | 
			
		||||
                    "Does not update root rule chain for already created edges. " + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}/edgeTemplateRoot", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @PostMapping("/ruleChain/{ruleChainId}/edgeTemplateRoot")
 | 
			
		||||
    public RuleChain setEdgeTemplateRootRuleChain(@Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                  @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(RULE_CHAIN_ID, strRuleChainId);
 | 
			
		||||
@ -572,8 +541,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
            notes = "Makes the rule chain to be automatically assigned for any new edge that will be created. " +
 | 
			
		||||
                    "Does not assign this rule chain for already created edges. " + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.POST)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PostMapping("/ruleChain/{ruleChainId}/autoAssignToEdge")
 | 
			
		||||
    public RuleChain setAutoAssignToEdgeRuleChain(@Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                  @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(RULE_CHAIN_ID, strRuleChainId);
 | 
			
		||||
@ -586,8 +554,7 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
            notes = "Removes the rule chain from the list of rule chains that are going to be automatically assigned for any new edge that will be created. " +
 | 
			
		||||
                    "Does not unassign this rule chain for already assigned edges. " + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.DELETE)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @DeleteMapping("/ruleChain/{ruleChainId}/autoAssignToEdge")
 | 
			
		||||
    public RuleChain unsetAutoAssignToEdgeRuleChain(@Parameter(description = RULE_CHAIN_ID_PARAM_DESCRIPTION)
 | 
			
		||||
                                                    @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
 | 
			
		||||
        checkParameter(RULE_CHAIN_ID, strRuleChainId);
 | 
			
		||||
@ -599,9 +566,8 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
    // TODO: @voba refactor this - add new config to edge rule chain to set it as auto-assign
 | 
			
		||||
    @ApiOperation(value = "Get Auto Assign To Edge Rule Chains (getAutoAssignToEdgeRuleChains)",
 | 
			
		||||
            notes = "Returns a list of Rule Chains that will be assigned to a newly created edge. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @RequestMapping(value = "/ruleChain/autoAssignToEdgeRuleChains", method = RequestMethod.GET)
 | 
			
		||||
    @ResponseBody
 | 
			
		||||
    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
 | 
			
		||||
    @GetMapping("/ruleChain/autoAssignToEdgeRuleChains")
 | 
			
		||||
    public List<RuleChain> getAutoAssignToEdgeRuleChains() throws ThingsboardException {
 | 
			
		||||
        TenantId tenantId = getCurrentUser().getTenantId();
 | 
			
		||||
        List<RuleChain> result = new ArrayList<>();
 | 
			
		||||
@ -612,4 +578,5 @@ public class RuleChainController extends BaseController {
 | 
			
		||||
        }
 | 
			
		||||
        return checkNotNull(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
			
		||||
import org.junit.Assert;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.springframework.test.context.TestPropertySource;
 | 
			
		||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 | 
			
		||||
import org.thingsboard.server.common.data.Device;
 | 
			
		||||
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
 | 
			
		||||
@ -33,6 +34,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 | 
			
		||||
import static org.thingsboard.server.common.data.query.EntityKeyType.TIME_SERIES;
 | 
			
		||||
 | 
			
		||||
@ -208,6 +210,15 @@ public class TelemetryControllerTest extends AbstractControllerTest {
 | 
			
		||||
        doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/timeseries/smth", invalidRequestBody, String.class, status().isBadRequest());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBadRequestReturnedWhenMethodArgumentTypeMismatch() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
        String content = "{\"key\": \"value\"}";
 | 
			
		||||
        doPost("/api/plugins/telemetry/DEVICE/20b559f5-849f-4361-b4f6-b6d0b76687e9/INVALID_SCOPE", content, (String) null)
 | 
			
		||||
                .andExpect(status().isBadRequest())
 | 
			
		||||
                .andExpect(result -> assertThat(result.getResolvedException()).isInstanceOf(MethodArgumentTypeMismatchException.class));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testEmptyKeyIsProhibited() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
 | 
			
		||||
@ -140,6 +140,7 @@
 | 
			
		||||
    "tinymce": "6.8.5",
 | 
			
		||||
    "rollup": "4.22.4",
 | 
			
		||||
    "@babel/core": "7.25.2",
 | 
			
		||||
    "esbuild": "0.23.0"
 | 
			
		||||
    "esbuild": "0.23.0",
 | 
			
		||||
    "jquery.terminal/coveralls-next/form-data": "4.0.4"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3740,6 +3740,14 @@ cacache@^18.0.0:
 | 
			
		||||
    tar "^6.1.11"
 | 
			
		||||
    unique-filename "^3.0.0"
 | 
			
		||||
 | 
			
		||||
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
 | 
			
		||||
  version "1.0.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
 | 
			
		||||
  integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    es-errors "^1.3.0"
 | 
			
		||||
    function-bind "^1.1.2"
 | 
			
		||||
 | 
			
		||||
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
 | 
			
		||||
  version "1.0.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
 | 
			
		||||
@ -4788,6 +4796,15 @@ domutils@^3.0.1:
 | 
			
		||||
    domelementtype "^2.3.0"
 | 
			
		||||
    domhandler "^5.0.3"
 | 
			
		||||
 | 
			
		||||
dunder-proto@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
 | 
			
		||||
  integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    call-bind-apply-helpers "^1.0.1"
 | 
			
		||||
    es-errors "^1.3.0"
 | 
			
		||||
    gopd "^1.2.0"
 | 
			
		||||
 | 
			
		||||
earcut@^3.0.1:
 | 
			
		||||
  version "3.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/earcut/-/earcut-3.0.1.tgz#f60b3f671c5657cca9d3e131c5527c5dde00ef38"
 | 
			
		||||
@ -4968,6 +4985,11 @@ es-define-property@^1.0.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    get-intrinsic "^1.2.4"
 | 
			
		||||
 | 
			
		||||
es-define-property@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
 | 
			
		||||
  integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
 | 
			
		||||
 | 
			
		||||
es-errors@^1.2.1, es-errors@^1.3.0:
 | 
			
		||||
  version "1.3.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
 | 
			
		||||
@ -4985,6 +5007,13 @@ es-object-atoms@^1.0.0:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    es-errors "^1.3.0"
 | 
			
		||||
 | 
			
		||||
es-object-atoms@^1.1.1:
 | 
			
		||||
  version "1.1.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
 | 
			
		||||
  integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    es-errors "^1.3.0"
 | 
			
		||||
 | 
			
		||||
es-set-tostringtag@^2.0.3:
 | 
			
		||||
  version "2.0.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777"
 | 
			
		||||
@ -4994,6 +5023,16 @@ es-set-tostringtag@^2.0.3:
 | 
			
		||||
    has-tostringtag "^1.0.2"
 | 
			
		||||
    hasown "^2.0.1"
 | 
			
		||||
 | 
			
		||||
es-set-tostringtag@^2.1.0:
 | 
			
		||||
  version "2.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
 | 
			
		||||
  integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    es-errors "^1.3.0"
 | 
			
		||||
    get-intrinsic "^1.2.6"
 | 
			
		||||
    has-tostringtag "^1.0.2"
 | 
			
		||||
    hasown "^2.0.2"
 | 
			
		||||
 | 
			
		||||
es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2:
 | 
			
		||||
  version "1.0.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763"
 | 
			
		||||
@ -5508,13 +5547,15 @@ foreground-child@^3.1.0:
 | 
			
		||||
    cross-spawn "^7.0.0"
 | 
			
		||||
    signal-exit "^4.0.1"
 | 
			
		||||
 | 
			
		||||
form-data@4.0.0:
 | 
			
		||||
  version "4.0.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
 | 
			
		||||
  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
 | 
			
		||||
form-data@4.0.0, form-data@4.0.4:
 | 
			
		||||
  version "4.0.4"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4"
 | 
			
		||||
  integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    asynckit "^0.4.0"
 | 
			
		||||
    combined-stream "^1.0.8"
 | 
			
		||||
    es-set-tostringtag "^2.1.0"
 | 
			
		||||
    hasown "^2.0.2"
 | 
			
		||||
    mime-types "^2.1.12"
 | 
			
		||||
 | 
			
		||||
formdata-polyfill@^4.0.10:
 | 
			
		||||
@ -5635,6 +5676,30 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@
 | 
			
		||||
    has-symbols "^1.0.3"
 | 
			
		||||
    hasown "^2.0.0"
 | 
			
		||||
 | 
			
		||||
get-intrinsic@^1.2.6:
 | 
			
		||||
  version "1.3.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
 | 
			
		||||
  integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    call-bind-apply-helpers "^1.0.2"
 | 
			
		||||
    es-define-property "^1.0.1"
 | 
			
		||||
    es-errors "^1.3.0"
 | 
			
		||||
    es-object-atoms "^1.1.1"
 | 
			
		||||
    function-bind "^1.1.2"
 | 
			
		||||
    get-proto "^1.0.1"
 | 
			
		||||
    gopd "^1.2.0"
 | 
			
		||||
    has-symbols "^1.1.0"
 | 
			
		||||
    hasown "^2.0.2"
 | 
			
		||||
    math-intrinsics "^1.1.0"
 | 
			
		||||
 | 
			
		||||
get-proto@^1.0.1:
 | 
			
		||||
  version "1.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
 | 
			
		||||
  integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    dunder-proto "^1.0.1"
 | 
			
		||||
    es-object-atoms "^1.0.0"
 | 
			
		||||
 | 
			
		||||
get-stream@^6.0.0, get-stream@^6.0.1:
 | 
			
		||||
  version "6.0.1"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
 | 
			
		||||
@ -5750,6 +5815,11 @@ gopd@^1.0.1:
 | 
			
		||||
  dependencies:
 | 
			
		||||
    get-intrinsic "^1.1.3"
 | 
			
		||||
 | 
			
		||||
gopd@^1.2.0:
 | 
			
		||||
  version "1.2.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
 | 
			
		||||
  integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
 | 
			
		||||
 | 
			
		||||
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6:
 | 
			
		||||
  version "4.2.11"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
 | 
			
		||||
@ -5807,6 +5877,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
 | 
			
		||||
  integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
 | 
			
		||||
 | 
			
		||||
has-symbols@^1.1.0:
 | 
			
		||||
  version "1.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
 | 
			
		||||
  integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
 | 
			
		||||
 | 
			
		||||
has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
 | 
			
		||||
  version "1.0.2"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
 | 
			
		||||
@ -6970,6 +7045,11 @@ marked@~12.0.2:
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/marked/-/marked-12.0.2.tgz#b31578fe608b599944c69807b00f18edab84647e"
 | 
			
		||||
  integrity sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==
 | 
			
		||||
 | 
			
		||||
math-intrinsics@^1.1.0:
 | 
			
		||||
  version "1.1.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
 | 
			
		||||
  integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
 | 
			
		||||
 | 
			
		||||
media-typer@0.3.0:
 | 
			
		||||
  version "0.3.0"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user