committed by
GitHub
408 changed files with 20282 additions and 4485 deletions
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,856 @@ |
|||||
|
-- |
||||
|
-- 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_alarm_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; |
||||
|
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_alarm_time ON alarm(originator_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); |
||||
|
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 |
||||
|
PERFORM column_type_to_uuid(table_name, column_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,65 @@ |
|||||
|
/** |
||||
|
* 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.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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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.query; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
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.dao.entity.EntityService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
@TbCoreComponent |
||||
|
public class DefaultEntityQueryService implements EntityQueryService { |
||||
|
|
||||
|
@Autowired |
||||
|
private EntityService entityService; |
||||
|
|
||||
|
@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); |
||||
|
} |
||||
|
} |
||||
@ -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.query; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
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); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,500 @@ |
|||||
|
/** |
||||
|
* 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.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 |
||||
|
@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 AtomicInteger regularQueryInvocationCnt = new AtomicInteger(); |
||||
|
private AtomicInteger dynamicQueryInvocationCnt = new AtomicInteger(); |
||||
|
private AtomicLong regularQueryTimeSpent = new AtomicLong(); |
||||
|
private AtomicLong dynamicQueryTimeSpent = new AtomicLong(); |
||||
|
|
||||
|
@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) { |
||||
|
clearSubs(ctx); |
||||
|
} |
||||
|
} 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.setQuery(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(); |
||||
|
PageData<EntityData> data = entityService.findEntityDataByQuery(tenantId, customerId, ctx.getQuery()); |
||||
|
long end = System.currentTimeMillis(); |
||||
|
regularQueryInvocationCnt.incrementAndGet(); |
||||
|
regularQueryTimeSpent.addAndGet(end - start); |
||||
|
|
||||
|
if (log.isTraceEnabled()) { |
||||
|
data.getData().forEach(ed -> { |
||||
|
log.trace("[{}][{}] EntityData: {}", session.getSessionId(), cmd.getCmdId(), ed); |
||||
|
}); |
||||
|
} |
||||
|
ctx.setData(data); |
||||
|
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.setQuery(cmd.getQuery()); |
||||
|
AlarmDataQuery adq = ctx.getQuery(); |
||||
|
EntityDataSortOrder sortOrder = adq.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); |
||||
|
EntityDataQuery edq = new EntityDataQuery(adq.getEntityFilter(), edpl, adq.getEntityFields(), adq.getLatestValues(), adq.getKeyFilters()); |
||||
|
PageData<EntityData> entitiesData = entityService.findEntityDataByQuery(ctx.getTenantId(), ctx.getCustomerId(), edq); |
||||
|
List<EntityData> entities = entitiesData.getData(); |
||||
|
ctx.setEntitiesData(entitiesData); |
||||
|
ctx.cancelTasks(); |
||||
|
if (entities.isEmpty()) { |
||||
|
AlarmDataUpdate update = new AlarmDataUpdate(cmd.getCmdId(), new PageData<>(Collections.emptyList(), 1, 0, false), null); |
||||
|
wsService.sendWsMsg(ctx.getSessionId(), update); |
||||
|
} else { |
||||
|
ctx.fetchAlarms(); |
||||
|
if (adq.getPageLink().getTimeWindow() > 0) { |
||||
|
ctx.createSubscriptions(); |
||||
|
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(); |
||||
|
TbEntityDataSubCtx.TbEntityDataSubCtxUpdateResult result = finalCtx.update(entityService.findEntityDataByQuery(tenantId, customerId, finalCtx.getQuery())); |
||||
|
long end = System.currentTimeMillis(); |
||||
|
dynamicQueryInvocationCnt.incrementAndGet(); |
||||
|
dynamicQueryTimeSpent.addAndGet(end - start); |
||||
|
result.getSubsToCancel().forEach(subId -> localSubscriptionService.cancelSubscription(finalCtx.getSessionId(), subId)); |
||||
|
result.getSubsToAdd().forEach(localSubscriptionService::addSubscription); |
||||
|
} 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 regularQueryInvocationCntValue = regularQueryInvocationCnt.getAndSet(0); |
||||
|
long regularQueryInvocationTimeValue = regularQueryTimeSpent.getAndSet(0); |
||||
|
int dynamicQueryInvocationCntValue = dynamicQueryInvocationCnt.getAndSet(0); |
||||
|
long dynamicQueryInvocationTimeValue = dynamicQueryTimeSpent.getAndSet(0); |
||||
|
long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count(); |
||||
|
if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0) { |
||||
|
log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}]", |
||||
|
regularQueryInvocationCntValue, regularQueryInvocationTimeValue, dynamicQueryCnt, dynamicQueryInvocationCntValue, dynamicQueryInvocationTimeValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void clearSubs(TbAbstractDataSubCtx ctx) { |
||||
|
ctx.clearSubscriptions(); |
||||
|
} |
||||
|
|
||||
|
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { |
||||
|
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); |
||||
|
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, localSubscriptionService, sessionRef, cmd.getCmdId()); |
||||
|
ctx.setQuery(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, localSubscriptionService, alarmService, sessionRef, cmd.getCmdId()); |
||||
|
ctx.setQuery(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) { |
||||
|
createTelemetrySubscriptions(ctx, 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); |
||||
|
createTelemetrySubscriptions(ctx, latestCmd.getKeys()); |
||||
|
} |
||||
|
|
||||
|
@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); |
||||
|
} |
||||
|
createTelemetrySubscriptions(ctx, latestCmd.getKeys()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void createTelemetrySubscriptions(TbEntityDataSubCtx ctx, List<EntityKey> keys) { |
||||
|
createTelemetrySubscriptions(ctx, keys, true); |
||||
|
} |
||||
|
|
||||
|
private void createTelemetrySubscriptions(TbEntityDataSubCtx ctx, List<EntityKey> keys, boolean latest) { |
||||
|
List<TbSubscription> tbSubs = ctx.createSubscriptions(keys, latest); |
||||
|
tbSubs.forEach(sub -> localSubscriptionService.addSubscription(sub)); |
||||
|
} |
||||
|
|
||||
|
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(); |
||||
|
clearSubs(ctx); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@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,90 @@ |
|||||
|
/** |
||||
|
* 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 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.query.AbstractDataQuery; |
||||
|
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
||||
|
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
||||
|
|
||||
|
import java.util.Collection; |
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.ScheduledFuture; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery> { |
||||
|
|
||||
|
protected final String serviceId; |
||||
|
protected final TelemetryWebSocketService wsService; |
||||
|
protected final TbLocalSubscriptionService localSubscriptionService; |
||||
|
protected final TelemetryWebSocketSessionRef sessionRef; |
||||
|
protected final int cmdId; |
||||
|
@Getter |
||||
|
@Setter |
||||
|
protected T query; |
||||
|
protected Map<Integer, EntityId> subToEntityIdMap; |
||||
|
@Setter |
||||
|
protected volatile ScheduledFuture<?> refreshTask; |
||||
|
|
||||
|
public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService, TbLocalSubscriptionService localSubscriptionService, |
||||
|
TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
||||
|
this.serviceId = serviceId; |
||||
|
this.wsService = wsService; |
||||
|
this.localSubscriptionService = localSubscriptionService; |
||||
|
this.sessionRef = sessionRef; |
||||
|
this.cmdId = cmdId; |
||||
|
} |
||||
|
|
||||
|
public String getSessionId() { |
||||
|
return sessionRef.getSessionId(); |
||||
|
} |
||||
|
|
||||
|
public TenantId getTenantId() { |
||||
|
return sessionRef.getSecurityCtx().getTenantId(); |
||||
|
} |
||||
|
|
||||
|
public CustomerId getCustomerId() { |
||||
|
return sessionRef.getSecurityCtx().getCustomerId(); |
||||
|
} |
||||
|
|
||||
|
public void clearSubscriptions(){ |
||||
|
if (subToEntityIdMap != null) { |
||||
|
for (Integer subId : subToEntityIdMap.keySet()) { |
||||
|
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); |
||||
|
} |
||||
|
subToEntityIdMap.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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,198 @@ |
|||||
|
/** |
||||
|
* 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.dao.alarm.AlarmService; |
||||
|
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 java.util.Collection; |
||||
|
import java.util.Collections; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.LinkedHashMap; |
||||
|
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; |
||||
|
@Getter |
||||
|
@Setter |
||||
|
private PageData<AlarmData> alarms; |
||||
|
@Getter |
||||
|
@Setter |
||||
|
private boolean tooManyEntities; |
||||
|
|
||||
|
public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService, |
||||
|
TbLocalSubscriptionService localSubscriptionService, |
||||
|
AlarmService alarmService, |
||||
|
TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
||||
|
super(serviceId, wsService, localSubscriptionService, sessionRef, cmdId); |
||||
|
this.alarmService = alarmService; |
||||
|
this.entitiesMap = new LinkedHashMap<>(); |
||||
|
this.alarmsMap = new HashMap<>(); |
||||
|
} |
||||
|
|
||||
|
public void fetchAlarms() { |
||||
|
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), |
||||
|
query.getPageLink(), getOrderedEntityIds()); |
||||
|
alarms = setAndMergeAlarmsData(alarms); |
||||
|
AlarmDataUpdate update = new AlarmDataUpdate(cmdId, alarms, null); |
||||
|
wsService.sendWsMsg(getSessionId(), update); |
||||
|
} |
||||
|
|
||||
|
public void setEntitiesData(PageData<EntityData> entitiesData) { |
||||
|
entitiesMap.clear(); |
||||
|
tooManyEntities = entitiesData.hasNext(); |
||||
|
for (EntityData entityData : entitiesData.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()))); |
||||
|
return this.alarms; |
||||
|
} |
||||
|
|
||||
|
public void createSubscriptions() { |
||||
|
clearSubscriptions(); |
||||
|
this.subToEntityIdMap = new HashMap<>(); |
||||
|
AlarmDataPageLink pageLink = query.getPageLink(); |
||||
|
long startTs = System.currentTimeMillis() - pageLink.getTimeWindow(); |
||||
|
for (EntityData entityData : entitiesMap.values()) { |
||||
|
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() |
||||
|
.type(TbSubscriptionType.ALARMS) |
||||
|
.serviceId(serviceId) |
||||
|
.sessionId(sessionRef.getSessionId()) |
||||
|
.subscriptionId(subIdx) |
||||
|
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
||||
|
.entityId(entityData.getEntityId()) |
||||
|
.updateConsumer(this::sendWsMsg) |
||||
|
.ts(startTs) |
||||
|
.build(); |
||||
|
localSubscriptionService.addSubscription(subscription); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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()); |
||||
|
alarmsMap.put(alarmId, updated); |
||||
|
wsService.sendWsMsg(sessionId, new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated))); |
||||
|
} 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; |
||||
|
} |
||||
|
} |
||||
@ -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, |
||||
|
TbSubscriptionType type, BiConsumer<String, AlarmSubscriptionUpdate> updateConsumer, long ts) { |
||||
|
super(serviceId, sessionId, subscriptionId, tenantId, entityId, type, updateConsumer); |
||||
|
this.ts = ts; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean equals(Object o) { |
||||
|
return super.equals(o); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
return super.hashCode(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,343 @@ |
|||||
|
/** |
||||
|
* 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.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 |
||||
|
private PageData<EntityData> data; |
||||
|
@Getter |
||||
|
@Setter |
||||
|
private boolean initialDataSent; |
||||
|
private TimeSeriesCmd curTsCmd; |
||||
|
private LatestValueCmd latestValueCmd; |
||||
|
|
||||
|
public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, |
||||
|
TbLocalSubscriptionService localSubscriptionService, |
||||
|
TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
||||
|
super(serviceId, wsService, localSubscriptionService, sessionRef, cmdId); |
||||
|
} |
||||
|
|
||||
|
public void setData(PageData<EntityData> data) { |
||||
|
this.data = data; |
||||
|
} |
||||
|
|
||||
|
public List<TbSubscription> createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) { |
||||
|
this.subToEntityIdMap = new HashMap<>(); |
||||
|
List<TbSubscription> tbSubs = new ArrayList<>(); |
||||
|
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys); |
||||
|
for (EntityData entityData : data.getData()) { |
||||
|
tbSubs.addAll(addSubscriptions(entityData, keysByType, resultToLatestValues)); |
||||
|
} |
||||
|
return tbSubs; |
||||
|
} |
||||
|
|
||||
|
private 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; |
||||
|
} |
||||
|
|
||||
|
private 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 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; |
||||
|
} |
||||
|
|
||||
|
private void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) { |
||||
|
sendWsMsg(sessionId, subscriptionUpdate, keyType, true); |
||||
|
} |
||||
|
|
||||
|
private 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 TbEntityDataSubCtxUpdateResult update(PageData<EntityData> newData) { |
||||
|
Map<EntityId, EntityData> oldDataMap; |
||||
|
if (data != null && !data.getData().isEmpty()) { |
||||
|
oldDataMap = data.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity())); |
||||
|
} else { |
||||
|
oldDataMap = Collections.emptyMap(); |
||||
|
} |
||||
|
Map<EntityId, EntityData> newDataMap = newData.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity())); |
||||
|
if (oldDataMap.size() == newDataMap.size() && oldDataMap.keySet().equals(newDataMap.keySet())) { |
||||
|
log.trace("[{}][{}] No updates to entity data found", sessionRef.getSessionId(), cmdId); |
||||
|
return TbEntityDataSubCtxUpdateResult.EMPTY; |
||||
|
} else { |
||||
|
this.data = newData; |
||||
|
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)); |
||||
|
return new TbEntityDataSubCtxUpdateResult(subIdsToCancel, subsToAdd); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void setCurrentCmd(EntityDataCmd cmd) { |
||||
|
curTsCmd = cmd.getTsCmd(); |
||||
|
latestValueCmd = cmd.getLatestCmd(); |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
public static class TbEntityDataSubCtxUpdateResult { |
||||
|
|
||||
|
private static TbEntityDataSubCtxUpdateResult EMPTY = new TbEntityDataSubCtxUpdateResult(Collections.emptyList(), Collections.emptyList()); |
||||
|
|
||||
|
private List<Integer> subsToCancel; |
||||
|
private List<TbSubscription> subsToAdd; |
||||
|
} |
||||
|
} |
||||
@ -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,213 @@ |
|||||
|
/** |
||||
|
* 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.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, AlarmDataPageLink pageLink, Collection<EntityId> orderedEntityIds) { |
||||
|
return alarmService.findAlarmDataByQueryForEntities(tenantId, customerId, pageLink, 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,51 @@ |
|||||
|
/** |
||||
|
* 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.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> { |
||||
|
|
||||
|
public AlarmDataUpdate(int cmdId, PageData<AlarmData> data, List<AlarmData> update) { |
||||
|
super(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null); |
||||
|
} |
||||
|
|
||||
|
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) { |
||||
|
super(cmdId, data, update, errorCode, errorMsg); |
||||
|
} |
||||
|
} |
||||
@ -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 + "]"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,495 @@ |
|||||
|
/** |
||||
|
* 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 com.fasterxml.jackson.core.type.TypeReference; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import io.jsonwebtoken.Claims; |
||||
|
import io.jsonwebtoken.Header; |
||||
|
import io.jsonwebtoken.Jwt; |
||||
|
import io.jsonwebtoken.Jwts; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.hamcrest.Matcher; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Rule; |
||||
|
import org.junit.rules.TestRule; |
||||
|
import org.junit.rules.TestWatcher; |
||||
|
import org.junit.runner.Description; |
||||
|
import org.junit.runner.RunWith; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.test.context.SpringBootContextLoader; |
||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
import org.springframework.context.annotation.ComponentScan; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.http.HttpHeaders; |
||||
|
import org.springframework.http.MediaType; |
||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||
|
import org.springframework.http.converter.StringHttpMessageConverter; |
||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||
|
import org.springframework.mock.http.MockHttpInputMessage; |
||||
|
import org.springframework.mock.http.MockHttpOutputMessage; |
||||
|
import org.springframework.test.annotation.DirtiesContext; |
||||
|
import org.springframework.test.context.ActiveProfiles; |
||||
|
import org.springframework.test.context.ContextConfiguration; |
||||
|
import org.springframework.test.context.junit4.SpringRunner; |
||||
|
import org.springframework.test.web.servlet.MockMvc; |
||||
|
import org.springframework.test.web.servlet.MvcResult; |
||||
|
import org.springframework.test.web.servlet.ResultActions; |
||||
|
import org.springframework.test.web.servlet.ResultMatcher; |
||||
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; |
||||
|
import org.springframework.util.LinkedMultiValueMap; |
||||
|
import org.springframework.util.MultiValueMap; |
||||
|
import org.springframework.web.context.WebApplicationContext; |
||||
|
import org.thingsboard.server.common.data.BaseData; |
||||
|
import org.thingsboard.server.common.data.Customer; |
||||
|
import org.thingsboard.server.common.data.Tenant; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UUIDBased; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.page.TimePageLink; |
||||
|
import org.thingsboard.server.common.data.security.Authority; |
||||
|
import org.thingsboard.server.config.ThingsboardSecurityConfiguration; |
||||
|
import org.thingsboard.server.service.mail.TestMailService; |
||||
|
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; |
||||
|
import org.thingsboard.server.service.security.auth.rest.LoginRequest; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Comparator; |
||||
|
import java.util.List; |
||||
|
|
||||
|
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; |
||||
|
|
||||
|
@Slf4j |
||||
|
public abstract class AbstractWebTest { |
||||
|
|
||||
|
protected ObjectMapper mapper = new ObjectMapper(); |
||||
|
|
||||
|
protected static final String TEST_TENANT_NAME = "TEST TENANT"; |
||||
|
|
||||
|
protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org"; |
||||
|
private static final String SYS_ADMIN_PASSWORD = "sysadmin"; |
||||
|
|
||||
|
protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org"; |
||||
|
private static final String TENANT_ADMIN_PASSWORD = "tenant"; |
||||
|
|
||||
|
protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org"; |
||||
|
private static final String CUSTOMER_USER_PASSWORD = "customer"; |
||||
|
|
||||
|
/** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)} |
||||
|
* and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()} |
||||
|
*/ |
||||
|
private static final long DEFAULT_TIMEOUT = -1L; |
||||
|
|
||||
|
protected MediaType contentType = MediaType.APPLICATION_JSON; |
||||
|
|
||||
|
protected MockMvc mockMvc; |
||||
|
|
||||
|
protected String token; |
||||
|
protected String refreshToken; |
||||
|
protected String username; |
||||
|
|
||||
|
private TenantId tenantId; |
||||
|
|
||||
|
@SuppressWarnings("rawtypes") |
||||
|
private HttpMessageConverter mappingJackson2HttpMessageConverter; |
||||
|
|
||||
|
@SuppressWarnings("rawtypes") |
||||
|
private HttpMessageConverter stringHttpMessageConverter; |
||||
|
|
||||
|
@Autowired |
||||
|
private WebApplicationContext webApplicationContext; |
||||
|
|
||||
|
@Rule |
||||
|
public TestRule watcher = new TestWatcher() { |
||||
|
protected void starting(Description description) { |
||||
|
log.info("Starting test: {}", description.getMethodName()); |
||||
|
} |
||||
|
|
||||
|
protected void finished(Description description) { |
||||
|
log.info("Finished test: {}", description.getMethodName()); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
@Autowired |
||||
|
void setConverters(HttpMessageConverter<?>[] converters) { |
||||
|
|
||||
|
this.mappingJackson2HttpMessageConverter = Arrays.stream(converters) |
||||
|
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter) |
||||
|
.findAny() |
||||
|
.get(); |
||||
|
|
||||
|
this.stringHttpMessageConverter = Arrays.stream(converters) |
||||
|
.filter(hmc -> hmc instanceof StringHttpMessageConverter) |
||||
|
.findAny() |
||||
|
.get(); |
||||
|
|
||||
|
Assert.assertNotNull("the JSON message converter must not be null", |
||||
|
this.mappingJackson2HttpMessageConverter); |
||||
|
} |
||||
|
|
||||
|
@Before |
||||
|
public void setup() throws Exception { |
||||
|
log.info("Executing setup"); |
||||
|
if (this.mockMvc == null) { |
||||
|
this.mockMvc = webAppContextSetup(webApplicationContext) |
||||
|
.apply(springSecurity()).build(); |
||||
|
} |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
Tenant tenant = new Tenant(); |
||||
|
tenant.setTitle(TEST_TENANT_NAME); |
||||
|
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
||||
|
Assert.assertNotNull(savedTenant); |
||||
|
tenantId = savedTenant.getId(); |
||||
|
|
||||
|
User tenantAdmin = new User(); |
||||
|
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
||||
|
tenantAdmin.setTenantId(tenantId); |
||||
|
tenantAdmin.setEmail(TENANT_ADMIN_EMAIL); |
||||
|
|
||||
|
createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD); |
||||
|
|
||||
|
Customer customer = new Customer(); |
||||
|
customer.setTitle("Customer"); |
||||
|
customer.setTenantId(tenantId); |
||||
|
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); |
||||
|
|
||||
|
User customerUser = new User(); |
||||
|
customerUser.setAuthority(Authority.CUSTOMER_USER); |
||||
|
customerUser.setTenantId(tenantId); |
||||
|
customerUser.setCustomerId(savedCustomer.getId()); |
||||
|
customerUser.setEmail(CUSTOMER_USER_EMAIL); |
||||
|
|
||||
|
createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD); |
||||
|
|
||||
|
logout(); |
||||
|
|
||||
|
log.info("Executed setup"); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void teardown() throws Exception { |
||||
|
log.info("Executing teardown"); |
||||
|
loginSysAdmin(); |
||||
|
doDelete("/api/tenant/" + tenantId.getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
log.info("Executed teardown"); |
||||
|
} |
||||
|
|
||||
|
protected void loginSysAdmin() throws Exception { |
||||
|
login(SYS_ADMIN_EMAIL, SYS_ADMIN_PASSWORD); |
||||
|
} |
||||
|
|
||||
|
protected void loginTenantAdmin() throws Exception { |
||||
|
login(TENANT_ADMIN_EMAIL, TENANT_ADMIN_PASSWORD); |
||||
|
} |
||||
|
|
||||
|
protected void loginCustomerUser() throws Exception { |
||||
|
login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD); |
||||
|
} |
||||
|
|
||||
|
private Tenant savedDifferentTenant; |
||||
|
protected void loginDifferentTenant() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
Tenant tenant = new Tenant(); |
||||
|
tenant.setTitle("Different tenant"); |
||||
|
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class); |
||||
|
Assert.assertNotNull(savedDifferentTenant); |
||||
|
User differentTenantAdmin = new User(); |
||||
|
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
||||
|
differentTenantAdmin.setTenantId(savedDifferentTenant.getId()); |
||||
|
differentTenantAdmin.setEmail("different_tenant@thingsboard.org"); |
||||
|
|
||||
|
createUserAndLogin(differentTenantAdmin, "testPassword"); |
||||
|
} |
||||
|
|
||||
|
protected void deleteDifferentTenant() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
protected User createUserAndLogin(User user, String password) throws Exception { |
||||
|
User savedUser = doPost("/api/user", user, User.class); |
||||
|
logout(); |
||||
|
doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) |
||||
|
.andExpect(status().isSeeOther()) |
||||
|
.andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); |
||||
|
JsonNode activateRequest = new ObjectMapper().createObjectNode() |
||||
|
.put("activateToken", TestMailService.currentActivateToken) |
||||
|
.put("password", password); |
||||
|
JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class); |
||||
|
validateAndSetJwtToken(tokenInfo, user.getEmail()); |
||||
|
return savedUser; |
||||
|
} |
||||
|
|
||||
|
protected void login(String username, String password) throws Exception { |
||||
|
this.token = null; |
||||
|
this.refreshToken = null; |
||||
|
this.username = null; |
||||
|
JsonNode tokenInfo = readResponse(doPost("/api/auth/login", new LoginRequest(username, password)).andExpect(status().isOk()), JsonNode.class); |
||||
|
validateAndSetJwtToken(tokenInfo, username); |
||||
|
} |
||||
|
|
||||
|
protected void refreshToken() throws Exception { |
||||
|
this.token = null; |
||||
|
JsonNode tokenInfo = readResponse(doPost("/api/auth/token", new RefreshTokenRequest(this.refreshToken)).andExpect(status().isOk()), JsonNode.class); |
||||
|
validateAndSetJwtToken(tokenInfo, this.username); |
||||
|
} |
||||
|
|
||||
|
protected void validateAndSetJwtToken(JsonNode tokenInfo, String username) { |
||||
|
Assert.assertNotNull(tokenInfo); |
||||
|
Assert.assertTrue(tokenInfo.has("token")); |
||||
|
Assert.assertTrue(tokenInfo.has("refreshToken")); |
||||
|
String token = tokenInfo.get("token").asText(); |
||||
|
String refreshToken = tokenInfo.get("refreshToken").asText(); |
||||
|
validateJwtToken(token, username); |
||||
|
validateJwtToken(refreshToken, username); |
||||
|
this.token = token; |
||||
|
this.refreshToken = refreshToken; |
||||
|
this.username = username; |
||||
|
} |
||||
|
|
||||
|
protected void validateJwtToken(String token, String username) { |
||||
|
Assert.assertNotNull(token); |
||||
|
Assert.assertFalse(token.isEmpty()); |
||||
|
int i = token.lastIndexOf('.'); |
||||
|
Assert.assertTrue(i > 0); |
||||
|
String withoutSignature = token.substring(0, i + 1); |
||||
|
Jwt<Header, Claims> jwsClaims = Jwts.parser().parseClaimsJwt(withoutSignature); |
||||
|
Claims claims = jwsClaims.getBody(); |
||||
|
String subject = claims.getSubject(); |
||||
|
Assert.assertEquals(username, subject); |
||||
|
} |
||||
|
|
||||
|
protected void logout() throws Exception { |
||||
|
this.token = null; |
||||
|
this.refreshToken = null; |
||||
|
this.username = null; |
||||
|
} |
||||
|
|
||||
|
protected void setJwtToken(MockHttpServletRequestBuilder request) { |
||||
|
if (this.token != null) { |
||||
|
request.header(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM, "Bearer " + this.token); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception { |
||||
|
MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables); |
||||
|
setJwtToken(getRequest); |
||||
|
return mockMvc.perform(getRequest); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doGet(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception { |
||||
|
return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doGetAsync(String urlTemplate, Class<T> responseClass, Object... urlVariables) throws Exception { |
||||
|
return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception { |
||||
|
MockHttpServletRequestBuilder getRequest; |
||||
|
getRequest = get(urlTemplate, urlVariables); |
||||
|
setJwtToken(getRequest); |
||||
|
return mockMvc.perform(asyncDispatch(mockMvc.perform(getRequest).andExpect(request().asyncStarted()).andReturn())); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doGetTyped(String urlTemplate, TypeReference<T> responseType, Object... urlVariables) throws Exception { |
||||
|
return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseType); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doGetTypedWithPageLink(String urlTemplate, TypeReference<T> responseType, |
||||
|
PageLink pageLink, |
||||
|
Object... urlVariables) throws Exception { |
||||
|
List<Object> pageLinkVariables = new ArrayList<>(); |
||||
|
urlTemplate += "pageSize={pageSize}&page={page}"; |
||||
|
pageLinkVariables.add(pageLink.getPageSize()); |
||||
|
pageLinkVariables.add(pageLink.getPage()); |
||||
|
if (StringUtils.isNotEmpty(pageLink.getTextSearch())) { |
||||
|
urlTemplate += "&textSearch={textSearch}"; |
||||
|
pageLinkVariables.add(pageLink.getTextSearch()); |
||||
|
} |
||||
|
if (pageLink.getSortOrder() != null) { |
||||
|
urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}"; |
||||
|
pageLinkVariables.add(pageLink.getSortOrder().getProperty()); |
||||
|
pageLinkVariables.add(pageLink.getSortOrder().getDirection().name()); |
||||
|
} |
||||
|
|
||||
|
Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; |
||||
|
System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); |
||||
|
System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size()); |
||||
|
|
||||
|
return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType, |
||||
|
TimePageLink pageLink, |
||||
|
Object... urlVariables) throws Exception { |
||||
|
List<Object> pageLinkVariables = new ArrayList<>(); |
||||
|
urlTemplate += "pageSize={pageSize}&page={page}"; |
||||
|
pageLinkVariables.add(pageLink.getPageSize()); |
||||
|
pageLinkVariables.add(pageLink.getPage()); |
||||
|
if (pageLink.getStartTime() != null) { |
||||
|
urlTemplate += "&startTime={startTime}"; |
||||
|
pageLinkVariables.add(pageLink.getStartTime()); |
||||
|
} |
||||
|
if (pageLink.getEndTime() != null) { |
||||
|
urlTemplate += "&endTime={endTime}"; |
||||
|
pageLinkVariables.add(pageLink.getEndTime()); |
||||
|
} |
||||
|
if (StringUtils.isNotEmpty(pageLink.getTextSearch())) { |
||||
|
urlTemplate += "&textSearch={textSearch}"; |
||||
|
pageLinkVariables.add(pageLink.getTextSearch()); |
||||
|
} |
||||
|
if (pageLink.getSortOrder() != null) { |
||||
|
urlTemplate += "&sortProperty={sortProperty}&sortOrder={sortOrder}"; |
||||
|
pageLinkVariables.add(pageLink.getSortOrder().getProperty()); |
||||
|
pageLinkVariables.add(pageLink.getSortOrder().getDirection().name()); |
||||
|
} |
||||
|
Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; |
||||
|
System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); |
||||
|
System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size()); |
||||
|
|
||||
|
return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception { |
||||
|
return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception { |
||||
|
return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, String... params) throws Exception { |
||||
|
return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected <T,R> R doPostWithResponse(String urlTemplate, T content, Class<R> responseClass, String... params) throws Exception { |
||||
|
return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected <T,R> R doPostWithTypedResponse(String urlTemplate, T content, TypeReference<R> responseType, String... params) throws Exception { |
||||
|
return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseType); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception { |
||||
|
return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doPostAsync(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, Long timeout, String... params) throws Exception { |
||||
|
return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception { |
||||
|
return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass); |
||||
|
} |
||||
|
|
||||
|
protected ResultActions doPost(String urlTemplate, String... params) throws Exception { |
||||
|
MockHttpServletRequestBuilder postRequest = post(urlTemplate); |
||||
|
setJwtToken(postRequest); |
||||
|
populateParams(postRequest, params); |
||||
|
return mockMvc.perform(postRequest); |
||||
|
} |
||||
|
|
||||
|
protected <T> ResultActions doPost(String urlTemplate, T content, String... params) throws Exception { |
||||
|
MockHttpServletRequestBuilder postRequest = post(urlTemplate); |
||||
|
setJwtToken(postRequest); |
||||
|
String json = json(content); |
||||
|
postRequest.contentType(contentType).content(json); |
||||
|
return mockMvc.perform(postRequest); |
||||
|
} |
||||
|
|
||||
|
protected <T> ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception { |
||||
|
MockHttpServletRequestBuilder postRequest = post(urlTemplate); |
||||
|
setJwtToken(postRequest); |
||||
|
String json = json(content); |
||||
|
postRequest.contentType(contentType).content(json); |
||||
|
MvcResult result = mockMvc.perform(postRequest).andReturn(); |
||||
|
result.getAsyncResult(timeout); |
||||
|
return mockMvc.perform(asyncDispatch(result)); |
||||
|
} |
||||
|
|
||||
|
protected ResultActions doDelete(String urlTemplate, String... params) throws Exception { |
||||
|
MockHttpServletRequestBuilder deleteRequest = delete(urlTemplate); |
||||
|
setJwtToken(deleteRequest); |
||||
|
populateParams(deleteRequest, params); |
||||
|
return mockMvc.perform(deleteRequest); |
||||
|
} |
||||
|
|
||||
|
protected void populateParams(MockHttpServletRequestBuilder request, String... params) { |
||||
|
if (params != null && params.length > 0) { |
||||
|
Assert.assertEquals(0, params.length % 2); |
||||
|
MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>(); |
||||
|
for (int i = 0; i < params.length; i += 2) { |
||||
|
paramsMap.add(params[i], params[i + 1]); |
||||
|
} |
||||
|
request.params(paramsMap); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
protected String json(Object o) throws IOException { |
||||
|
MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage(); |
||||
|
|
||||
|
HttpMessageConverter converter = o instanceof String ? stringHttpMessageConverter : mappingJackson2HttpMessageConverter; |
||||
|
converter.write(o, MediaType.APPLICATION_JSON, mockHttpOutputMessage); |
||||
|
return mockHttpOutputMessage.getBodyAsString(); |
||||
|
} |
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
protected <T> T readResponse(ResultActions result, Class<T> responseClass) throws Exception { |
||||
|
byte[] content = result.andReturn().getResponse().getContentAsByteArray(); |
||||
|
MockHttpInputMessage mockHttpInputMessage = new MockHttpInputMessage(content); |
||||
|
HttpMessageConverter converter = responseClass.equals(String.class) ? stringHttpMessageConverter : mappingJackson2HttpMessageConverter; |
||||
|
return (T) converter.read(responseClass, mockHttpInputMessage); |
||||
|
} |
||||
|
|
||||
|
protected <T> T readResponse(ResultActions result, TypeReference<T> type) throws Exception { |
||||
|
byte[] content = result.andReturn().getResponse().getContentAsByteArray(); |
||||
|
ObjectMapper mapper = new ObjectMapper(); |
||||
|
return mapper.readerFor(type).readValue(content); |
||||
|
} |
||||
|
|
||||
|
public class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> { |
||||
|
@Override |
||||
|
public int compare(D o1, D o2) { |
||||
|
return o1.getId().getId().compareTo(o2.getId().getId()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected static <T> ResultMatcher statusReason(Matcher<T> matcher) { |
||||
|
return jsonPath("$.message", matcher); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
/** |
||||
|
* 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 com.fasterxml.jackson.core.type.TypeReference; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import io.jsonwebtoken.Claims; |
||||
|
import io.jsonwebtoken.Header; |
||||
|
import io.jsonwebtoken.Jwt; |
||||
|
import io.jsonwebtoken.Jwts; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.hamcrest.Matcher; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Rule; |
||||
|
import org.junit.rules.TestRule; |
||||
|
import org.junit.rules.TestWatcher; |
||||
|
import org.junit.runner.Description; |
||||
|
import org.junit.runner.RunWith; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.test.context.SpringBootContextLoader; |
||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
import org.springframework.boot.web.server.LocalServerPort; |
||||
|
import org.springframework.context.annotation.ComponentScan; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.http.HttpHeaders; |
||||
|
import org.springframework.http.MediaType; |
||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||
|
import org.springframework.http.converter.StringHttpMessageConverter; |
||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||
|
import org.springframework.mock.http.MockHttpInputMessage; |
||||
|
import org.springframework.mock.http.MockHttpOutputMessage; |
||||
|
import org.springframework.test.annotation.DirtiesContext; |
||||
|
import org.springframework.test.context.ActiveProfiles; |
||||
|
import org.springframework.test.context.ContextConfiguration; |
||||
|
import org.springframework.test.context.junit4.SpringRunner; |
||||
|
import org.springframework.test.web.servlet.MockMvc; |
||||
|
import org.springframework.test.web.servlet.MvcResult; |
||||
|
import org.springframework.test.web.servlet.ResultActions; |
||||
|
import org.springframework.test.web.servlet.ResultMatcher; |
||||
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; |
||||
|
import org.springframework.util.LinkedMultiValueMap; |
||||
|
import org.springframework.util.MultiValueMap; |
||||
|
import org.springframework.web.context.WebApplicationContext; |
||||
|
import org.thingsboard.server.common.data.BaseData; |
||||
|
import org.thingsboard.server.common.data.Customer; |
||||
|
import org.thingsboard.server.common.data.Tenant; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UUIDBased; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.page.TimePageLink; |
||||
|
import org.thingsboard.server.common.data.security.Authority; |
||||
|
import org.thingsboard.server.config.ThingsboardSecurityConfiguration; |
||||
|
import org.thingsboard.server.service.mail.TestMailService; |
||||
|
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; |
||||
|
import org.thingsboard.server.service.security.auth.rest.LoginRequest; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Comparator; |
||||
|
import java.util.List; |
||||
|
|
||||
|
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; |
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; |
||||
|
|
||||
|
@ActiveProfiles("test") |
||||
|
@RunWith(SpringRunner.class) |
||||
|
@ContextConfiguration(classes = AbstractControllerTest.class, loader = SpringBootContextLoader.class) |
||||
|
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) |
||||
|
@Configuration |
||||
|
@ComponentScan({"org.thingsboard.server"}) |
||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) |
||||
|
@Slf4j |
||||
|
public abstract class AbstractWebsocketTest extends AbstractWebTest { |
||||
|
|
||||
|
protected static final String WS_URL = "ws://localhost:"; |
||||
|
|
||||
|
@LocalServerPort |
||||
|
protected int wsPort; |
||||
|
|
||||
|
protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { |
||||
|
TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token)); |
||||
|
Assert.assertTrue(wsClient.connectBlocking()); |
||||
|
return wsClient; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,288 @@ |
|||||
|
/** |
||||
|
* 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 com.fasterxml.jackson.core.type.TypeReference; |
||||
|
import com.google.gson.JsonArray; |
||||
|
import com.google.gson.JsonObject; |
||||
|
import org.apache.http.conn.ssl.TrustStrategy; |
||||
|
import org.apache.http.ssl.SSLContextBuilder; |
||||
|
import org.apache.http.ssl.SSLContexts; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.springframework.boot.web.server.LocalServerPort; |
||||
|
import org.thingsboard.server.common.data.DataConstants; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.Tenant; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.kv.Aggregation; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.query.DeviceTypeFilter; |
||||
|
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.common.data.query.EntityListFilter; |
||||
|
import org.thingsboard.server.common.data.query.FilterPredicateValue; |
||||
|
import org.thingsboard.server.common.data.query.KeyFilter; |
||||
|
import org.thingsboard.server.common.data.query.NumericFilterPredicate; |
||||
|
import org.thingsboard.server.common.data.security.Authority; |
||||
|
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
||||
|
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; |
||||
|
|
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.Random; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
|
||||
|
public abstract class BaseEntityQueryControllerTest extends AbstractControllerTest { |
||||
|
|
||||
|
private Tenant savedTenant; |
||||
|
private User tenantAdmin; |
||||
|
|
||||
|
@Before |
||||
|
public void beforeTest() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
Tenant tenant = new Tenant(); |
||||
|
tenant.setTitle("My tenant"); |
||||
|
savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
||||
|
Assert.assertNotNull(savedTenant); |
||||
|
|
||||
|
tenantAdmin = new User(); |
||||
|
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
||||
|
tenantAdmin.setTenantId(savedTenant.getId()); |
||||
|
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
||||
|
tenantAdmin.setFirstName("Joe"); |
||||
|
tenantAdmin.setLastName("Downs"); |
||||
|
|
||||
|
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void afterTest() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testCountEntitiesByQuery() throws Exception { |
||||
|
List<Device> devices = new ArrayList<>(); |
||||
|
for (int i = 0; i < 97; i++) { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device" + i); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
devices.add(doPost("/api/device", device, Device.class)); |
||||
|
Thread.sleep(1); |
||||
|
} |
||||
|
DeviceTypeFilter filter = new DeviceTypeFilter(); |
||||
|
filter.setDeviceType("default"); |
||||
|
filter.setDeviceNameFilter(""); |
||||
|
|
||||
|
EntityCountQuery countQuery = new EntityCountQuery(filter); |
||||
|
|
||||
|
Long count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class); |
||||
|
Assert.assertEquals(97, count.longValue()); |
||||
|
|
||||
|
filter.setDeviceType("unknown"); |
||||
|
count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class); |
||||
|
Assert.assertEquals(0, count.longValue()); |
||||
|
|
||||
|
filter.setDeviceType("default"); |
||||
|
filter.setDeviceNameFilter("Device1"); |
||||
|
|
||||
|
count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class); |
||||
|
Assert.assertEquals(11, count.longValue()); |
||||
|
|
||||
|
EntityListFilter entityListFilter = new EntityListFilter(); |
||||
|
entityListFilter.setEntityType(EntityType.DEVICE); |
||||
|
entityListFilter.setEntityList(devices.stream().map(Device::getId).map(DeviceId::toString).collect(Collectors.toList())); |
||||
|
|
||||
|
countQuery = new EntityCountQuery(entityListFilter); |
||||
|
|
||||
|
count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class); |
||||
|
Assert.assertEquals(97, count.longValue()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testSimpleFindEntityDataByQuery() throws Exception { |
||||
|
List<Device> devices = new ArrayList<>(); |
||||
|
for (int i = 0; i < 97; i++) { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device" + i); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
devices.add(doPost("/api/device", device, Device.class)); |
||||
|
Thread.sleep(1); |
||||
|
} |
||||
|
|
||||
|
DeviceTypeFilter filter = new DeviceTypeFilter(); |
||||
|
filter.setDeviceType("default"); |
||||
|
filter.setDeviceNameFilter(""); |
||||
|
|
||||
|
EntityDataSortOrder sortOrder = new EntityDataSortOrder( |
||||
|
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC |
||||
|
); |
||||
|
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); |
||||
|
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); |
||||
|
|
||||
|
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, null); |
||||
|
|
||||
|
PageData<EntityData> data = |
||||
|
doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() { |
||||
|
}); |
||||
|
|
||||
|
Assert.assertEquals(97, data.getTotalElements()); |
||||
|
Assert.assertEquals(10, data.getTotalPages()); |
||||
|
Assert.assertTrue(data.hasNext()); |
||||
|
Assert.assertEquals(10, data.getData().size()); |
||||
|
|
||||
|
List<EntityData> loadedEntities = new ArrayList<>(data.getData()); |
||||
|
while (data.hasNext()) { |
||||
|
query = query.next(); |
||||
|
data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() { |
||||
|
}); |
||||
|
loadedEntities.addAll(data.getData()); |
||||
|
} |
||||
|
Assert.assertEquals(97, loadedEntities.size()); |
||||
|
|
||||
|
List<EntityId> loadedIds = loadedEntities.stream().map(EntityData::getEntityId).collect(Collectors.toList()); |
||||
|
List<EntityId> deviceIds = devices.stream().map(Device::getId).collect(Collectors.toList()); |
||||
|
|
||||
|
Assert.assertEquals(deviceIds, loadedIds); |
||||
|
|
||||
|
List<String> loadedNames = loadedEntities.stream().map(entityData -> |
||||
|
entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).collect(Collectors.toList()); |
||||
|
List<String> deviceNames = devices.stream().map(Device::getName).collect(Collectors.toList()); |
||||
|
|
||||
|
Assert.assertEquals(deviceNames, loadedNames); |
||||
|
|
||||
|
sortOrder = new EntityDataSortOrder( |
||||
|
new EntityKey(EntityKeyType.ENTITY_FIELD, "name"), EntityDataSortOrder.Direction.DESC |
||||
|
); |
||||
|
|
||||
|
pageLink = new EntityDataPageLink(10, 0, "device1", sortOrder); |
||||
|
query = new EntityDataQuery(filter, pageLink, entityFields, null, null); |
||||
|
data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() { |
||||
|
}); |
||||
|
Assert.assertEquals(11, data.getTotalElements()); |
||||
|
Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testFindEntityDataByQueryWithAttributes() throws Exception { |
||||
|
|
||||
|
List<Device> devices = new ArrayList<>(); |
||||
|
List<Long> temperatures = new ArrayList<>(); |
||||
|
List<Long> highTemperatures = new ArrayList<>(); |
||||
|
for (int i = 0; i < 67; i++) { |
||||
|
Device device = new Device(); |
||||
|
String name = "Device" + i; |
||||
|
device.setName(name); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
devices.add(doPost("/api/device?accessToken=" + name, device, Device.class)); |
||||
|
Thread.sleep(1); |
||||
|
long temperature = (long) (Math.random() * 100); |
||||
|
temperatures.add(temperature); |
||||
|
if (temperature > 45) { |
||||
|
highTemperatures.add(temperature); |
||||
|
} |
||||
|
} |
||||
|
for (int i = 0; i < devices.size(); i++) { |
||||
|
Device device = devices.get(i); |
||||
|
String payload = "{\"temperature\":" + temperatures.get(i) + "}"; |
||||
|
doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, payload, String.class, status().isOk()); |
||||
|
} |
||||
|
Thread.sleep(1000); |
||||
|
|
||||
|
DeviceTypeFilter filter = new DeviceTypeFilter(); |
||||
|
filter.setDeviceType("default"); |
||||
|
filter.setDeviceNameFilter(""); |
||||
|
|
||||
|
EntityDataSortOrder sortOrder = new EntityDataSortOrder( |
||||
|
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC |
||||
|
); |
||||
|
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); |
||||
|
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); |
||||
|
List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); |
||||
|
|
||||
|
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null); |
||||
|
PageData<EntityData> data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() { |
||||
|
}); |
||||
|
|
||||
|
List<EntityData> loadedEntities = new ArrayList<>(data.getData()); |
||||
|
while (data.hasNext()) { |
||||
|
query = query.next(); |
||||
|
data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() { |
||||
|
}); |
||||
|
loadedEntities.addAll(data.getData()); |
||||
|
} |
||||
|
Assert.assertEquals(67, loadedEntities.size()); |
||||
|
|
||||
|
List<String> loadedTemperatures = loadedEntities.stream().map(entityData -> |
||||
|
entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList()); |
||||
|
List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList()); |
||||
|
Assert.assertEquals(deviceTemperatures, loadedTemperatures); |
||||
|
|
||||
|
pageLink = new EntityDataPageLink(10, 0, null, sortOrder); |
||||
|
KeyFilter highTemperatureFilter = new KeyFilter(); |
||||
|
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); |
||||
|
NumericFilterPredicate predicate = new NumericFilterPredicate(); |
||||
|
predicate.setValue(FilterPredicateValue.fromDouble(45)); |
||||
|
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); |
||||
|
highTemperatureFilter.setPredicate(predicate); |
||||
|
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter); |
||||
|
|
||||
|
query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters); |
||||
|
|
||||
|
data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() { |
||||
|
}); |
||||
|
loadedEntities = new ArrayList<>(data.getData()); |
||||
|
while (data.hasNext()) { |
||||
|
query = query.next(); |
||||
|
data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() { |
||||
|
}); |
||||
|
loadedEntities.addAll(data.getData()); |
||||
|
} |
||||
|
Assert.assertEquals(highTemperatures.size(), loadedEntities.size()); |
||||
|
|
||||
|
List<String> loadedHighTemperatures = loadedEntities.stream().map(entityData -> |
||||
|
entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("temperature").getValue()).collect(Collectors.toList()); |
||||
|
List<String> deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList()); |
||||
|
|
||||
|
Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures); |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,680 @@ |
|||||
|
/** |
||||
|
* 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 com.google.common.util.concurrent.FutureCallback; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.checkerframework.checker.nullness.qual.Nullable; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Ignore; |
||||
|
import org.junit.Test; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.Tenant; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.kv.Aggregation; |
||||
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.LongDataEntry; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.query.DeviceTypeFilter; |
||||
|
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.TsValue; |
||||
|
import org.thingsboard.server.common.data.security.Authority; |
||||
|
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; |
||||
|
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
||||
|
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; |
||||
|
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.LatestValueCmd; |
||||
|
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.CountDownLatch; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class BaseWebsocketApiTest extends AbstractWebsocketTest { |
||||
|
|
||||
|
private Tenant savedTenant; |
||||
|
private User tenantAdmin; |
||||
|
private TbTestWebSocketClient wsClient; |
||||
|
|
||||
|
@Autowired |
||||
|
private TelemetrySubscriptionService tsService; |
||||
|
|
||||
|
@Before |
||||
|
public void beforeTest() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
Tenant tenant = new Tenant(); |
||||
|
tenant.setTitle("My tenant"); |
||||
|
savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
||||
|
Assert.assertNotNull(savedTenant); |
||||
|
|
||||
|
tenantAdmin = new User(); |
||||
|
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
||||
|
tenantAdmin.setTenantId(savedTenant.getId()); |
||||
|
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
||||
|
tenantAdmin.setFirstName("Joe"); |
||||
|
tenantAdmin.setLastName("Downs"); |
||||
|
|
||||
|
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); |
||||
|
|
||||
|
wsClient = buildAndConnectWebSocketClient(); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void afterTest() throws Exception { |
||||
|
wsClient.close(); |
||||
|
|
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testEntityDataHistoryWsCmd() throws Exception { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device"); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
device = doPost("/api/device", device, Device.class); |
||||
|
|
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
DeviceTypeFilter dtf = new DeviceTypeFilter(); |
||||
|
dtf.setDeviceNameFilter("D"); |
||||
|
dtf.setDeviceType("default"); |
||||
|
EntityDataQuery edq = new EntityDataQuery(dtf, |
||||
|
new EntityDataPageLink(1, 0, null, null), |
||||
|
Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); |
||||
|
|
||||
|
EntityHistoryCmd historyCmd = new EntityHistoryCmd(); |
||||
|
historyCmd.setKeys(Arrays.asList("temperature")); |
||||
|
historyCmd.setAgg(Aggregation.NONE); |
||||
|
historyCmd.setLimit(1000); |
||||
|
historyCmd.setStartTs(now - TimeUnit.HOURS.toMillis(1)); |
||||
|
historyCmd.setEndTs(now); |
||||
|
EntityDataCmd cmd = new EntityDataCmd(1, edq, historyCmd, null, null); |
||||
|
|
||||
|
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
String msg = wsClient.waitForReply(); |
||||
|
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
PageData<EntityData> pageData = update.getData(); |
||||
|
Assert.assertNotNull(pageData); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getTimeseries().get("temperature").length); |
||||
|
|
||||
|
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); |
||||
|
TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 42L)); |
||||
|
TsKvEntry dataPoint3 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(3), new LongDataEntry("temperature", 42L)); |
||||
|
List<TsKvEntry> tsData = Arrays.asList(dataPoint1, dataPoint2, dataPoint3); |
||||
|
|
||||
|
sendTelemetry(device, tsData); |
||||
|
Thread.sleep(100); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
msg = wsClient.waitForReply(); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> dataList = update.getUpdate(); |
||||
|
Assert.assertNotNull(dataList); |
||||
|
Assert.assertEquals(1, dataList.size()); |
||||
|
Assert.assertEquals(device.getId(), dataList.get(0).getEntityId()); |
||||
|
TsValue[] tsArray = dataList.get(0).getTimeseries().get("temperature"); |
||||
|
Assert.assertEquals(3, tsArray.length); |
||||
|
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]); |
||||
|
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]); |
||||
|
Assert.assertEquals(new TsValue(dataPoint3.getTs(), dataPoint3.getValueAsString()), tsArray[2]); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testEntityDataTimeSeriesWsCmd() throws Exception { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device"); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
device = doPost("/api/device", device, Device.class); |
||||
|
|
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
DeviceTypeFilter dtf = new DeviceTypeFilter(); |
||||
|
dtf.setDeviceNameFilter("D"); |
||||
|
dtf.setDeviceType("default"); |
||||
|
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), |
||||
|
Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); |
||||
|
|
||||
|
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null); |
||||
|
|
||||
|
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
String msg = wsClient.waitForReply(); |
||||
|
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
PageData<EntityData> pageData = update.getData(); |
||||
|
Assert.assertNotNull(pageData); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); |
||||
|
|
||||
|
TimeSeriesCmd tsCmd = new TimeSeriesCmd(); |
||||
|
tsCmd.setKeys(Arrays.asList("temperature")); |
||||
|
tsCmd.setAgg(Aggregation.NONE); |
||||
|
tsCmd.setLimit(1000); |
||||
|
tsCmd.setStartTs(now - TimeUnit.HOURS.toMillis(1)); |
||||
|
tsCmd.setTimeWindow(TimeUnit.HOURS.toMillis(1)); |
||||
|
|
||||
|
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); |
||||
|
TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 43L)); |
||||
|
TsKvEntry dataPoint3 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(3), new LongDataEntry("temperature", 44L)); |
||||
|
List<TsKvEntry> tsData = Arrays.asList(dataPoint1, dataPoint2, dataPoint3); |
||||
|
|
||||
|
sendTelemetry(device, tsData); |
||||
|
Thread.sleep(100); |
||||
|
|
||||
|
cmd = new EntityDataCmd(1, null, null, null, tsCmd); |
||||
|
wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
msg = wsClient.waitForReply(); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> listData = update.getUpdate(); |
||||
|
Assert.assertNotNull(listData); |
||||
|
Assert.assertEquals(1, listData.size()); |
||||
|
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); |
||||
|
TsValue[] tsArray = listData.get(0).getTimeseries().get("temperature"); |
||||
|
Assert.assertEquals(3, tsArray.length); |
||||
|
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsArray[0]); |
||||
|
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsArray[1]); |
||||
|
Assert.assertEquals(new TsValue(dataPoint3.getTs(), dataPoint3.getValueAsString()), tsArray[2]); |
||||
|
|
||||
|
now = System.currentTimeMillis(); |
||||
|
TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L)); |
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
Thread.sleep(100); |
||||
|
sendTelemetry(device, Arrays.asList(dataPoint4)); |
||||
|
msg = wsClient.waitForUpdate(); |
||||
|
|
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getTimeseries()); |
||||
|
TsValue[] tsValues = eData.get(0).getTimeseries().get("temperature"); |
||||
|
Assert.assertNotNull(tsValues); |
||||
|
Assert.assertEquals(new TsValue(dataPoint4.getTs(), dataPoint4.getValueAsString()), tsValues[0]); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testEntityDataLatestWidgetFlow() throws Exception { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device"); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
device = doPost("/api/device", device, Device.class); |
||||
|
|
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
DeviceTypeFilter dtf = new DeviceTypeFilter(); |
||||
|
dtf.setDeviceNameFilter("D"); |
||||
|
dtf.setDeviceType("default"); |
||||
|
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), Collections.emptyList(), |
||||
|
Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")), Collections.emptyList()); |
||||
|
|
||||
|
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null); |
||||
|
|
||||
|
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
String msg = wsClient.waitForReply(); |
||||
|
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
PageData<EntityData> pageData = update.getData(); |
||||
|
Assert.assertNotNull(pageData); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); |
||||
|
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature")); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs()); |
||||
|
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()); |
||||
|
|
||||
|
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); |
||||
|
List<TsKvEntry> tsData = Arrays.asList(dataPoint1); |
||||
|
sendTelemetry(device, tsData); |
||||
|
|
||||
|
Thread.sleep(100); |
||||
|
|
||||
|
LatestValueCmd latestCmd = new LatestValueCmd(); |
||||
|
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"))); |
||||
|
cmd = new EntityDataCmd(1, null, null, latestCmd, null); |
||||
|
wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
msg = wsClient.waitForReply(); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
|
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
|
||||
|
List<EntityData> listData = update.getUpdate(); |
||||
|
Assert.assertNotNull(listData); |
||||
|
Assert.assertEquals(1, listData.size()); |
||||
|
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); |
||||
|
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue); |
||||
|
|
||||
|
now = System.currentTimeMillis(); |
||||
|
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); |
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendTelemetry(device, Arrays.asList(dataPoint2)); |
||||
|
msg = wsClient.waitForUpdate(); |
||||
|
|
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); |
||||
|
tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); |
||||
|
|
||||
|
//Sending update from the past, while latest value has new timestamp;
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendTelemetry(device, Arrays.asList(dataPoint1)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
|
||||
|
//Sending duplicate update again
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendTelemetry(device, Arrays.asList(dataPoint2)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testEntityDataLatestTsWsCmd() throws Exception { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device"); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
device = doPost("/api/device", device, Device.class); |
||||
|
|
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
DeviceTypeFilter dtf = new DeviceTypeFilter(); |
||||
|
dtf.setDeviceNameFilter("D"); |
||||
|
dtf.setDeviceType("default"); |
||||
|
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); |
||||
|
|
||||
|
LatestValueCmd latestCmd = new LatestValueCmd(); |
||||
|
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"))); |
||||
|
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); |
||||
|
|
||||
|
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
String msg = wsClient.waitForReply(); |
||||
|
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
PageData<EntityData> pageData = update.getData(); |
||||
|
Assert.assertNotNull(pageData); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); |
||||
|
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature")); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs()); |
||||
|
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()); |
||||
|
|
||||
|
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); |
||||
|
List<TsKvEntry> tsData = Arrays.asList(dataPoint1); |
||||
|
sendTelemetry(device, tsData); |
||||
|
|
||||
|
Thread.sleep(100); |
||||
|
|
||||
|
cmd = new EntityDataCmd(1, edq, null, latestCmd, null); |
||||
|
wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
msg = wsClient.waitForReply(); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
|
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
|
||||
|
List<EntityData> listData = update.getUpdate(); |
||||
|
Assert.assertNotNull(listData); |
||||
|
Assert.assertEquals(1, listData.size()); |
||||
|
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); |
||||
|
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint1.getTs(), dataPoint1.getValueAsString()), tsValue); |
||||
|
|
||||
|
now = System.currentTimeMillis(); |
||||
|
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); |
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendTelemetry(device, Arrays.asList(dataPoint2)); |
||||
|
msg = wsClient.waitForUpdate(); |
||||
|
|
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); |
||||
|
tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); |
||||
|
|
||||
|
//Sending update from the past, while latest value has new timestamp;
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendTelemetry(device, Arrays.asList(dataPoint1)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
|
||||
|
//Sending duplicate update again
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendTelemetry(device, Arrays.asList(dataPoint2)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testEntityDataLatestAttrWsCmd() throws Exception { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device"); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
device = doPost("/api/device", device, Device.class); |
||||
|
|
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
DeviceTypeFilter dtf = new DeviceTypeFilter(); |
||||
|
dtf.setDeviceNameFilter("D"); |
||||
|
dtf.setDeviceType("default"); |
||||
|
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), |
||||
|
Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); |
||||
|
|
||||
|
LatestValueCmd latestCmd = new LatestValueCmd(); |
||||
|
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey"))); |
||||
|
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); |
||||
|
|
||||
|
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
String msg = wsClient.waitForReply(); |
||||
|
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
PageData<EntityData> pageData = update.getData(); |
||||
|
Assert.assertNotNull(pageData); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); |
||||
|
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey")); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs()); |
||||
|
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue()); |
||||
|
|
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
Thread.sleep(100); |
||||
|
|
||||
|
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); |
||||
|
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); |
||||
|
|
||||
|
msg = wsClient.waitForUpdate(); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
|
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> listData = update.getUpdate(); |
||||
|
Assert.assertNotNull(listData); |
||||
|
Assert.assertEquals(1, listData.size()); |
||||
|
Assert.assertEquals(device.getId(), listData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(listData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE)); |
||||
|
TsValue tsValue = listData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), tsValue); |
||||
|
|
||||
|
now = System.currentTimeMillis(); |
||||
|
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L)); |
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
Thread.sleep(100); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); |
||||
|
msg = wsClient.waitForUpdate(); |
||||
|
|
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE)); |
||||
|
tsValue = eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), tsValue); |
||||
|
|
||||
|
//Sending update from the past, while latest value has new timestamp;
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
Thread.sleep(100); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
|
||||
|
//Sending duplicate update again
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
Thread.sleep(100); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testEntityDataLatestAttrTypesWsCmd() throws Exception { |
||||
|
Device device = new Device(); |
||||
|
device.setName("Device"); |
||||
|
device.setType("default"); |
||||
|
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
||||
|
device = doPost("/api/device", device, Device.class); |
||||
|
|
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
DeviceTypeFilter dtf = new DeviceTypeFilter(); |
||||
|
dtf.setDeviceNameFilter("D"); |
||||
|
dtf.setDeviceType("default"); |
||||
|
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), |
||||
|
Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); |
||||
|
|
||||
|
LatestValueCmd latestCmd = new LatestValueCmd(); |
||||
|
List<EntityKey> keys = new ArrayList<>(); |
||||
|
keys.add(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey")); |
||||
|
keys.add(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, "clientAttributeKey")); |
||||
|
keys.add(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, "sharedAttributeKey")); |
||||
|
keys.add(new EntityKey(EntityKeyType.ATTRIBUTE, "anyAttributeKey")); |
||||
|
latestCmd.setKeys(keys); |
||||
|
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); |
||||
|
|
||||
|
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); |
||||
|
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); |
||||
|
|
||||
|
wsClient.send(mapper.writeValueAsString(wrapper)); |
||||
|
String msg = wsClient.waitForReply(); |
||||
|
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
PageData<EntityData> pageData = update.getData(); |
||||
|
Assert.assertNotNull(pageData); |
||||
|
Assert.assertEquals(1, pageData.getData().size()); |
||||
|
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); |
||||
|
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey")); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs()); |
||||
|
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue()); |
||||
|
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey")); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getTs()); |
||||
|
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey").getValue()); |
||||
|
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey")); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getTs()); |
||||
|
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey").getValue()); |
||||
|
Assert.assertNotNull(pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey")); |
||||
|
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs()); |
||||
|
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue()); |
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); |
||||
|
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); |
||||
|
Thread.sleep(100); |
||||
|
|
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); |
||||
|
|
||||
|
msg = wsClient.waitForUpdate(); |
||||
|
Assert.assertNotNull(msg); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
List<EntityData> eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE)); |
||||
|
TsValue attrValue = eData.get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), attrValue); |
||||
|
|
||||
|
//Sending update from the past, while latest value has new timestamp;
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint1)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
|
||||
|
//Sending duplicate update again
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint1)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
Assert.assertNull(msg); |
||||
|
|
||||
|
//Sending update from the past, while latest value has new timestamp;
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("sharedAttributeKey", 42L)); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint2)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE)); |
||||
|
attrValue = eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), attrValue); |
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
AttributeKvEntry dataPoint3 = new BaseAttributeKvEntry(now, new LongDataEntry("clientAttributeKey", 42L)); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint3)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE)); |
||||
|
attrValue = eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint3.getLastUpdateTs(), dataPoint3.getValueAsString()), attrValue); |
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
AttributeKvEntry dataPoint4 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 42L)); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint4)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE)); |
||||
|
attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint4.getLastUpdateTs(), dataPoint4.getValueAsString()), attrValue); |
||||
|
|
||||
|
wsClient.registerWaitForUpdate(); |
||||
|
AttributeKvEntry dataPoint5 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 43L)); |
||||
|
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint5)); |
||||
|
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); |
||||
|
update = mapper.readValue(msg, EntityDataUpdate.class); |
||||
|
Assert.assertEquals(1, update.getCmdId()); |
||||
|
eData = update.getUpdate(); |
||||
|
Assert.assertNotNull(eData); |
||||
|
Assert.assertEquals(1, eData.size()); |
||||
|
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); |
||||
|
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE)); |
||||
|
attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"); |
||||
|
Assert.assertEquals(new TsValue(dataPoint5.getLastUpdateTs(), dataPoint5.getValueAsString()), attrValue); |
||||
|
} |
||||
|
|
||||
|
private void sendTelemetry(Device device, List<TsKvEntry> tsData) throws InterruptedException { |
||||
|
CountDownLatch latch = new CountDownLatch(1); |
||||
|
tsService.saveAndNotify(device.getTenantId(), device.getId(), tsData, 0, new FutureCallback<Void>() { |
||||
|
@Override |
||||
|
public void onSuccess(@Nullable Void result) { |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable t) { |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
}); |
||||
|
latch.await(3, TimeUnit.SECONDS); |
||||
|
} |
||||
|
|
||||
|
private void sendAttributes(Device device, TbAttributeSubscriptionScope scope, List<AttributeKvEntry> attrData) throws InterruptedException { |
||||
|
CountDownLatch latch = new CountDownLatch(1); |
||||
|
tsService.saveAndNotify(device.getTenantId(), device.getId(), scope.name(), attrData, new FutureCallback<Void>() { |
||||
|
@Override |
||||
|
public void onSuccess(@Nullable Void result) { |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable t) { |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
}); |
||||
|
latch.await(3, TimeUnit.SECONDS); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
/** |
||||
|
* 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 lombok.extern.slf4j.Slf4j; |
||||
|
import org.java_websocket.client.WebSocketClient; |
||||
|
import org.java_websocket.handshake.ServerHandshake; |
||||
|
|
||||
|
import java.net.URI; |
||||
|
import java.nio.channels.NotYetConnectedException; |
||||
|
import java.util.concurrent.CountDownLatch; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class TbTestWebSocketClient extends WebSocketClient { |
||||
|
|
||||
|
private volatile String lastMsg; |
||||
|
private CountDownLatch reply; |
||||
|
private CountDownLatch update; |
||||
|
|
||||
|
public TbTestWebSocketClient(URI serverUri) { |
||||
|
super(serverUri); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onOpen(ServerHandshake serverHandshake) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onMessage(String s) { |
||||
|
log.info("RECEIVED: {}", s); |
||||
|
lastMsg = s; |
||||
|
if (reply != null) { |
||||
|
reply.countDown(); |
||||
|
} |
||||
|
if (update != null) { |
||||
|
update.countDown(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onClose(int i, String s, boolean b) { |
||||
|
log.error("CLOSED:"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onError(Exception e) { |
||||
|
log.error("ERROR:", e); |
||||
|
} |
||||
|
|
||||
|
public void registerWaitForUpdate() { |
||||
|
lastMsg = null; |
||||
|
update = new CountDownLatch(1); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void send(String text) throws NotYetConnectedException { |
||||
|
reply = new CountDownLatch(1); |
||||
|
super.send(text); |
||||
|
} |
||||
|
|
||||
|
public String waitForUpdate() { |
||||
|
return waitForUpdate(TimeUnit.SECONDS.toMillis(3)); |
||||
|
} |
||||
|
|
||||
|
public String waitForUpdate(long ms) { |
||||
|
try { |
||||
|
update.await(ms, TimeUnit.MILLISECONDS); |
||||
|
} catch (InterruptedException e) { |
||||
|
log.warn("Failed to await reply", e); |
||||
|
} |
||||
|
return lastMsg; |
||||
|
} |
||||
|
|
||||
|
public String waitForReply() { |
||||
|
try { |
||||
|
reply.await(3, TimeUnit.SECONDS); |
||||
|
} catch (InterruptedException e) { |
||||
|
log.warn("Failed to await reply", e); |
||||
|
} |
||||
|
return lastMsg; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* 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.sql; |
||||
|
|
||||
|
import org.thingsboard.server.controller.BaseEntityQueryControllerTest; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class EntityQueryControllerSqlTest extends BaseEntityQueryControllerTest { |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* 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.sql; |
||||
|
|
||||
|
import org.thingsboard.server.controller.BaseWebsocketApiTest; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class WebsocketApiSqlTest extends BaseWebsocketApiTest { |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
/** |
||||
|
* 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.dao.alarm; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.alarm.Alarm; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class AlarmOperationResult { |
||||
|
private final Alarm alarm; |
||||
|
private final boolean successful; |
||||
|
private final List<EntityId> propagatedEntitiesList; |
||||
|
|
||||
|
public AlarmOperationResult(Alarm alarm, boolean successful) { |
||||
|
this.alarm = alarm; |
||||
|
this.successful = successful; |
||||
|
this.propagatedEntitiesList = Collections.emptyList(); |
||||
|
} |
||||
|
|
||||
|
public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) { |
||||
|
this.alarm = alarm; |
||||
|
this.successful = successful; |
||||
|
this.propagatedEntitiesList = propagatedEntitiesList; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
/** |
||||
|
* 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.common.data.query; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import lombok.Getter; |
||||
|
import lombok.ToString; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@ToString |
||||
|
public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends EntityCountQuery { |
||||
|
|
||||
|
@Getter |
||||
|
protected T pageLink; |
||||
|
@Getter |
||||
|
protected List<EntityKey> entityFields; |
||||
|
@Getter |
||||
|
protected List<EntityKey> latestValues; |
||||
|
@Getter |
||||
|
protected List<KeyFilter> keyFilters; |
||||
|
|
||||
|
public AbstractDataQuery() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public AbstractDataQuery(EntityFilter entityFilter) { |
||||
|
super(entityFilter); |
||||
|
} |
||||
|
|
||||
|
public AbstractDataQuery(EntityFilter entityFilter, |
||||
|
T pageLink, |
||||
|
List<EntityKey> entityFields, |
||||
|
List<EntityKey> latestValues, |
||||
|
List<KeyFilter> keyFilters) { |
||||
|
super(entityFilter); |
||||
|
this.pageLink = pageLink; |
||||
|
this.entityFields = entityFields; |
||||
|
this.latestValues = latestValues; |
||||
|
this.keyFilters = keyFilters; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
/** |
||||
|
* 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.common.data.query; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import org.thingsboard.server.common.data.alarm.Alarm; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmInfo; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public class AlarmData extends AlarmInfo { |
||||
|
|
||||
|
@Getter |
||||
|
private final EntityId entityId; |
||||
|
@Getter |
||||
|
private final Map<EntityKeyType, Map<String, TsValue>> latest; |
||||
|
|
||||
|
public AlarmData(Alarm alarm, String originatorName, EntityId entityId) { |
||||
|
super(alarm, originatorName); |
||||
|
this.entityId = entityId; |
||||
|
this.latest = new HashMap<>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
/** |
||||
|
* 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.common.data.query; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
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.alarm.AlarmStatus; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
public class AlarmDataPageLink extends EntityDataPageLink { |
||||
|
|
||||
|
private long startTs; |
||||
|
private long endTs; |
||||
|
//TODO: handle this;
|
||||
|
private long timeWindow; |
||||
|
private List<String> typeList; |
||||
|
private List<AlarmSearchStatus> statusList; |
||||
|
private List<AlarmSeverity> severityList; |
||||
|
private boolean searchPropagatedAlarms; |
||||
|
|
||||
|
public AlarmDataPageLink() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public AlarmDataPageLink(int pageSize, int page, String textSearch, EntityDataSortOrder sortOrder, boolean dynamic, |
||||
|
boolean searchPropagatedAlarms, |
||||
|
long startTs, long endTs, long timeWindow, |
||||
|
List<String> typeList, List<AlarmSearchStatus> statusList, List<AlarmSeverity> severityList) { |
||||
|
super(pageSize, page, textSearch, sortOrder, dynamic); |
||||
|
this.searchPropagatedAlarms = searchPropagatedAlarms; |
||||
|
this.startTs = startTs; |
||||
|
this.endTs = endTs; |
||||
|
this.timeWindow = timeWindow; |
||||
|
this.typeList = typeList; |
||||
|
this.statusList = statusList; |
||||
|
this.severityList = severityList; |
||||
|
} |
||||
|
|
||||
|
@JsonIgnore |
||||
|
public AlarmDataPageLink nextPageLink() { |
||||
|
return new AlarmDataPageLink(this.getPageSize(), this.getPage() + 1, this.getTextSearch(), this.getSortOrder(), this.isDynamic(), |
||||
|
this.searchPropagatedAlarms, |
||||
|
this.startTs, this.endTs, this.timeWindow, |
||||
|
this.typeList, this.statusList, this.severityList |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* 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.common.data.query; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import lombok.ToString; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@ToString |
||||
|
public class AlarmDataQuery extends AbstractDataQuery<AlarmDataPageLink> { |
||||
|
|
||||
|
public AlarmDataQuery() { |
||||
|
} |
||||
|
|
||||
|
public AlarmDataQuery(EntityFilter entityFilter) { |
||||
|
super(entityFilter); |
||||
|
} |
||||
|
|
||||
|
public AlarmDataQuery(EntityFilter entityFilter, AlarmDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters) { |
||||
|
super(entityFilter, pageLink, entityFields, latestValues, keyFilters); |
||||
|
} |
||||
|
|
||||
|
@JsonIgnore |
||||
|
public AlarmDataQuery next() { |
||||
|
return new AlarmDataQuery(getEntityFilter(), getPageLink().nextPageLink(), entityFields, latestValues, keyFilters); |
||||
|
} |
||||
|
} |
||||
@ -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.common.data.query; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class AssetSearchQueryFilter extends EntitySearchQueryFilter { |
||||
|
|
||||
|
@Override |
||||
|
public EntityFilterType getType() { |
||||
|
return EntityFilterType.ASSET_SEARCH_QUERY; |
||||
|
} |
||||
|
|
||||
|
private List<String> assetTypes; |
||||
|
|
||||
|
} |
||||
@ -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.common.data.query; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class AssetTypeFilter implements EntityFilter { |
||||
|
|
||||
|
@Override |
||||
|
public EntityFilterType getType() { |
||||
|
return EntityFilterType.ASSET_TYPE; |
||||
|
} |
||||
|
|
||||
|
private String assetType; |
||||
|
|
||||
|
private String assetNameFilter; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
/** |
||||
|
* 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.common.data.query; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class BooleanFilterPredicate implements KeyFilterPredicate { |
||||
|
|
||||
|
private BooleanOperation operation; |
||||
|
private FilterPredicateValue<Boolean> value; |
||||
|
|
||||
|
@Override |
||||
|
public FilterPredicateType getType() { |
||||
|
return FilterPredicateType.BOOLEAN; |
||||
|
} |
||||
|
|
||||
|
public enum BooleanOperation { |
||||
|
EQUAL, |
||||
|
NOT_EQUAL |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* 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.common.data.query; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class ComplexFilterPredicate implements KeyFilterPredicate { |
||||
|
|
||||
|
private ComplexOperation operation; |
||||
|
private List<KeyFilterPredicate> predicates; |
||||
|
|
||||
|
@Override |
||||
|
public FilterPredicateType getType() { |
||||
|
return FilterPredicateType.COMPLEX; |
||||
|
} |
||||
|
|
||||
|
public enum ComplexOperation { |
||||
|
AND, |
||||
|
OR |
||||
|
} |
||||
|
} |
||||
@ -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.common.data.query; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class DeviceSearchQueryFilter extends EntitySearchQueryFilter { |
||||
|
|
||||
|
@Override |
||||
|
public EntityFilterType getType() { |
||||
|
return EntityFilterType.DEVICE_SEARCH_QUERY; |
||||
|
} |
||||
|
|
||||
|
private List<String> deviceTypes; |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue