Browse Source
# Conflicts: # dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java # dao/src/main/resources/sql/schema-entities-hsql.sqlpull/3557/head
628 changed files with 28600 additions and 7469 deletions
@ -1,12 +0,0 @@ |
|||
before_install: |
|||
- sudo rm -f /etc/mavenrc |
|||
- export M2_HOME=/usr/local/maven |
|||
- export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" |
|||
- export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false |
|||
jdk: |
|||
- openjdk8 |
|||
language: java |
|||
sudo: required |
|||
services: |
|||
- docker |
|||
script: mvn clean verify -Ddockerfile.skip=false |
|||
@ -0,0 +1,22 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ |
|||
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD |
|||
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX |
|||
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y |
|||
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy |
|||
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr |
|||
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr |
|||
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK |
|||
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu |
|||
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy |
|||
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye |
|||
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 |
|||
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 |
|||
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 |
|||
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx |
|||
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 |
|||
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz |
|||
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS |
|||
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp |
|||
-----END CERTIFICATE----- |
|||
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,878 @@ |
|||
-- |
|||
-- Copyright © 2016-2020 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. |
|||
-- |
|||
|
|||
CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS |
|||
$$ |
|||
BEGIN |
|||
uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) || |
|||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12); |
|||
END; |
|||
$$ LANGUAGE plpgsql; |
|||
|
|||
CREATE OR REPLACE FUNCTION extract_ts(uuid UUID) RETURNS BIGINT AS |
|||
$$ |
|||
DECLARE |
|||
bytes bytea; |
|||
BEGIN |
|||
bytes := uuid_send(uuid); |
|||
RETURN |
|||
( |
|||
( |
|||
(get_byte(bytes, 0)::bigint << 24) | |
|||
(get_byte(bytes, 1)::bigint << 16) | |
|||
(get_byte(bytes, 2)::bigint << 8) | |
|||
(get_byte(bytes, 3)::bigint << 0) |
|||
) + ( |
|||
((get_byte(bytes, 4)::bigint << 8 | |
|||
get_byte(bytes, 5)::bigint)) << 32 |
|||
) + ( |
|||
(((get_byte(bytes, 6)::bigint & 15) << 8 | get_byte(bytes, 7)::bigint) & 4095) << 48 |
|||
) - 122192928000000000 |
|||
) / 10000::double precision; |
|||
END |
|||
$$ LANGUAGE plpgsql |
|||
IMMUTABLE |
|||
PARALLEL SAFE |
|||
RETURNS NULL ON NULL INPUT; |
|||
|
|||
|
|||
CREATE OR REPLACE FUNCTION column_type_to_uuid(table_name varchar, column_name varchar) RETURNS VOID |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
execute format('ALTER TABLE %s RENAME COLUMN %s TO old_%s;', table_name, column_name, column_name); |
|||
execute format('ALTER TABLE %s ADD COLUMN %s UUID;', table_name, column_name); |
|||
execute format('UPDATE %s SET %s = to_uuid(old_%s) WHERE old_%s is not null;', table_name, column_name, column_name, column_name); |
|||
execute format('ALTER TABLE %s DROP COLUMN old_%s;', table_name, column_name); |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION get_column_type(table_name varchar, column_name varchar, OUT data_type varchar) RETURNS varchar |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
execute (format('SELECT data_type from information_schema.columns where table_name = %L and column_name = %L', |
|||
table_name, column_name)) INTO data_type; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
CREATE OR REPLACE PROCEDURE drop_all_idx() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
DROP INDEX IF EXISTS idx_alarm_originator_alarm_type; |
|||
DROP INDEX IF EXISTS idx_alarm_originator_created_time; |
|||
DROP INDEX IF EXISTS idx_alarm_tenant_created_time; |
|||
DROP INDEX IF EXISTS idx_event_type_entity_id; |
|||
DROP INDEX IF EXISTS idx_relation_to_id; |
|||
DROP INDEX IF EXISTS idx_relation_from_id; |
|||
DROP INDEX IF EXISTS idx_device_customer_id; |
|||
DROP INDEX IF EXISTS idx_device_customer_id_and_type; |
|||
DROP INDEX IF EXISTS idx_device_type; |
|||
DROP INDEX IF EXISTS idx_asset_customer_id; |
|||
DROP INDEX IF EXISTS idx_asset_customer_id_and_type; |
|||
DROP INDEX IF EXISTS idx_asset_type; |
|||
DROP INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE PROCEDURE create_all_idx() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); |
|||
CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); |
|||
CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); |
|||
CREATE INDEX IF NOT EXISTS idx_device_customer_id ON device(tenant_id, customer_id); |
|||
CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, customer_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id); |
|||
CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- admin_settings |
|||
CREATE OR REPLACE PROCEDURE update_admin_settings() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'admin_settings'; |
|||
column_id varchar := 'id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE admin_settings DROP CONSTRAINT admin_settings_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE admin_settings ADD CONSTRAINT admin_settings_pkey PRIMARY KEY (id); |
|||
ALTER TABLE admin_settings ADD COLUMN created_time BIGINT; |
|||
UPDATE admin_settings SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- alarm |
|||
CREATE OR REPLACE PROCEDURE update_alarm() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'alarm'; |
|||
column_id varchar := 'id'; |
|||
column_originator_id varchar := 'originator_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE alarm DROP CONSTRAINT alarm_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE alarm ADD COLUMN created_time BIGINT; |
|||
UPDATE alarm SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE alarm ADD CONSTRAINT alarm_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_originator_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_originator_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_originator_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_originator_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- asset |
|||
CREATE OR REPLACE PROCEDURE update_asset() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'asset'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE asset DROP CONSTRAINT asset_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE asset ADD COLUMN created_time BIGINT; |
|||
UPDATE asset SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE asset ADD CONSTRAINT asset_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE asset DROP CONSTRAINT asset_name_unq_key; |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
ALTER TABLE asset ADD CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- attribute_kv |
|||
CREATE OR REPLACE PROCEDURE update_attribute_kv() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'attribute_kv'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE attribute_kv DROP CONSTRAINT attribute_kv_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
ALTER TABLE attribute_kv ADD CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- audit_log |
|||
CREATE OR REPLACE PROCEDURE update_audit_log() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'audit_log'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
column_user_id varchar := 'user_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE audit_log DROP CONSTRAINT audit_log_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE audit_log ADD COLUMN created_time BIGINT; |
|||
UPDATE audit_log SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE audit_log ADD CONSTRAINT audit_log_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_user_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_user_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_user_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- component_descriptor |
|||
CREATE OR REPLACE PROCEDURE update_component_descriptor() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'component_descriptor'; |
|||
column_id varchar := 'id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE component_descriptor DROP CONSTRAINT component_descriptor_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE component_descriptor ADD CONSTRAINT component_descriptor_pkey PRIMARY KEY (id); |
|||
ALTER TABLE component_descriptor ADD COLUMN created_time BIGINT; |
|||
UPDATE component_descriptor SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- customer |
|||
CREATE OR REPLACE PROCEDURE update_customer() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'customer'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE customer DROP CONSTRAINT customer_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE customer ADD CONSTRAINT customer_pkey PRIMARY KEY (id); |
|||
ALTER TABLE customer ADD COLUMN created_time BIGINT; |
|||
UPDATE customer SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- dashboard |
|||
CREATE OR REPLACE PROCEDURE update_dashboard() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'dashboard'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE dashboard DROP CONSTRAINT dashboard_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE dashboard ADD CONSTRAINT dashboard_pkey PRIMARY KEY (id); |
|||
ALTER TABLE dashboard ADD COLUMN created_time BIGINT; |
|||
UPDATE dashboard SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- device |
|||
CREATE OR REPLACE PROCEDURE update_device() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'device'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device DROP CONSTRAINT device_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE device ADD COLUMN created_time BIGINT; |
|||
UPDATE device SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE device ADD CONSTRAINT device_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device DROP CONSTRAINT device_name_unq_key; |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
ALTER TABLE device ADD CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- device_credentials |
|||
CREATE OR REPLACE PROCEDURE update_device_credentials() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'device_credentials'; |
|||
column_id varchar := 'id'; |
|||
column_device_id varchar := 'device_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device_credentials DROP CONSTRAINT device_credentials_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE device_credentials ADD COLUMN created_time BIGINT; |
|||
UPDATE device_credentials SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_device_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device_credentials DROP CONSTRAINT IF EXISTS device_credentials_device_id_unq_key; |
|||
PERFORM column_type_to_uuid(table_name, column_device_id); |
|||
-- remove duplicate credentials with same device_id |
|||
DELETE from device_credentials where id in ( |
|||
select dc.id |
|||
from ( |
|||
SELECT id, device_id, |
|||
ROW_NUMBER() OVER ( |
|||
PARTITION BY |
|||
device_id |
|||
ORDER BY |
|||
created_time DESC |
|||
) row_num |
|||
FROM |
|||
device_credentials |
|||
) as dc |
|||
WHERE dc.row_num > 1 |
|||
); |
|||
ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_device_id_unq_key UNIQUE (device_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_device_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_device_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- event |
|||
CREATE OR REPLACE PROCEDURE update_event() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'event'; |
|||
column_id varchar := 'id'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE event DROP CONSTRAINT event_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE event ADD COLUMN created_time BIGINT; |
|||
UPDATE event SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE event ADD CONSTRAINT event_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
ALTER TABLE event DROP CONSTRAINT event_unq_key; |
|||
|
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
|
|||
ALTER TABLE event ADD CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- relation |
|||
CREATE OR REPLACE PROCEDURE update_relation() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'relation'; |
|||
column_from_id varchar := 'from_id'; |
|||
column_to_id varchar := 'to_id'; |
|||
BEGIN |
|||
ALTER TABLE relation DROP CONSTRAINT relation_pkey; |
|||
|
|||
data_type := get_column_type(table_name, column_from_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_from_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_from_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_from_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_to_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_to_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_to_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_to_id; |
|||
END IF; |
|||
|
|||
ALTER TABLE relation ADD CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- tb_user |
|||
CREATE OR REPLACE PROCEDURE update_tb_user() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'tb_user'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE tb_user DROP CONSTRAINT tb_user_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE tb_user ADD COLUMN created_time BIGINT; |
|||
UPDATE tb_user SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE tb_user ADD CONSTRAINT tb_user_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- tenant |
|||
CREATE OR REPLACE PROCEDURE update_tenant() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'tenant'; |
|||
column_id varchar := 'id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE tenant DROP CONSTRAINT tenant_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE tenant ADD COLUMN created_time BIGINT; |
|||
UPDATE tenant SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE tenant ADD CONSTRAINT tenant_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- user_credentials |
|||
CREATE OR REPLACE PROCEDURE update_user_credentials() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'user_credentials'; |
|||
column_id varchar := 'id'; |
|||
column_user_id varchar := 'user_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE user_credentials ADD COLUMN created_time BIGINT; |
|||
UPDATE user_credentials SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE user_credentials ADD CONSTRAINT user_credentials_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_user_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_user_id_key; |
|||
ALTER TABLE user_credentials RENAME COLUMN user_id TO old_user_id; |
|||
ALTER TABLE user_credentials ADD COLUMN user_id UUID UNIQUE; |
|||
UPDATE user_credentials SET user_id = to_uuid(old_user_id) WHERE old_user_id is not null; |
|||
ALTER TABLE user_credentials DROP COLUMN old_user_id; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_user_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- widget_type |
|||
CREATE OR REPLACE PROCEDURE update_widget_type() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'widget_type'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE widget_type DROP CONSTRAINT widget_type_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE widget_type ADD COLUMN created_time BIGINT; |
|||
UPDATE widget_type SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE widget_type ADD CONSTRAINT widget_type_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- widgets_bundle |
|||
CREATE OR REPLACE PROCEDURE update_widgets_bundle() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'widgets_bundle'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE widgets_bundle DROP CONSTRAINT widgets_bundle_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE widgets_bundle ADD COLUMN created_time BIGINT; |
|||
UPDATE widgets_bundle SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE widgets_bundle ADD CONSTRAINT widgets_bundle_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- rule_chain |
|||
CREATE OR REPLACE PROCEDURE update_rule_chain() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'rule_chain'; |
|||
column_id varchar := 'id'; |
|||
column_first_rule_node_id varchar := 'first_rule_node_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE rule_chain DROP CONSTRAINT rule_chain_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE rule_chain ADD COLUMN created_time BIGINT; |
|||
UPDATE rule_chain SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE rule_chain ADD CONSTRAINT rule_chain_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_first_rule_node_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_first_rule_node_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_first_rule_node_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_first_rule_node_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- rule_node |
|||
CREATE OR REPLACE PROCEDURE update_rule_node() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'rule_node'; |
|||
column_id varchar := 'id'; |
|||
column_rule_chain_id varchar := 'rule_chain_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE rule_node DROP CONSTRAINT rule_node_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE rule_node ADD COLUMN created_time BIGINT; |
|||
UPDATE rule_node SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE rule_node ADD CONSTRAINT rule_node_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_rule_chain_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_rule_chain_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_rule_chain_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_rule_chain_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- entity_view |
|||
CREATE OR REPLACE PROCEDURE update_entity_view() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'entity_view'; |
|||
column_id varchar := 'id'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE entity_view DROP CONSTRAINT entity_view_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE entity_view ADD COLUMN created_time BIGINT; |
|||
UPDATE entity_view SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE entity_view ADD CONSTRAINT entity_view_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE TABLE IF NOT EXISTS ts_kv_latest |
|||
( |
|||
entity_id uuid NOT NULL, |
|||
key int NOT NULL, |
|||
ts bigint NOT NULL, |
|||
bool_v boolean, |
|||
str_v varchar(10000000), |
|||
long_v bigint, |
|||
dbl_v double precision, |
|||
json_v json, |
|||
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS ts_kv_dictionary |
|||
( |
|||
key varchar(255) NOT NULL, |
|||
key_id serial UNIQUE, |
|||
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) |
|||
); |
|||
@ -0,0 +1,79 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.controller; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
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.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityCountQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.query.EntityQueryService; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
public class EntityQueryController extends BaseController { |
|||
|
|||
@Autowired |
|||
private EntityQueryService entityQueryService; |
|||
|
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/entitiesQuery/count", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public long countEntitiesByQuery(@RequestBody EntityCountQuery query) throws ThingsboardException { |
|||
checkNotNull(query); |
|||
try { |
|||
return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/entitiesQuery/find", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public PageData<EntityData> findEntityDataByQuery(@RequestBody EntityDataQuery query) throws ThingsboardException { |
|||
checkNotNull(query); |
|||
try { |
|||
return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/alarmsQuery/find", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public PageData<AlarmData> findAlarmDataByQuery(@RequestBody AlarmDataQuery query) throws ThingsboardException { |
|||
checkNotNull(query); |
|||
try { |
|||
return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.install; |
|||
|
|||
import org.springframework.context.annotation.Profile; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.dao.util.NoSqlTsLatestDao; |
|||
|
|||
@Service |
|||
@NoSqlTsLatestDao |
|||
@Profile("install") |
|||
public class CassandraTsLatestDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService |
|||
implements TsLatestDatabaseSchemaService { |
|||
public CassandraTsLatestDatabaseSchemaService() { |
|||
super("schema-ts-latest.cql"); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.install; |
|||
|
|||
public interface TsLatestDatabaseSchemaService extends DatabaseSchemaService { |
|||
} |
|||
@ -0,0 +1,205 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.install.migrate; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.hibernate.exception.ConstraintViolationException; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.annotation.Profile; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.UUIDConverter; |
|||
import org.thingsboard.server.dao.cassandra.CassandraCluster; |
|||
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; |
|||
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; |
|||
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; |
|||
import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; |
|||
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository; |
|||
import org.thingsboard.server.dao.util.NoSqlAnyDao; |
|||
import org.thingsboard.server.service.install.EntityDatabaseSchemaService; |
|||
|
|||
import java.sql.Connection; |
|||
import java.sql.DriverManager; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.locks.ReentrantLock; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.bigintColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.booleanColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.doubleColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.idColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.jsonColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.stringColumn; |
|||
|
|||
@Service |
|||
@Profile("install") |
|||
@NoSqlAnyDao |
|||
@Slf4j |
|||
public class CassandraTsLatestToSqlMigrateService implements TsLatestMigrateService { |
|||
|
|||
@Autowired |
|||
private EntityDatabaseSchemaService entityDatabaseSchemaService; |
|||
|
|||
@Autowired |
|||
private InsertLatestTsRepository insertLatestTsRepository; |
|||
|
|||
@Autowired |
|||
protected CassandraCluster cluster; |
|||
|
|||
@Autowired |
|||
protected TsKvDictionaryRepository dictionaryRepository; |
|||
|
|||
@Value("${spring.datasource.url}") |
|||
protected String dbUrl; |
|||
|
|||
@Value("${spring.datasource.username}") |
|||
protected String dbUserName; |
|||
|
|||
@Value("${spring.datasource.password}") |
|||
protected String dbPassword; |
|||
|
|||
private final ConcurrentMap<String, Integer> tsKvDictionaryMap = new ConcurrentHashMap<>(); |
|||
|
|||
protected static final ReentrantLock tsCreationLock = new ReentrantLock(); |
|||
|
|||
@Override |
|||
public void migrate() throws Exception { |
|||
log.info("Performing migration of latest timeseries data from cassandra to SQL database ..."); |
|||
entityDatabaseSchemaService.createDatabaseSchema(false); |
|||
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { |
|||
conn.setAutoCommit(false); |
|||
for (CassandraToSqlTable table : tables) { |
|||
table.migrateToSql(cluster.getSession(), conn); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Unexpected error during ThingsBoard entities data migration!", e); |
|||
throw e; |
|||
} |
|||
entityDatabaseSchemaService.createDatabaseIndexes(); |
|||
} |
|||
|
|||
private List<CassandraToSqlTable> tables = Arrays.asList( |
|||
new CassandraToSqlTable("ts_kv_latest_cf", |
|||
idColumn("entity_id"), |
|||
stringColumn("key"), |
|||
bigintColumn("ts"), |
|||
booleanColumn("bool_v"), |
|||
stringColumn("str_v"), |
|||
bigintColumn("long_v"), |
|||
doubleColumn("dbl_v"), |
|||
jsonColumn("json_v")) { |
|||
|
|||
@Override |
|||
protected void batchInsert(List<CassandraToSqlColumnData[]> batchData, Connection conn) { |
|||
insertLatestTsRepository |
|||
.saveOrUpdate(batchData.stream().map(data -> getTsKvLatestEntity(data)).collect(Collectors.toList())); |
|||
} |
|||
|
|||
@Override |
|||
protected CassandraToSqlColumnData[] validateColumnData(CassandraToSqlColumnData[] data) { |
|||
return data; |
|||
} |
|||
}); |
|||
|
|||
private TsKvLatestEntity getTsKvLatestEntity(CassandraToSqlColumnData[] data) { |
|||
TsKvLatestEntity latestEntity = new TsKvLatestEntity(); |
|||
latestEntity.setEntityId(UUIDConverter.fromString(data[0].getValue())); |
|||
latestEntity.setKey(getOrSaveKeyId(data[1].getValue())); |
|||
latestEntity.setTs(Long.parseLong(data[2].getValue())); |
|||
|
|||
String strV = data[4].getValue(); |
|||
if (strV != null) { |
|||
latestEntity.setStrValue(strV); |
|||
} else { |
|||
Long longV = null; |
|||
try { |
|||
longV = Long.parseLong(data[5].getValue()); |
|||
} catch (Exception e) { |
|||
} |
|||
if (longV != null) { |
|||
latestEntity.setLongValue(longV); |
|||
} else { |
|||
Double doubleV = null; |
|||
try { |
|||
doubleV = Double.parseDouble(data[6].getValue()); |
|||
} catch (Exception e) { |
|||
} |
|||
if (doubleV != null) { |
|||
latestEntity.setDoubleValue(doubleV); |
|||
} else { |
|||
|
|||
String jsonV = data[7].getValue(); |
|||
if (StringUtils.isNoneEmpty(jsonV)) { |
|||
latestEntity.setJsonValue(jsonV); |
|||
} else { |
|||
Boolean boolV = null; |
|||
try { |
|||
boolV = Boolean.parseBoolean(data[3].getValue()); |
|||
} catch (Exception e) { |
|||
} |
|||
if (boolV != null) { |
|||
latestEntity.setBooleanValue(boolV); |
|||
} else { |
|||
log.warn("All values in key-value row are nullable "); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return latestEntity; |
|||
} |
|||
|
|||
protected Integer getOrSaveKeyId(String strKey) { |
|||
Integer keyId = tsKvDictionaryMap.get(strKey); |
|||
if (keyId == null) { |
|||
Optional<TsKvDictionary> tsKvDictionaryOptional; |
|||
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
|||
if (!tsKvDictionaryOptional.isPresent()) { |
|||
tsCreationLock.lock(); |
|||
try { |
|||
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
|||
if (!tsKvDictionaryOptional.isPresent()) { |
|||
TsKvDictionary tsKvDictionary = new TsKvDictionary(); |
|||
tsKvDictionary.setKey(strKey); |
|||
try { |
|||
TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); |
|||
tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); |
|||
keyId = saved.getKeyId(); |
|||
} catch (ConstraintViolationException e) { |
|||
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
|||
TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); |
|||
tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); |
|||
keyId = dictionary.getKeyId(); |
|||
} |
|||
} else { |
|||
keyId = tsKvDictionaryOptional.get().getKeyId(); |
|||
} |
|||
} finally { |
|||
tsCreationLock.unlock(); |
|||
} |
|||
} else { |
|||
keyId = tsKvDictionaryOptional.get().getKeyId(); |
|||
tsKvDictionaryMap.put(strKey, keyId); |
|||
} |
|||
} |
|||
return keyId; |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.install.migrate; |
|||
|
|||
public interface TsLatestMigrateService { |
|||
|
|||
void migrate() throws Exception; |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.query; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityCountQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
|
|||
import java.util.LinkedHashMap; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class DefaultEntityQueryService implements EntityQueryService { |
|||
|
|||
@Autowired |
|||
private EntityService entityService; |
|||
|
|||
@Autowired |
|||
private AlarmService alarmService; |
|||
|
|||
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") |
|||
private int maxEntitiesPerAlarmSubscription; |
|||
|
|||
@Override |
|||
public long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) { |
|||
return entityService.countEntitiesByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) { |
|||
return entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) { |
|||
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); |
|||
PageData<EntityData> entities = entityService.findEntityDataByQuery(securityUser.getTenantId(), |
|||
securityUser.getCustomerId(), entityDataQuery); |
|||
if (entities.getTotalElements() > 0) { |
|||
LinkedHashMap<EntityId, EntityData> entitiesMap = new LinkedHashMap<>(); |
|||
for (EntityData entityData : entities.getData()) { |
|||
entitiesMap.put(entityData.getEntityId(), entityData); |
|||
} |
|||
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(securityUser.getTenantId(), |
|||
securityUser.getCustomerId(), query, entitiesMap.keySet()); |
|||
for (AlarmData alarmData : alarms.getData()) { |
|||
EntityId entityId = alarmData.getEntityId(); |
|||
if (entityId != null) { |
|||
EntityData entityData = entitiesMap.get(entityId); |
|||
if (entityData != null) { |
|||
alarmData.getLatest().putAll(entityData.getLatest()); |
|||
} |
|||
} |
|||
} |
|||
return alarms; |
|||
} else { |
|||
return new PageData<>(); |
|||
} |
|||
} |
|||
|
|||
private EntityDataQuery buildEntityDataQuery(AlarmDataQuery query) { |
|||
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); |
|||
EntityDataSortOrder entitiesSortOrder; |
|||
if (sortOrder == null || sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) { |
|||
entitiesSortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY)); |
|||
} else { |
|||
entitiesSortOrder = sortOrder; |
|||
} |
|||
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder); |
|||
return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters()); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.query; |
|||
|
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityCountQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
|
|||
public interface EntityQueryService { |
|||
|
|||
long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query); |
|||
|
|||
PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query); |
|||
|
|||
PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query); |
|||
|
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.stats; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.actors.JsInvokeStats; |
|||
import org.thingsboard.server.common.stats.StatsCounter; |
|||
import org.thingsboard.server.common.stats.StatsFactory; |
|||
import org.thingsboard.server.common.stats.StatsType; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
|
|||
@Service |
|||
public class DefaultJsInvokeStats implements JsInvokeStats { |
|||
private static final String REQUESTS = "requests"; |
|||
private static final String RESPONSES = "responses"; |
|||
private static final String FAILURES = "failures"; |
|||
|
|||
private StatsCounter requestsCounter; |
|||
private StatsCounter responsesCounter; |
|||
private StatsCounter failuresCounter; |
|||
|
|||
@Autowired |
|||
private StatsFactory statsFactory; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
String key = StatsType.JS_INVOKE.getName(); |
|||
this.requestsCounter = statsFactory.createStatsCounter(key, REQUESTS); |
|||
this.responsesCounter = statsFactory.createStatsCounter(key, RESPONSES); |
|||
this.failuresCounter = statsFactory.createStatsCounter(key, FAILURES); |
|||
} |
|||
|
|||
@Override |
|||
public void incrementRequests(int amount) { |
|||
requestsCounter.add(amount); |
|||
} |
|||
|
|||
@Override |
|||
public void incrementResponses(int amount) { |
|||
responsesCounter.add(amount); |
|||
} |
|||
|
|||
@Override |
|||
public void incrementFailures(int amount) { |
|||
failuresCounter.add(amount); |
|||
} |
|||
|
|||
@Override |
|||
public int getRequests() { |
|||
return requestsCounter.get(); |
|||
} |
|||
|
|||
@Override |
|||
public int getResponses() { |
|||
return responsesCounter.get(); |
|||
} |
|||
|
|||
@Override |
|||
public int getFailures() { |
|||
return failuresCounter.get(); |
|||
} |
|||
|
|||
@Override |
|||
public void reset() { |
|||
requestsCounter.clear(); |
|||
responsesCounter.clear(); |
|||
failuresCounter.clear(); |
|||
} |
|||
} |
|||
@ -0,0 +1,480 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.subscription; |
|||
|
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.checkerframework.checker.nullness.qual.Nullable; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; |
|||
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.GetTsCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.LinkedHashMap; |
|||
import java.util.LinkedHashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutionException; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.ScheduledFuture; |
|||
import java.util.concurrent.ThreadFactory; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
@TbCoreComponent |
|||
@Service |
|||
public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService { |
|||
|
|||
private static final int DEFAULT_LIMIT = 100; |
|||
private final Map<String, Map<Integer, TbAbstractDataSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>(); |
|||
|
|||
@Autowired |
|||
private TelemetryWebSocketService wsService; |
|||
|
|||
@Autowired |
|||
private EntityService entityService; |
|||
|
|||
@Autowired |
|||
private AlarmService alarmService; |
|||
|
|||
@Autowired |
|||
private AttributesService attributesService; |
|||
|
|||
@Autowired |
|||
@Lazy |
|||
private TbLocalSubscriptionService localSubscriptionService; |
|||
|
|||
@Autowired |
|||
private TimeseriesService tsService; |
|||
|
|||
@Autowired |
|||
private TbServiceInfoProvider serviceInfoProvider; |
|||
|
|||
@Autowired |
|||
@Getter |
|||
private DbCallbackExecutorService dbCallbackExecutor; |
|||
|
|||
private ScheduledExecutorService scheduler; |
|||
|
|||
@Value("${database.ts.type}") |
|||
private String databaseTsType; |
|||
@Value("${server.ws.dynamic_page_link.refresh_interval:6}") |
|||
private long dynamicPageLinkRefreshInterval; |
|||
@Value("${server.ws.dynamic_page_link.refresh_pool_size:1}") |
|||
private int dynamicPageLinkRefreshPoolSize; |
|||
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") |
|||
private int maxEntitiesPerAlarmSubscription; |
|||
|
|||
private ExecutorService wsCallBackExecutor; |
|||
private boolean tsInSqlDB; |
|||
private String serviceId; |
|||
private SubscriptionServiceStatistics stats = new SubscriptionServiceStatistics(); |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
serviceId = serviceInfoProvider.getServiceId(); |
|||
wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-entity-sub-callback")); |
|||
tsInSqlDB = databaseTsType.equalsIgnoreCase("sql") || databaseTsType.equalsIgnoreCase("timescale"); |
|||
ThreadFactory tbThreadFactory = ThingsBoardThreadFactory.forName("ws-entity-sub-scheduler"); |
|||
if (dynamicPageLinkRefreshPoolSize == 1) { |
|||
scheduler = Executors.newSingleThreadScheduledExecutor(tbThreadFactory); |
|||
} else { |
|||
scheduler = Executors.newScheduledThreadPool(dynamicPageLinkRefreshPoolSize, tbThreadFactory); |
|||
} |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (wsCallBackExecutor != null) { |
|||
wsCallBackExecutor.shutdownNow(); |
|||
} |
|||
if (scheduler != null) { |
|||
scheduler.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void handleCmd(TelemetryWebSocketSessionRef session, EntityDataCmd cmd) { |
|||
TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); |
|||
if (ctx != null) { |
|||
log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd); |
|||
if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null || cmd.getHistoryCmd() != null) { |
|||
ctx.clearEntitySubscriptions(); |
|||
} |
|||
} else { |
|||
log.debug("[{}][{}] Creating new subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd); |
|||
ctx = createSubCtx(session, cmd); |
|||
} |
|||
ctx.setCurrentCmd(cmd); |
|||
if (cmd.getQuery() != null) { |
|||
if (ctx.getQuery() == null) { |
|||
log.debug("[{}][{}] Initializing data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery()); |
|||
} else { |
|||
log.debug("[{}][{}] Updating data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery()); |
|||
} |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
TenantId tenantId = ctx.getTenantId(); |
|||
CustomerId customerId = ctx.getCustomerId(); |
|||
EntityDataQuery query = ctx.getQuery(); |
|||
//Step 1. Update existing query with the contents of LatestValueCmd
|
|||
if (cmd.getLatestCmd() != null) { |
|||
cmd.getLatestCmd().getKeys().forEach(key -> { |
|||
if (!query.getLatestValues().contains(key)) { |
|||
query.getLatestValues().add(key); |
|||
} |
|||
}); |
|||
} |
|||
long start = System.currentTimeMillis(); |
|||
ctx.fetchData(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getRegularQueryInvocationCnt().incrementAndGet(); |
|||
stats.getRegularQueryTimeSpent().addAndGet(end - start); |
|||
ctx.cancelTasks(); |
|||
if (ctx.getQuery().getPageLink().isDynamic()) { |
|||
//TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached.
|
|||
TbEntityDataSubCtx finalCtx = ctx; |
|||
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( |
|||
() -> refreshDynamicQuery(tenantId, customerId, finalCtx), |
|||
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); |
|||
finalCtx.setRefreshTask(task); |
|||
} |
|||
} |
|||
ListenableFuture<TbEntityDataSubCtx> historyFuture; |
|||
if (cmd.getHistoryCmd() != null) { |
|||
log.trace("[{}][{}] Going to process history command: {}", session.getSessionId(), cmd.getCmdId(), cmd.getHistoryCmd()); |
|||
historyFuture = handleHistoryCmd(ctx, cmd.getHistoryCmd()); |
|||
} else { |
|||
historyFuture = Futures.immediateFuture(ctx); |
|||
} |
|||
Futures.addCallback(historyFuture, new FutureCallback<TbEntityDataSubCtx>() { |
|||
@Override |
|||
public void onSuccess(@Nullable TbEntityDataSubCtx theCtx) { |
|||
if (cmd.getLatestCmd() != null) { |
|||
handleLatestCmd(theCtx, cmd.getLatestCmd()); |
|||
} else if (cmd.getTsCmd() != null) { |
|||
handleTimeSeriesCmd(theCtx, cmd.getTsCmd()); |
|||
} else if (!theCtx.isInitialDataSent()) { |
|||
EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null); |
|||
wsService.sendWsMsg(theCtx.getSessionId(), update); |
|||
theCtx.setInitialDataSent(true); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.warn("[{}][{}] Failed to process command", session.getSessionId(), cmd.getCmdId()); |
|||
} |
|||
}, wsCallBackExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) { |
|||
TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); |
|||
if (ctx == null) { |
|||
log.debug("[{}][{}] Creating new alarm subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd); |
|||
ctx = createSubCtx(session, cmd); |
|||
} |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
AlarmDataQuery adq = ctx.getQuery(); |
|||
long start = System.currentTimeMillis(); |
|||
ctx.fetchData(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getRegularQueryInvocationCnt().incrementAndGet(); |
|||
stats.getRegularQueryTimeSpent().addAndGet(end - start); |
|||
List<EntityData> entities = ctx.getEntitiesData(); |
|||
ctx.cancelTasks(); |
|||
ctx.clearEntitySubscriptions(); |
|||
if (entities.isEmpty()) { |
|||
AlarmDataUpdate update = new AlarmDataUpdate(cmd.getCmdId(), new PageData<>(), null, 0, 0); |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
} else { |
|||
ctx.fetchAlarms(); |
|||
ctx.createSubscriptions(cmd.getQuery().getLatestValues(), true); |
|||
if (adq.getPageLink().getTimeWindow() > 0) { |
|||
TbAlarmDataSubCtx finalCtx = ctx; |
|||
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( |
|||
finalCtx::cleanupOldAlarms, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); |
|||
finalCtx.setRefreshTask(task); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void refreshDynamicQuery(TenantId tenantId, CustomerId customerId, TbEntityDataSubCtx finalCtx) { |
|||
try { |
|||
long start = System.currentTimeMillis(); |
|||
finalCtx.update(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getDynamicQueryInvocationCnt().incrementAndGet(); |
|||
stats.getDynamicQueryTimeSpent().addAndGet(end - start); |
|||
} catch (Exception e) { |
|||
log.warn("[{}][{}] Failed to refresh query", finalCtx.getSessionId(), finalCtx.getCmdId(), e); |
|||
} |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${server.ws.dynamic_page_link.stats:10000}") |
|||
public void printStats() { |
|||
int alarmQueryInvocationCntValue = stats.getAlarmQueryInvocationCnt().getAndSet(0); |
|||
long alarmQueryInvocationTimeValue = stats.getAlarmQueryTimeSpent().getAndSet(0); |
|||
int regularQueryInvocationCntValue = stats.getRegularQueryInvocationCnt().getAndSet(0); |
|||
long regularQueryInvocationTimeValue = stats.getRegularQueryTimeSpent().getAndSet(0); |
|||
int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0); |
|||
long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0); |
|||
long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count(); |
|||
if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0 || alarmQueryInvocationCntValue > 0) { |
|||
log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " + |
|||
"dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " + |
|||
"alarmQueryInvocationCnt = [{}], alarmQueryInvocationTime = [{}]", |
|||
regularQueryInvocationCntValue, regularQueryInvocationTimeValue, |
|||
dynamicQueryCnt, dynamicQueryInvocationCntValue, dynamicQueryInvocationTimeValue, |
|||
alarmQueryInvocationCntValue, alarmQueryInvocationTimeValue); |
|||
} |
|||
} |
|||
|
|||
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); |
|||
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId()); |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
sessionSubs.put(cmd.getCmdId(), ctx); |
|||
return ctx; |
|||
} |
|||
|
|||
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); |
|||
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
sessionSubs.put(cmd.getCmdId(), ctx); |
|||
return ctx; |
|||
} |
|||
|
|||
private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId); |
|||
if (sessionSubs != null) { |
|||
return (T) sessionSubs.get(cmdId); |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private ListenableFuture<TbEntityDataSubCtx> handleTimeSeriesCmd(TbEntityDataSubCtx ctx, TimeSeriesCmd cmd) { |
|||
log.debug("[{}][{}] Fetching time-series data for last {} ms for keys: ({})", ctx.getSessionId(), ctx.getCmdId(), cmd.getTimeWindow(), cmd.getKeys()); |
|||
return handleGetTsCmd(ctx, cmd, true); |
|||
} |
|||
|
|||
|
|||
private ListenableFuture<TbEntityDataSubCtx> handleHistoryCmd(TbEntityDataSubCtx ctx, EntityHistoryCmd cmd) { |
|||
log.debug("[{}][{}] Fetching history data for start {} and end {} ms for keys: ({})", ctx.getSessionId(), ctx.getCmdId(), cmd.getStartTs(), cmd.getEndTs(), cmd.getKeys()); |
|||
return handleGetTsCmd(ctx, cmd, false); |
|||
} |
|||
|
|||
private ListenableFuture<TbEntityDataSubCtx> handleGetTsCmd(TbEntityDataSubCtx ctx, GetTsCmd cmd, boolean subscribe) { |
|||
List<String> keys = cmd.getKeys(); |
|||
List<ReadTsKvQuery> finalTsKvQueryList; |
|||
List<ReadTsKvQuery> tsKvQueryList = cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery( |
|||
key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), cmd.getAgg() |
|||
)).collect(Collectors.toList()); |
|||
if (cmd.isFetchLatestPreviousPoint()) { |
|||
finalTsKvQueryList = new ArrayList<>(tsKvQueryList); |
|||
finalTsKvQueryList.addAll(cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery( |
|||
key, cmd.getStartTs() - TimeUnit.DAYS.toMillis(365), cmd.getStartTs(), cmd.getInterval(), 1, cmd.getAgg() |
|||
)).collect(Collectors.toList())); |
|||
} else { |
|||
finalTsKvQueryList = tsKvQueryList; |
|||
} |
|||
Map<EntityData, ListenableFuture<List<TsKvEntry>>> fetchResultMap = new HashMap<>(); |
|||
ctx.getData().getData().forEach(entityData -> fetchResultMap.put(entityData, |
|||
tsService.findAll(ctx.getTenantId(), entityData.getEntityId(), finalTsKvQueryList))); |
|||
return Futures.transform(Futures.allAsList(fetchResultMap.values()), f -> { |
|||
fetchResultMap.forEach((entityData, future) -> { |
|||
Map<String, List<TsValue>> keyData = new LinkedHashMap<>(); |
|||
cmd.getKeys().forEach(key -> keyData.put(key, new ArrayList<>())); |
|||
try { |
|||
List<TsKvEntry> entityTsData = future.get(); |
|||
if (entityTsData != null) { |
|||
entityTsData.forEach(entry -> keyData.get(entry.getKey()).add(new TsValue(entry.getTs(), entry.getValueAsString()))); |
|||
} |
|||
keyData.forEach((k, v) -> entityData.getTimeseries().put(k, v.toArray(new TsValue[v.size()]))); |
|||
if (cmd.isFetchLatestPreviousPoint()) { |
|||
entityData.getTimeseries().values().forEach(dataArray -> { |
|||
Arrays.sort(dataArray, (o1, o2) -> Long.compare(o2.getTs(), o1.getTs())); |
|||
}); |
|||
} |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e); |
|||
wsService.sendWsMsg(ctx.getSessionId(), |
|||
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to fetch historical data!")); |
|||
} |
|||
}); |
|||
EntityDataUpdate update; |
|||
if (!ctx.isInitialDataSent()) { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); |
|||
ctx.setInitialDataSent(true); |
|||
} else { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData()); |
|||
} |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
if (subscribe) { |
|||
ctx.createSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), false); |
|||
} |
|||
ctx.getData().getData().forEach(ed -> ed.getTimeseries().clear()); |
|||
return ctx; |
|||
}, wsCallBackExecutor); |
|||
} |
|||
|
|||
private void handleLatestCmd(TbEntityDataSubCtx ctx, LatestValueCmd latestCmd) { |
|||
log.trace("[{}][{}] Going to process latest command: {}", ctx.getSessionId(), ctx.getCmdId(), latestCmd); |
|||
//Fetch the latest values for telemetry keys (in case they are not copied from NoSQL to SQL DB in hybrid mode.
|
|||
if (!tsInSqlDB) { |
|||
log.trace("[{}][{}] Going to fetch missing latest values: {}", ctx.getSessionId(), ctx.getCmdId(), latestCmd); |
|||
List<String> allTsKeys = latestCmd.getKeys().stream() |
|||
.filter(key -> key.getType().equals(EntityKeyType.TIME_SERIES)) |
|||
.map(EntityKey::getKey).collect(Collectors.toList()); |
|||
|
|||
Map<EntityData, ListenableFuture<Map<String, TsValue>>> missingTelemetryFutures = new HashMap<>(); |
|||
for (EntityData entityData : ctx.getData().getData()) { |
|||
Map<EntityKeyType, Map<String, TsValue>> latestEntityData = entityData.getLatest(); |
|||
Map<String, TsValue> tsEntityData = latestEntityData.get(EntityKeyType.TIME_SERIES); |
|||
Set<String> missingTsKeys = new LinkedHashSet<>(allTsKeys); |
|||
if (tsEntityData != null) { |
|||
missingTsKeys.removeAll(tsEntityData.keySet()); |
|||
} else { |
|||
tsEntityData = new HashMap<>(); |
|||
latestEntityData.put(EntityKeyType.TIME_SERIES, tsEntityData); |
|||
} |
|||
|
|||
ListenableFuture<List<TsKvEntry>> missingTsData = tsService.findLatest(ctx.getTenantId(), entityData.getEntityId(), missingTsKeys); |
|||
missingTelemetryFutures.put(entityData, Futures.transform(missingTsData, this::toTsValue, MoreExecutors.directExecutor())); |
|||
} |
|||
Futures.addCallback(Futures.allAsList(missingTelemetryFutures.values()), new FutureCallback<List<Map<String, TsValue>>>() { |
|||
@Override |
|||
public void onSuccess(@Nullable List<Map<String, TsValue>> result) { |
|||
missingTelemetryFutures.forEach((key, value) -> { |
|||
try { |
|||
key.getLatest().get(EntityKeyType.TIME_SERIES).putAll(value.get()); |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
log.warn("[{}][{}] Failed to lookup latest telemetry: {}:{}", ctx.getSessionId(), ctx.getCmdId(), key.getEntityId(), allTsKeys, e); |
|||
} |
|||
}); |
|||
EntityDataUpdate update; |
|||
if (!ctx.isInitialDataSent()) { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); |
|||
ctx.setInitialDataSent(true); |
|||
} else { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData()); |
|||
} |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
ctx.createSubscriptions(latestCmd.getKeys(), true); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.warn("[{}][{}] Failed to process websocket command: {}:{}", ctx.getSessionId(), ctx.getCmdId(), ctx.getQuery(), latestCmd, t); |
|||
wsService.sendWsMsg(ctx.getSessionId(), |
|||
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to process websocket command!")); |
|||
} |
|||
}, wsCallBackExecutor); |
|||
} else { |
|||
if (!ctx.isInitialDataSent()) { |
|||
EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
ctx.setInitialDataSent(true); |
|||
} |
|||
ctx.createSubscriptions(latestCmd.getKeys(), true); |
|||
} |
|||
} |
|||
|
|||
private Map<String, TsValue> toTsValue(List<TsKvEntry> data) { |
|||
return data.stream().collect(Collectors.toMap(TsKvEntry::getKey, value -> new TsValue(value.getTs(), value.getValueAsString()))); |
|||
} |
|||
|
|||
@Override |
|||
public void cancelSubscription(String sessionId, UnsubscribeCmd cmd) { |
|||
cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId())); |
|||
} |
|||
|
|||
private void cleanupAndCancel(TbAbstractDataSubCtx ctx) { |
|||
if (ctx != null) { |
|||
ctx.cancelTasks(); |
|||
ctx.clearEntitySubscriptions(); |
|||
ctx.clearDynamicValueSubscriptions(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void cancelAllSessionSubscriptions(String sessionId) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId); |
|||
if (sessionSubs != null) { |
|||
sessionSubs.values().stream().filter(sub -> sub instanceof TbEntityDataSubCtx).map(sub -> (TbEntityDataSubCtx) sub).forEach(this::cleanupAndCancel); |
|||
} |
|||
} |
|||
|
|||
private int getLimit(int limit) { |
|||
return limit == 0 ? DEFAULT_LIMIT : limit; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.subscription; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
|
|||
@Data |
|||
public class SubscriptionServiceStatistics { |
|||
private AtomicInteger alarmQueryInvocationCnt = new AtomicInteger(); |
|||
private AtomicInteger regularQueryInvocationCnt = new AtomicInteger(); |
|||
private AtomicInteger dynamicQueryInvocationCnt = new AtomicInteger(); |
|||
private AtomicLong alarmQueryTimeSpent = new AtomicLong(); |
|||
private AtomicLong regularQueryTimeSpent = new AtomicLong(); |
|||
private AtomicLong dynamicQueryTimeSpent = new AtomicLong(); |
|||
} |
|||
@ -0,0 +1,469 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.subscription; |
|||
|
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AbstractDataQuery; |
|||
import org.thingsboard.server.common.data.query.ComplexFilterPredicate; |
|||
import org.thingsboard.server.common.data.query.DynamicValue; |
|||
import org.thingsboard.server.common.data.query.DynamicValueSourceType; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.FilterPredicateType; |
|||
import org.thingsboard.server.common.data.query.KeyFilter; |
|||
import org.thingsboard.server.common.data.query.KeyFilterPredicate; |
|||
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
|||
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ExecutionException; |
|||
import java.util.concurrent.ScheduledFuture; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> { |
|||
|
|||
protected final String serviceId; |
|||
protected final SubscriptionServiceStatistics stats; |
|||
protected final TelemetryWebSocketService wsService; |
|||
protected final EntityService entityService; |
|||
protected final TbLocalSubscriptionService localSubscriptionService; |
|||
protected final AttributesService attributesService; |
|||
protected final TelemetryWebSocketSessionRef sessionRef; |
|||
protected final int cmdId; |
|||
protected final Map<Integer, EntityId> subToEntityIdMap; |
|||
protected final Set<Integer> subToDynamicValueKeySet; |
|||
@Getter |
|||
protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues; |
|||
@Getter |
|||
protected PageData<EntityData> data; |
|||
@Getter |
|||
@Setter |
|||
protected T query; |
|||
@Setter |
|||
protected volatile ScheduledFuture<?> refreshTask; |
|||
|
|||
public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService, |
|||
EntityService entityService, TbLocalSubscriptionService localSubscriptionService, |
|||
AttributesService attributesService, SubscriptionServiceStatistics stats, |
|||
TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
|||
this.serviceId = serviceId; |
|||
this.wsService = wsService; |
|||
this.entityService = entityService; |
|||
this.localSubscriptionService = localSubscriptionService; |
|||
this.attributesService = attributesService; |
|||
this.stats = stats; |
|||
this.sessionRef = sessionRef; |
|||
this.cmdId = cmdId; |
|||
this.subToEntityIdMap = new HashMap<>(); |
|||
this.subToDynamicValueKeySet = new HashSet<>(); |
|||
this.dynamicValues = new HashMap<>(); |
|||
} |
|||
|
|||
public void setAndResolveQuery(T query) { |
|||
dynamicValues.clear(); |
|||
this.query = query; |
|||
if (query.getKeyFilters() != null) { |
|||
for (KeyFilter filter : query.getKeyFilters()) { |
|||
registerDynamicValues(filter.getPredicate()); |
|||
} |
|||
} |
|||
resolve(getTenantId(), getCustomerId(), getUserId()); |
|||
} |
|||
|
|||
public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) { |
|||
List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>(); |
|||
for (DynamicValueKey key : dynamicValues.keySet()) { |
|||
switch (key.getSourceType()) { |
|||
case CURRENT_TENANT: |
|||
futures.add(resolveEntityValue(tenantId, tenantId, key)); |
|||
break; |
|||
case CURRENT_CUSTOMER: |
|||
if (customerId != null && !customerId.isNullUid()) { |
|||
futures.add(resolveEntityValue(tenantId, customerId, key)); |
|||
} |
|||
break; |
|||
case CURRENT_USER: |
|||
if (userId != null && !userId.isNullUid()) { |
|||
futures.add(resolveEntityValue(tenantId, userId, key)); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
try { |
|||
Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>(); |
|||
for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) { |
|||
tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub); |
|||
} |
|||
for (EntityId entityId : tmpSubMap.keySet()) { |
|||
Map<String, Long> keyStates = new HashMap<>(); |
|||
Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId); |
|||
dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs())); |
|||
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); |
|||
TbAttributeSubscription sub = TbAttributeSubscription.builder() |
|||
.serviceId(serviceId) |
|||
.sessionId(sessionRef.getSessionId()) |
|||
.subscriptionId(subIdx) |
|||
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
|||
.entityId(entityId) |
|||
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap)) |
|||
.allKeys(false) |
|||
.keyStates(keyStates) |
|||
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE) |
|||
.build(); |
|||
subToDynamicValueKeySet.add(subIdx); |
|||
localSubscriptionService.addSubscription(sub); |
|||
} |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet()); |
|||
} |
|||
|
|||
} |
|||
|
|||
private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, |
|||
Map<String, DynamicValueKeySub> dynamicValueKeySubMap) { |
|||
Map<String, TsValue> latestUpdate = new HashMap<>(); |
|||
subscriptionUpdate.getData().forEach((k, v) -> { |
|||
Object[] data = (Object[]) v.get(0); |
|||
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); |
|||
}); |
|||
|
|||
boolean invalidateFilter = false; |
|||
for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) { |
|||
String k = entry.getKey(); |
|||
TsValue tsValue = entry.getValue(); |
|||
DynamicValueKeySub sub = dynamicValueKeySubMap.get(k); |
|||
if (sub.updateValue(tsValue)) { |
|||
invalidateFilter = true; |
|||
updateDynamicValuesByKey(sub, tsValue); |
|||
} |
|||
} |
|||
|
|||
if (invalidateFilter) { |
|||
update(); |
|||
} |
|||
} |
|||
|
|||
public void fetchData() { |
|||
this.data = findEntityData(); |
|||
} |
|||
|
|||
protected PageData<EntityData> findEntityData() { |
|||
PageData<EntityData> result = entityService.findEntityDataByQuery(getTenantId(), getCustomerId(), buildEntityDataQuery()); |
|||
if (log.isTraceEnabled()) { |
|||
result.getData().forEach(ed -> { |
|||
log.trace("[{}][{}] EntityData: {}", getSessionId(), getCmdId(), ed); |
|||
}); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
protected synchronized void update() { |
|||
long start = System.currentTimeMillis(); |
|||
PageData<EntityData> newData = findEntityData(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getRegularQueryInvocationCnt().incrementAndGet(); |
|||
stats.getRegularQueryTimeSpent().addAndGet(end - start); |
|||
Map<EntityId, EntityData> oldDataMap; |
|||
if (data != null && !data.getData().isEmpty()) { |
|||
oldDataMap = data.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a, b) -> a)); |
|||
} else { |
|||
oldDataMap = Collections.emptyMap(); |
|||
} |
|||
Map<EntityId, EntityData> newDataMap = newData.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a,b)-> a)); |
|||
if (oldDataMap.size() == newDataMap.size() && oldDataMap.keySet().equals(newDataMap.keySet())) { |
|||
log.trace("[{}][{}] No updates to entity data found", sessionRef.getSessionId(), cmdId); |
|||
} else { |
|||
this.data = newData; |
|||
doUpdate(newDataMap); |
|||
} |
|||
} |
|||
|
|||
protected abstract void doUpdate(Map<EntityId, EntityData> newDataMap); |
|||
|
|||
protected abstract EntityDataQuery buildEntityDataQuery(); |
|||
|
|||
public List<EntityData> getEntitiesData() { |
|||
return data.getData(); |
|||
} |
|||
|
|||
@Data |
|||
private static class DynamicValueKeySub { |
|||
private final DynamicValueKey key; |
|||
private final EntityId entityId; |
|||
private long lastUpdateTs; |
|||
private String lastUpdateValue; |
|||
|
|||
boolean updateValue(TsValue value) { |
|||
if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) { |
|||
this.lastUpdateTs = value.getTs(); |
|||
this.lastUpdateValue = value.getValue(); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) { |
|||
ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId, |
|||
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute()); |
|||
return Futures.transform(entry, attributeOpt -> { |
|||
DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId); |
|||
if (attributeOpt.isPresent()) { |
|||
AttributeKvEntry attribute = attributeOpt.get(); |
|||
sub.setLastUpdateTs(attribute.getLastUpdateTs()); |
|||
sub.setLastUpdateValue(attribute.getValueAsString()); |
|||
updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString())); |
|||
} |
|||
return sub; |
|||
}, MoreExecutors.directExecutor()); |
|||
} |
|||
|
|||
private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) { |
|||
DynamicValueKey dvk = sub.getKey(); |
|||
switch (dvk.getPredicateType()) { |
|||
case STRING: |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue())); |
|||
break; |
|||
case NUMERIC: |
|||
try { |
|||
Double dValue = Double.parseDouble(tsValue.getValue()); |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue)); |
|||
} catch (NumberFormatException e) { |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null)); |
|||
} |
|||
break; |
|||
case BOOLEAN: |
|||
Boolean bValue = Boolean.parseBoolean(tsValue.getValue()); |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue)); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private void registerDynamicValues(KeyFilterPredicate predicate) { |
|||
switch (predicate.getType()) { |
|||
case STRING: |
|||
case NUMERIC: |
|||
case BOOLEAN: |
|||
Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate); |
|||
if (value.isPresent()) { |
|||
DynamicValue dynamicValue = value.get(); |
|||
DynamicValueKey key = new DynamicValueKey( |
|||
predicate.getType(), |
|||
dynamicValue.getSourceType(), |
|||
dynamicValue.getSourceAttribute()); |
|||
dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue); |
|||
} |
|||
break; |
|||
case COMPLEX: |
|||
((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues); |
|||
} |
|||
} |
|||
|
|||
private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) { |
|||
if (predicate.getValue().getUserValue() == null) { |
|||
return Optional.ofNullable(predicate.getValue().getDynamicValue()); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
public String getSessionId() { |
|||
return sessionRef.getSessionId(); |
|||
} |
|||
|
|||
public TenantId getTenantId() { |
|||
return sessionRef.getSecurityCtx().getTenantId(); |
|||
} |
|||
|
|||
public CustomerId getCustomerId() { |
|||
return sessionRef.getSecurityCtx().getCustomerId(); |
|||
} |
|||
|
|||
public UserId getUserId() { |
|||
return sessionRef.getSecurityCtx().getId(); |
|||
} |
|||
|
|||
public void clearEntitySubscriptions() { |
|||
if (subToEntityIdMap != null) { |
|||
for (Integer subId : subToEntityIdMap.keySet()) { |
|||
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); |
|||
} |
|||
subToEntityIdMap.clear(); |
|||
} |
|||
} |
|||
|
|||
public void clearDynamicValueSubscriptions() { |
|||
if (subToDynamicValueKeySet != null) { |
|||
for (Integer subId : subToDynamicValueKeySet) { |
|||
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); |
|||
} |
|||
subToDynamicValueKeySet.clear(); |
|||
} |
|||
} |
|||
|
|||
public void setRefreshTask(ScheduledFuture<?> task) { |
|||
this.refreshTask = task; |
|||
} |
|||
|
|||
public void cancelTasks() { |
|||
if (this.refreshTask != null) { |
|||
log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId); |
|||
this.refreshTask.cancel(true); |
|||
} |
|||
} |
|||
|
|||
public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) { |
|||
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys); |
|||
for (EntityData entityData : data.getData()) { |
|||
List<TbSubscription> entitySubscriptions = addSubscriptions(entityData, keysByType, resultToLatestValues); |
|||
entitySubscriptions.forEach(localSubscriptionService::addSubscription); |
|||
} |
|||
} |
|||
|
|||
protected Map<EntityKeyType, List<EntityKey>> getEntityKeyByTypeMap(List<EntityKey> keys) { |
|||
Map<EntityKeyType, List<EntityKey>> keysByType = new HashMap<>(); |
|||
keys.forEach(key -> keysByType.computeIfAbsent(key.getType(), k -> new ArrayList<>()).add(key)); |
|||
return keysByType; |
|||
} |
|||
|
|||
protected List<TbSubscription> addSubscriptions(EntityData entityData, Map<EntityKeyType, List<EntityKey>> keysByType, boolean resultToLatestValues) { |
|||
List<TbSubscription> subscriptionList = new ArrayList<>(); |
|||
keysByType.forEach((keysType, keysList) -> { |
|||
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); |
|||
subToEntityIdMap.put(subIdx, entityData.getEntityId()); |
|||
switch (keysType) { |
|||
case TIME_SERIES: |
|||
subscriptionList.add(createTsSub(entityData, subIdx, keysList, resultToLatestValues)); |
|||
break; |
|||
case CLIENT_ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.CLIENT_SCOPE, keysList)); |
|||
break; |
|||
case SHARED_ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.SHARED_SCOPE, keysList)); |
|||
break; |
|||
case SERVER_ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.SERVER_SCOPE, keysList)); |
|||
break; |
|||
case ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.ANY_SCOPE, keysList)); |
|||
break; |
|||
} |
|||
}); |
|||
return subscriptionList; |
|||
} |
|||
|
|||
private TbSubscription createAttrSub(EntityData entityData, int subIdx, EntityKeyType keysType, TbAttributeSubscriptionScope scope, List<EntityKey> subKeys) { |
|||
Map<String, Long> keyStates = buildKeyStats(entityData, keysType, subKeys); |
|||
log.trace("[{}][{}][{}] Creating attributes subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates); |
|||
return TbAttributeSubscription.builder() |
|||
.serviceId(serviceId) |
|||
.sessionId(sessionRef.getSessionId()) |
|||
.subscriptionId(subIdx) |
|||
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
|||
.entityId(entityData.getEntityId()) |
|||
.updateConsumer((s, subscriptionUpdate) -> sendWsMsg(s, subscriptionUpdate, keysType)) |
|||
.allKeys(false) |
|||
.keyStates(keyStates) |
|||
.scope(scope) |
|||
.build(); |
|||
} |
|||
|
|||
private TbSubscription createTsSub(EntityData entityData, int subIdx, List<EntityKey> subKeys, boolean resultToLatestValues) { |
|||
Map<String, Long> keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys); |
|||
if (entityData.getTimeseries() != null) { |
|||
entityData.getTimeseries().forEach((k, v) -> { |
|||
long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L); |
|||
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, ts); |
|||
keyStates.put(k, ts); |
|||
}); |
|||
} |
|||
log.trace("[{}][{}][{}] Creating time-series subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates); |
|||
return TbTimeseriesSubscription.builder() |
|||
.serviceId(serviceId) |
|||
.sessionId(sessionRef.getSessionId()) |
|||
.subscriptionId(subIdx) |
|||
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
|||
.entityId(entityData.getEntityId()) |
|||
.updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) |
|||
.allKeys(false) |
|||
.keyStates(keyStates) |
|||
.build(); |
|||
} |
|||
|
|||
private void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) { |
|||
sendWsMsg(sessionId, subscriptionUpdate, keyType, true); |
|||
} |
|||
|
|||
private Map<String, Long> buildKeyStats(EntityData entityData, EntityKeyType keysType, List<EntityKey> subKeys) { |
|||
Map<String, Long> keyStates = new HashMap<>(); |
|||
subKeys.forEach(key -> keyStates.put(key.getKey(), 0L)); |
|||
if (entityData.getLatest() != null) { |
|||
Map<String, TsValue> currentValues = entityData.getLatest().get(keysType); |
|||
if (currentValues != null) { |
|||
currentValues.forEach((k, v) -> { |
|||
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs()); |
|||
keyStates.put(k, v.getTs()); |
|||
}); |
|||
} |
|||
} |
|||
return keyStates; |
|||
} |
|||
|
|||
abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues); |
|||
|
|||
@Data |
|||
private static class DynamicValueKey { |
|||
@Getter |
|||
private final FilterPredicateType predicateType; |
|||
@Getter |
|||
private final DynamicValueSourceType sourceType; |
|||
@Getter |
|||
private final String sourceAttribute; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,309 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.subscription; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataPageLink; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; |
|||
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; |
|||
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
import java.util.LinkedHashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> { |
|||
|
|||
private final AlarmService alarmService; |
|||
@Getter |
|||
@Setter |
|||
private final LinkedHashMap<EntityId, EntityData> entitiesMap; |
|||
@Getter |
|||
@Setter |
|||
private final HashMap<AlarmId, AlarmData> alarmsMap; |
|||
|
|||
private final int maxEntitiesPerAlarmSubscription; |
|||
|
|||
@Getter |
|||
@Setter |
|||
private PageData<AlarmData> alarms; |
|||
@Getter |
|||
@Setter |
|||
private boolean tooManyEntities; |
|||
|
|||
public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService, |
|||
EntityService entityService, TbLocalSubscriptionService localSubscriptionService, |
|||
AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService, |
|||
TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerAlarmSubscription) { |
|||
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); |
|||
this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription; |
|||
this.alarmService = alarmService; |
|||
this.entitiesMap = new LinkedHashMap<>(); |
|||
this.alarmsMap = new HashMap<>(); |
|||
} |
|||
|
|||
public void fetchAlarms() { |
|||
AlarmDataUpdate update; |
|||
if (!entitiesMap.isEmpty()) { |
|||
long start = System.currentTimeMillis(); |
|||
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), |
|||
query, getOrderedEntityIds()); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getAlarmQueryInvocationCnt().incrementAndGet(); |
|||
stats.getAlarmQueryTimeSpent().addAndGet(end - start); |
|||
alarms = setAndMergeAlarmsData(alarms); |
|||
update = new AlarmDataUpdate(cmdId, alarms, null, maxEntitiesPerAlarmSubscription, data.getTotalElements()); |
|||
} else { |
|||
update = new AlarmDataUpdate(cmdId, new PageData<>(), null, maxEntitiesPerAlarmSubscription, data.getTotalElements()); |
|||
} |
|||
wsService.sendWsMsg(getSessionId(), update); |
|||
} |
|||
|
|||
public void fetchData() { |
|||
super.fetchData(); |
|||
entitiesMap.clear(); |
|||
tooManyEntities = data.hasNext(); |
|||
for (EntityData entityData : data.getData()) { |
|||
entitiesMap.put(entityData.getEntityId(), entityData); |
|||
} |
|||
} |
|||
|
|||
public Collection<EntityId> getOrderedEntityIds() { |
|||
return entitiesMap.keySet(); |
|||
} |
|||
|
|||
public PageData<AlarmData> setAndMergeAlarmsData(PageData<AlarmData> alarms) { |
|||
this.alarms = alarms; |
|||
for (AlarmData alarmData : alarms.getData()) { |
|||
EntityId entityId = alarmData.getEntityId(); |
|||
if (entityId != null) { |
|||
EntityData entityData = entitiesMap.get(entityId); |
|||
if (entityData != null) { |
|||
alarmData.getLatest().putAll(entityData.getLatest()); |
|||
} |
|||
} |
|||
} |
|||
alarmsMap.clear(); |
|||
alarmsMap.putAll(alarms.getData().stream().collect(Collectors.toMap(AlarmData::getId, Function.identity(), (a, b) -> a))); |
|||
return this.alarms; |
|||
} |
|||
|
|||
@Override |
|||
public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) { |
|||
super.createSubscriptions(keys, resultToLatestValues); |
|||
createAlarmSubscriptions(); |
|||
} |
|||
|
|||
public void createAlarmSubscriptions() { |
|||
AlarmDataPageLink pageLink = query.getPageLink(); |
|||
long startTs = System.currentTimeMillis() - pageLink.getTimeWindow(); |
|||
for (EntityData entityData : entitiesMap.values()) { |
|||
createAlarmSubscriptionForEntity(pageLink, startTs, entityData); |
|||
} |
|||
} |
|||
|
|||
private void createAlarmSubscriptionForEntity(AlarmDataPageLink pageLink, long startTs, EntityData entityData) { |
|||
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); |
|||
subToEntityIdMap.put(subIdx, entityData.getEntityId()); |
|||
log.trace("[{}][{}][{}] Creating alarms subscription for [{}] with query: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), pageLink); |
|||
TbAlarmsSubscription subscription = TbAlarmsSubscription.builder() |
|||
.serviceId(serviceId) |
|||
.sessionId(sessionRef.getSessionId()) |
|||
.subscriptionId(subIdx) |
|||
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
|||
.entityId(entityData.getEntityId()) |
|||
.updateConsumer(this::sendWsMsg) |
|||
.ts(startTs) |
|||
.build(); |
|||
localSubscriptionService.addSubscription(subscription); |
|||
} |
|||
|
|||
@Override |
|||
void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues) { |
|||
EntityId entityId = subToEntityIdMap.get(subscriptionUpdate.getSubscriptionId()); |
|||
if (entityId != null) { |
|||
Map<String, TsValue> latestUpdate = new HashMap<>(); |
|||
subscriptionUpdate.getData().forEach((k, v) -> { |
|||
Object[] data = (Object[]) v.get(0); |
|||
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); |
|||
}); |
|||
EntityData entityData = entitiesMap.get(entityId); |
|||
entityData.getLatest().computeIfAbsent(keyType, tmp -> new HashMap<>()).putAll(latestUpdate); |
|||
log.trace("[{}][{}][{}][{}] Received subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), keyType, subscriptionUpdate); |
|||
List<AlarmData> update = alarmsMap.values().stream().filter(alarm -> entityId.equals(alarm.getEntityId())).map(alarm -> { |
|||
alarm.getLatest().computeIfAbsent(keyType, tmp -> new HashMap<>()).putAll(latestUpdate); |
|||
return alarm; |
|||
}).collect(Collectors.toList()); |
|||
wsService.sendWsMsg(sessionId, new AlarmDataUpdate(cmdId, null, update, maxEntitiesPerAlarmSubscription, data.getTotalElements())); |
|||
} else { |
|||
log.trace("[{}][{}][{}][{}] Received stale subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), keyType, subscriptionUpdate); |
|||
} |
|||
} |
|||
|
|||
private void sendWsMsg(String sessionId, AlarmSubscriptionUpdate subscriptionUpdate) { |
|||
Alarm alarm = subscriptionUpdate.getAlarm(); |
|||
AlarmId alarmId = alarm.getId(); |
|||
if (subscriptionUpdate.isAlarmDeleted()) { |
|||
Alarm deleted = alarmsMap.remove(alarmId); |
|||
if (deleted != null) { |
|||
fetchAlarms(); |
|||
} |
|||
} else { |
|||
AlarmData current = alarmsMap.get(alarmId); |
|||
boolean onCurrentPage = current != null; |
|||
boolean matchesFilter = filter(alarm); |
|||
if (onCurrentPage) { |
|||
if (matchesFilter) { |
|||
AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId()); |
|||
updated.getLatest().putAll(current.getLatest()); |
|||
alarmsMap.put(alarmId, updated); |
|||
wsService.sendWsMsg(sessionId, new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements())); |
|||
} else { |
|||
fetchAlarms(); |
|||
} |
|||
} else if (matchesFilter && query.getPageLink().getPage() == 0) { |
|||
fetchAlarms(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void cleanupOldAlarms() { |
|||
long expTime = System.currentTimeMillis() - query.getPageLink().getTimeWindow(); |
|||
boolean shouldRefresh = false; |
|||
for (AlarmData alarmData : alarms.getData()) { |
|||
if (alarmData.getCreatedTime() < expTime) { |
|||
shouldRefresh = true; |
|||
break; |
|||
} |
|||
} |
|||
if (shouldRefresh) { |
|||
fetchAlarms(); |
|||
} |
|||
} |
|||
|
|||
private boolean filter(Alarm alarm) { |
|||
AlarmDataPageLink filter = query.getPageLink(); |
|||
long startTs = System.currentTimeMillis() - filter.getTimeWindow(); |
|||
if (alarm.getCreatedTime() < startTs) { |
|||
//Skip update that does not match time window.
|
|||
return false; |
|||
} |
|||
if (filter.getTypeList() != null && !filter.getTypeList().isEmpty() && !filter.getTypeList().contains(alarm.getType())) { |
|||
return false; |
|||
} |
|||
if (filter.getSeverityList() != null && !filter.getSeverityList().isEmpty()) { |
|||
if (!filter.getSeverityList().contains(alarm.getSeverity())) { |
|||
return false; |
|||
} |
|||
} |
|||
if (filter.getStatusList() != null && !filter.getStatusList().isEmpty()) { |
|||
boolean matches = false; |
|||
for (AlarmSearchStatus status : filter.getStatusList()) { |
|||
if (status.getStatuses().contains(alarm.getStatus())) { |
|||
matches = true; |
|||
break; |
|||
} |
|||
} |
|||
if (!matches) { |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
protected synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) { |
|||
entitiesMap.clear(); |
|||
tooManyEntities = data.hasNext(); |
|||
for (EntityData entityData : data.getData()) { |
|||
entitiesMap.put(entityData.getEntityId(), entityData); |
|||
} |
|||
fetchAlarms(); |
|||
List<Integer> subIdsToCancel = new ArrayList<>(); |
|||
List<TbSubscription> subsToAdd = new ArrayList<>(); |
|||
Set<EntityId> currentSubs = new HashSet<>(); |
|||
subToEntityIdMap.forEach((subId, entityId) -> { |
|||
if (!newDataMap.containsKey(entityId)) { |
|||
subIdsToCancel.add(subId); |
|||
} else { |
|||
currentSubs.add(entityId); |
|||
} |
|||
}); |
|||
log.trace("[{}][{}] Subscriptions that are invalid: {}", sessionRef.getSessionId(), cmdId, subIdsToCancel); |
|||
subIdsToCancel.forEach(subToEntityIdMap::remove); |
|||
List<EntityData> newSubsList = newDataMap.entrySet().stream().filter(entry -> !currentSubs.contains(entry.getKey())).map(Map.Entry::getValue).collect(Collectors.toList()); |
|||
if (!newSubsList.isEmpty()) { |
|||
List<EntityKey> keys = query.getLatestValues(); |
|||
if (keys != null && !keys.isEmpty()) { |
|||
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys); |
|||
newSubsList.forEach( |
|||
entity -> { |
|||
log.trace("[{}][{}] Found new subscription for entity: {}", sessionRef.getSessionId(), cmdId, entity.getEntityId()); |
|||
subsToAdd.addAll(addSubscriptions(entity, keysByType, true)); |
|||
} |
|||
); |
|||
} |
|||
long startTs = System.currentTimeMillis() - query.getPageLink().getTimeWindow(); |
|||
newSubsList.forEach(entity -> createAlarmSubscriptionForEntity(query.getPageLink(), startTs, entity)); |
|||
} |
|||
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId)); |
|||
subsToAdd.forEach(localSubscriptionService::addSubscription); |
|||
} |
|||
|
|||
@Override |
|||
protected EntityDataQuery buildEntityDataQuery() { |
|||
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); |
|||
EntityDataSortOrder entitiesSortOrder; |
|||
if (sortOrder == null || sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) { |
|||
entitiesSortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY)); |
|||
} else { |
|||
entitiesSortOrder = sortOrder; |
|||
} |
|||
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder); |
|||
return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters()); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.subscription; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; |
|||
|
|||
import java.util.List; |
|||
import java.util.function.BiConsumer; |
|||
|
|||
public class TbAlarmsSubscription extends TbSubscription<AlarmSubscriptionUpdate> { |
|||
|
|||
@Getter |
|||
private final long ts; |
|||
|
|||
@Builder |
|||
public TbAlarmsSubscription(String serviceId, String sessionId, int subscriptionId, TenantId tenantId, EntityId entityId, |
|||
BiConsumer<String, AlarmSubscriptionUpdate> updateConsumer, long ts) { |
|||
super(serviceId, sessionId, subscriptionId, tenantId, entityId, TbSubscriptionType.ALARMS, updateConsumer); |
|||
this.ts = ts; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object o) { |
|||
return super.equals(o); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return super.hashCode(); |
|||
} |
|||
} |
|||
@ -0,0 +1,224 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.subscription; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; |
|||
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.Comparator; |
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ScheduledFuture; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> { |
|||
|
|||
@Getter |
|||
@Setter |
|||
private TimeSeriesCmd tsCmd; |
|||
@Getter |
|||
@Setter |
|||
private boolean initialDataSent; |
|||
private TimeSeriesCmd curTsCmd; |
|||
private LatestValueCmd latestValueCmd; |
|||
|
|||
public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, |
|||
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, |
|||
SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
|||
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); |
|||
} |
|||
|
|||
@Override |
|||
protected void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues) { |
|||
EntityId entityId = subToEntityIdMap.get(subscriptionUpdate.getSubscriptionId()); |
|||
if (entityId != null) { |
|||
log.trace("[{}][{}][{}][{}] Received subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), keyType, subscriptionUpdate); |
|||
if (resultToLatestValues) { |
|||
sendLatestWsMsg(entityId, sessionId, subscriptionUpdate, keyType); |
|||
} else { |
|||
sendTsWsMsg(entityId, sessionId, subscriptionUpdate, keyType); |
|||
} |
|||
} else { |
|||
log.trace("[{}][{}][{}][{}] Received stale subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), keyType, subscriptionUpdate); |
|||
} |
|||
} |
|||
|
|||
private void sendLatestWsMsg(EntityId entityId, String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) { |
|||
Map<String, TsValue> latestUpdate = new HashMap<>(); |
|||
subscriptionUpdate.getData().forEach((k, v) -> { |
|||
Object[] data = (Object[]) v.get(0); |
|||
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); |
|||
}); |
|||
EntityData entityData = getDataForEntity(entityId); |
|||
if (entityData != null && entityData.getLatest() != null) { |
|||
Map<String, TsValue> latestCtxValues = entityData.getLatest().get(keyType); |
|||
log.trace("[{}][{}][{}] Going to compare update with {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), latestCtxValues); |
|||
if (latestCtxValues != null) { |
|||
latestCtxValues.forEach((k, v) -> { |
|||
TsValue update = latestUpdate.get(k); |
|||
if (update != null) { |
|||
//Ignore notifications about deleted keys
|
|||
if (!(update.getTs() == 0 && (update.getValue() == null || update.getValue().isEmpty()))) { |
|||
if (update.getTs() < v.getTs()) { |
|||
log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); |
|||
latestUpdate.remove(k); |
|||
} else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) { |
|||
log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); |
|||
latestUpdate.remove(k); |
|||
} |
|||
} else { |
|||
log.trace("[{}][{}][{}] Received deleted notification for: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k); |
|||
} |
|||
} |
|||
}); |
|||
//Setting new values
|
|||
latestUpdate.forEach(latestCtxValues::put); |
|||
} |
|||
} |
|||
if (!latestUpdate.isEmpty()) { |
|||
Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(keyType, latestUpdate); |
|||
entityData = new EntityData(entityId, latestMap, null); |
|||
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData))); |
|||
} |
|||
} |
|||
|
|||
private void sendTsWsMsg(EntityId entityId, String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) { |
|||
Map<String, List<TsValue>> tsUpdate = new HashMap<>(); |
|||
subscriptionUpdate.getData().forEach((k, v) -> { |
|||
Object[] data = (Object[]) v.get(0); |
|||
tsUpdate.computeIfAbsent(k, key -> new ArrayList<>()).add(new TsValue((Long) data[0], (String) data[1])); |
|||
}); |
|||
EntityData entityData = getDataForEntity(entityId); |
|||
if (entityData != null && entityData.getLatest() != null) { |
|||
Map<String, TsValue> latestCtxValues = entityData.getLatest().get(keyType); |
|||
log.trace("[{}][{}][{}] Going to compare update with {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), latestCtxValues); |
|||
if (latestCtxValues != null) { |
|||
latestCtxValues.forEach((k, v) -> { |
|||
List<TsValue> updateList = tsUpdate.get(k); |
|||
if (updateList != null) { |
|||
for (TsValue update : new ArrayList<>(updateList)) { |
|||
if (update.getTs() < v.getTs()) { |
|||
log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); |
|||
updateList.remove(update); |
|||
} else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) { |
|||
log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); |
|||
updateList.remove(update); |
|||
} |
|||
if (updateList.isEmpty()) { |
|||
tsUpdate.remove(k); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
//Setting new values
|
|||
tsUpdate.forEach((k, v) -> { |
|||
Optional<TsValue> maxValue = v.stream().max(Comparator.comparingLong(TsValue::getTs)); |
|||
maxValue.ifPresent(max -> latestCtxValues.put(k, max)); |
|||
}); |
|||
} |
|||
} |
|||
if (!tsUpdate.isEmpty()) { |
|||
Map<String, TsValue[]> tsMap = new HashMap<>(); |
|||
tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()]))); |
|||
entityData = new EntityData(entityId, null, tsMap); |
|||
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData))); |
|||
} |
|||
} |
|||
|
|||
private EntityData getDataForEntity(EntityId entityId) { |
|||
return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null); |
|||
} |
|||
|
|||
public synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) { |
|||
List<Integer> subIdsToCancel = new ArrayList<>(); |
|||
List<TbSubscription> subsToAdd = new ArrayList<>(); |
|||
Set<EntityId> currentSubs = new HashSet<>(); |
|||
subToEntityIdMap.forEach((subId, entityId) -> { |
|||
if (!newDataMap.containsKey(entityId)) { |
|||
subIdsToCancel.add(subId); |
|||
} else { |
|||
currentSubs.add(entityId); |
|||
} |
|||
}); |
|||
log.trace("[{}][{}] Subscriptions that are invalid: {}", sessionRef.getSessionId(), cmdId, subIdsToCancel); |
|||
subIdsToCancel.forEach(subToEntityIdMap::remove); |
|||
List<EntityData> newSubsList = newDataMap.entrySet().stream().filter(entry -> !currentSubs.contains(entry.getKey())).map(Map.Entry::getValue).collect(Collectors.toList()); |
|||
if (!newSubsList.isEmpty()) { |
|||
boolean resultToLatestValues; |
|||
List<EntityKey> keys = null; |
|||
if (curTsCmd != null) { |
|||
resultToLatestValues = false; |
|||
keys = curTsCmd.getKeys().stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()); |
|||
} else if (latestValueCmd != null) { |
|||
resultToLatestValues = true; |
|||
keys = latestValueCmd.getKeys(); |
|||
} else { |
|||
resultToLatestValues = true; |
|||
} |
|||
if (keys != null && !keys.isEmpty()) { |
|||
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys); |
|||
newSubsList.forEach( |
|||
entity -> { |
|||
log.trace("[{}][{}] Found new subscription for entity: {}", sessionRef.getSessionId(), cmdId, entity.getEntityId()); |
|||
subsToAdd.addAll(addSubscriptions(entity, keysByType, resultToLatestValues)); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityDataUpdate(cmdId, data, null)); |
|||
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId)); |
|||
subsToAdd.forEach(localSubscriptionService::addSubscription); |
|||
} |
|||
|
|||
public void setCurrentCmd(EntityDataCmd cmd) { |
|||
curTsCmd = cmd.getTsCmd(); |
|||
latestValueCmd = cmd.getLatestCmd(); |
|||
} |
|||
|
|||
@Override |
|||
protected EntityDataQuery buildEntityDataQuery() { |
|||
return query; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.subscription; |
|||
|
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; |
|||
|
|||
public interface TbEntityDataSubscriptionService { |
|||
|
|||
void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd); |
|||
|
|||
void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd); |
|||
|
|||
void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId); |
|||
|
|||
void cancelAllSessionSubscriptions(String sessionId); |
|||
|
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry; |
|||
|
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.ApplicationListener; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
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.BaseAttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
|||
import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
|||
import org.thingsboard.server.common.data.kv.LongDataEntry; |
|||
import org.thingsboard.server.common.data.kv.StringDataEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.service.queue.TbClusterService; |
|||
import org.thingsboard.server.service.subscription.SubscriptionManagerService; |
|||
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; |
|||
|
|||
import javax.annotation.Nullable; |
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* Created by ashvayka on 27.03.18. |
|||
*/ |
|||
@Slf4j |
|||
public abstract class AbstractSubscriptionService implements ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
protected final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet(); |
|||
|
|||
protected final TbClusterService clusterService; |
|||
protected final PartitionService partitionService; |
|||
protected Optional<SubscriptionManagerService> subscriptionManagerService; |
|||
|
|||
protected ExecutorService wsCallBackExecutor; |
|||
|
|||
public AbstractSubscriptionService(TbClusterService clusterService, |
|||
PartitionService partitionService) { |
|||
this.clusterService = clusterService; |
|||
this.partitionService = partitionService; |
|||
} |
|||
|
|||
@Autowired(required = false) |
|||
public void setSubscriptionManagerService(Optional<SubscriptionManagerService> subscriptionManagerService) { |
|||
this.subscriptionManagerService = subscriptionManagerService; |
|||
} |
|||
|
|||
abstract String getExecutorPrefix(); |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(getExecutorPrefix() + "-service-ws-callback")); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (wsCallBackExecutor != null) { |
|||
wsCallBackExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
@EventListener(PartitionChangeEvent.class) |
|||
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
|||
if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { |
|||
currentPartitions.clear(); |
|||
currentPartitions.addAll(partitionChangeEvent.getPartitions()); |
|||
} |
|||
} |
|||
|
|||
protected void addWsCallback(ListenableFuture<List<Void>> saveFuture, Consumer<Void> callback) { |
|||
Futures.addCallback(saveFuture, new FutureCallback<List<Void>>() { |
|||
@Override |
|||
public void onSuccess(@Nullable List<Void> result) { |
|||
callback.accept(null); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
} |
|||
}, wsCallBackExecutor); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry; |
|||
|
|||
import org.springframework.context.ApplicationListener; |
|||
import org.thingsboard.rule.engine.api.RuleEngineAlarmService; |
|||
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
|
|||
/** |
|||
* Created by ashvayka on 27.03.18. |
|||
*/ |
|||
public interface AlarmSubscriptionService extends RuleEngineAlarmService, ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
} |
|||
@ -0,0 +1,214 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.checkerframework.checker.nullness.qual.Nullable; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.alarm.AlarmInfo; |
|||
import org.thingsboard.server.common.data.alarm.AlarmQuery; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; |
|||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|||
import org.thingsboard.server.common.data.alarm.AlarmStatus; |
|||
import org.thingsboard.server.common.data.id.AlarmId; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
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.BaseAttributeKvEntry; |
|||
import org.thingsboard.server.common.data.kv.BooleanDataEntry; |
|||
import org.thingsboard.server.common.data.kv.DoubleDataEntry; |
|||
import org.thingsboard.server.common.data.kv.LongDataEntry; |
|||
import org.thingsboard.server.common.data.kv.StringDataEntry; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataPageLink; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.dao.alarm.AlarmOperationResult; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.service.queue.TbClusterService; |
|||
import org.thingsboard.server.service.subscription.SubscriptionManagerService; |
|||
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; |
|||
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.function.Consumer; |
|||
|
|||
/** |
|||
* Created by ashvayka on 27.03.18. |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { |
|||
|
|||
private final AlarmService alarmService; |
|||
|
|||
public DefaultAlarmSubscriptionService(TbClusterService clusterService, |
|||
PartitionService partitionService, |
|||
AlarmService alarmService) { |
|||
super(clusterService, partitionService); |
|||
this.alarmService = alarmService; |
|||
} |
|||
|
|||
@Autowired(required = false) |
|||
public void setSubscriptionManagerService(Optional<SubscriptionManagerService> subscriptionManagerService) { |
|||
this.subscriptionManagerService = subscriptionManagerService; |
|||
} |
|||
|
|||
@Override |
|||
String getExecutorPrefix() { |
|||
return "alarm"; |
|||
} |
|||
|
|||
@Override |
|||
public Alarm createOrUpdateAlarm(Alarm alarm) { |
|||
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); |
|||
if (result.isSuccessful()) { |
|||
onAlarmUpdated(result); |
|||
} |
|||
return result.getAlarm(); |
|||
} |
|||
|
|||
@Override |
|||
public Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId) { |
|||
AlarmOperationResult result = alarmService.deleteAlarm(tenantId, alarmId); |
|||
onAlarmDeleted(result); |
|||
return result.isSuccessful(); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs) { |
|||
ListenableFuture<AlarmOperationResult> result = alarmService.ackAlarm(tenantId, alarmId, ackTs); |
|||
Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); |
|||
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs) { |
|||
ListenableFuture<AlarmOperationResult> result = alarmService.clearAlarm(tenantId, alarmId, details, clearTs); |
|||
Futures.addCallback(result, new AlarmUpdateCallback(), wsCallBackExecutor); |
|||
return Futures.transform(result, AlarmOperationResult::isSuccessful, wsCallBackExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId) { |
|||
return alarmService.findAlarmByIdAsync(tenantId, alarmId); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<AlarmInfo> findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) { |
|||
return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) { |
|||
return alarmService.findAlarms(tenantId, query); |
|||
} |
|||
|
|||
@Override |
|||
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { |
|||
return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) { |
|||
return alarmService.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { |
|||
return alarmService.findLatestByOriginatorAndType(tenantId, originator, type); |
|||
} |
|||
|
|||
private void onAlarmUpdated(AlarmOperationResult result) { |
|||
wsCallBackExecutor.submit(() -> { |
|||
Alarm alarm = result.getAlarm(); |
|||
TenantId tenantId = result.getAlarm().getTenantId(); |
|||
for (EntityId entityId : result.getPropagatedEntitiesList()) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); |
|||
if (currentPartitions.contains(tpi)) { |
|||
if (subscriptionManagerService.isPresent()) { |
|||
subscriptionManagerService.get().onAlarmUpdate(tenantId, entityId, alarm, TbCallback.EMPTY); |
|||
} else { |
|||
log.warn("Possible misconfiguration because subscriptionManagerService is null!"); |
|||
} |
|||
} else { |
|||
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmUpdateProto(tenantId, entityId, alarm); |
|||
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void onAlarmDeleted(AlarmOperationResult result) { |
|||
wsCallBackExecutor.submit(() -> { |
|||
Alarm alarm = result.getAlarm(); |
|||
TenantId tenantId = result.getAlarm().getTenantId(); |
|||
for (EntityId entityId : result.getPropagatedEntitiesList()) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); |
|||
if (currentPartitions.contains(tpi)) { |
|||
if (subscriptionManagerService.isPresent()) { |
|||
subscriptionManagerService.get().onAlarmDeleted(tenantId, entityId, alarm, TbCallback.EMPTY); |
|||
} else { |
|||
log.warn("Possible misconfiguration because subscriptionManagerService is null!"); |
|||
} |
|||
} else { |
|||
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAlarmDeletedProto(tenantId, entityId, alarm); |
|||
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private class AlarmUpdateCallback implements FutureCallback<AlarmOperationResult> { |
|||
@Override |
|||
public void onSuccess(@Nullable AlarmOperationResult result) { |
|||
onAlarmUpdated(result); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.warn("Failed to update alarm", t); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
|
|||
public class AlarmDataCmd extends DataCmd { |
|||
|
|||
@Getter |
|||
private final AlarmDataQuery query; |
|||
|
|||
@JsonCreator |
|||
public AlarmDataCmd(@JsonProperty("cmdId") int cmdId, @JsonProperty("query") AlarmDataQuery query) { |
|||
super(cmdId); |
|||
this.query = query; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class AlarmDataUnsubscribeCmd implements UnsubscribeCmd { |
|||
|
|||
private final int cmdId; |
|||
|
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import lombok.Getter; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
|||
|
|||
import java.util.List; |
|||
|
|||
public class AlarmDataUpdate extends DataUpdate<AlarmData> { |
|||
|
|||
@Getter |
|||
private long allowedEntities; |
|||
@Getter |
|||
private long totalEntities; |
|||
|
|||
public AlarmDataUpdate(int cmdId, PageData<AlarmData> data, List<AlarmData> update, long allowedEntities, long totalEntities) { |
|||
super(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null); |
|||
this.allowedEntities = allowedEntities; |
|||
this.totalEntities = totalEntities; |
|||
} |
|||
|
|||
public AlarmDataUpdate(int cmdId, int errorCode, String errorMsg) { |
|||
super(cmdId, null, null, errorCode, errorMsg); |
|||
} |
|||
|
|||
@Override |
|||
public DataUpdateType getDataUpdateType() { |
|||
return DataUpdateType.ALARM_DATA; |
|||
} |
|||
|
|||
@JsonCreator |
|||
public AlarmDataUpdate(@JsonProperty("cmdId") int cmdId, |
|||
@JsonProperty("data") PageData<AlarmData> data, |
|||
@JsonProperty("update") List<AlarmData> update, |
|||
@JsonProperty("errorCode") int errorCode, |
|||
@JsonProperty("errorMsg") String errorMsg, |
|||
@JsonProperty("allowedEntities") long allowedEntities, |
|||
@JsonProperty("totalEntities") long totalEntities) { |
|||
super(cmdId, data, update, errorCode, errorMsg); |
|||
this.allowedEntities = allowedEntities; |
|||
this.totalEntities = totalEntities; |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
public class DataCmd { |
|||
|
|||
@Getter |
|||
private final int cmdId; |
|||
|
|||
public DataCmd(int cmdId) { |
|||
this.cmdId = cmdId; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public abstract class DataUpdate<T> { |
|||
|
|||
private final int cmdId; |
|||
private final PageData<T> data; |
|||
private final List<T> update; |
|||
private final int errorCode; |
|||
private final String errorMsg; |
|||
|
|||
public DataUpdate(int cmdId, PageData<T> data, List<T> update) { |
|||
this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null); |
|||
} |
|||
|
|||
public DataUpdate(int cmdId, int errorCode, String errorMsg) { |
|||
this(cmdId, null, null, errorCode, errorMsg); |
|||
} |
|||
|
|||
public abstract DataUpdateType getDataUpdateType(); |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
public enum DataUpdateType { |
|||
ENTITY_DATA, |
|||
ALARM_DATA |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
|
|||
public class EntityDataCmd extends DataCmd { |
|||
|
|||
@Getter |
|||
private final EntityDataQuery query; |
|||
@Getter |
|||
private final EntityHistoryCmd historyCmd; |
|||
@Getter |
|||
private final LatestValueCmd latestCmd; |
|||
@Getter |
|||
private final TimeSeriesCmd tsCmd; |
|||
|
|||
@JsonCreator |
|||
public EntityDataCmd(@JsonProperty("cmdId") int cmdId, |
|||
@JsonProperty("query") EntityDataQuery query, |
|||
@JsonProperty("historyCmd") EntityHistoryCmd historyCmd, |
|||
@JsonProperty("latestCmd") LatestValueCmd latestCmd, |
|||
@JsonProperty("tsCmd") TimeSeriesCmd tsCmd) { |
|||
super(cmdId); |
|||
this.query = query; |
|||
this.historyCmd = historyCmd; |
|||
this.latestCmd = latestCmd; |
|||
this.tsCmd = tsCmd; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class EntityDataUnsubscribeCmd implements UnsubscribeCmd { |
|||
|
|||
private final int cmdId; |
|||
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
|||
|
|||
import java.util.List; |
|||
|
|||
|
|||
public class EntityDataUpdate extends DataUpdate<EntityData> { |
|||
|
|||
public EntityDataUpdate(int cmdId, PageData<EntityData> data, List<EntityData> update) { |
|||
super(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null); |
|||
} |
|||
|
|||
public EntityDataUpdate(int cmdId, int errorCode, String errorMsg) { |
|||
super(cmdId, null, null, errorCode, errorMsg); |
|||
} |
|||
|
|||
@Override |
|||
public DataUpdateType getDataUpdateType() { |
|||
return DataUpdateType.ENTITY_DATA; |
|||
} |
|||
|
|||
@JsonCreator |
|||
public EntityDataUpdate(@JsonProperty("cmdId") int cmdId, |
|||
@JsonProperty("data") PageData<EntityData> data, |
|||
@JsonProperty("update") List<EntityData> update, |
|||
@JsonProperty("errorCode") int errorCode, |
|||
@JsonProperty("errorMsg") String errorMsg) { |
|||
super(cmdId, data, update, errorCode, errorMsg); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.kv.Aggregation; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
public class EntityHistoryCmd implements GetTsCmd { |
|||
|
|||
private List<String> keys; |
|||
private long startTs; |
|||
private long endTs; |
|||
private long interval; |
|||
private int limit; |
|||
private Aggregation agg; |
|||
private boolean fetchLatestPreviousPoint; |
|||
|
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import org.thingsboard.server.common.data.kv.Aggregation; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface GetTsCmd { |
|||
|
|||
long getStartTs(); |
|||
|
|||
long getEndTs(); |
|||
|
|||
List<String> getKeys(); |
|||
|
|||
long getInterval(); |
|||
|
|||
int getLimit(); |
|||
|
|||
Aggregation getAgg(); |
|||
|
|||
boolean isFetchLatestPreviousPoint(); |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
public class LatestValueCmd { |
|||
|
|||
private List<EntityKey> keys; |
|||
|
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.kv.Aggregation; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
public class TimeSeriesCmd implements GetTsCmd { |
|||
|
|||
private List<String> keys; |
|||
private long startTs; |
|||
private long timeWindow; |
|||
private long interval; |
|||
private int limit; |
|||
private Aggregation agg; |
|||
private boolean fetchLatestPreviousPoint; |
|||
|
|||
@JsonIgnore |
|||
@Override |
|||
public long getEndTs() { |
|||
return startTs + timeWindow; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.cmd.v2; |
|||
|
|||
import lombok.Data; |
|||
|
|||
public interface UnsubscribeCmd { |
|||
|
|||
int getCmdId(); |
|||
|
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* Copyright © 2016-2020 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.telemetry.sub; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.alarm.Alarm; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.TreeMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
public class AlarmSubscriptionUpdate { |
|||
|
|||
@Getter |
|||
private int subscriptionId; |
|||
@Getter |
|||
private int errorCode; |
|||
@Getter |
|||
private String errorMsg; |
|||
@Getter |
|||
private Alarm alarm; |
|||
@Getter |
|||
private boolean alarmDeleted; |
|||
|
|||
public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) { |
|||
this(subscriptionId, alarm, false); |
|||
} |
|||
|
|||
public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) { |
|||
super(); |
|||
this.subscriptionId = subscriptionId; |
|||
this.alarm = alarm; |
|||
this.alarmDeleted = alarmDeleted; |
|||
} |
|||
|
|||
public AlarmSubscriptionUpdate(int subscriptionId, SubscriptionErrorCode errorCode) { |
|||
this(subscriptionId, errorCode, null); |
|||
} |
|||
|
|||
public AlarmSubscriptionUpdate(int subscriptionId, SubscriptionErrorCode errorCode, String errorMsg) { |
|||
super(); |
|||
this.subscriptionId = subscriptionId; |
|||
this.errorCode = errorCode.getCode(); |
|||
this.errorMsg = errorMsg != null ? errorMsg : errorCode.getDefaultMsg(); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm=" |
|||
+ alarm + "]"; |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue