1171 changed files with 75834 additions and 35684 deletions
@ -0,0 +1,29 @@ |
|||
<component name="ProjectRunConfigurationManager"> |
|||
<configuration default="false" name="[CE Edge] Install" type="Application" factoryName="Application"> |
|||
<envs> |
|||
<env name="SPRING_DATASOURCE_URL" value="jdbc:postgresql://localhost:5432/tb_ce_edge_3" /> |
|||
</envs> |
|||
<option name="MAIN_CLASS_NAME" value="org.thingsboard.server.ThingsboardInstallApplication" /> |
|||
<module name="application" /> |
|||
<option name="VM_PARAMETERS" value="-Dinstall.data_dir=$PROJECT_DIR$/application/target/data -Dinstall.load_demo=true -Dspring.jpa.hibernate.ddl-auto=none -Dinstall.upgrade=false" /> |
|||
<extension name="coverage"> |
|||
<pattern> |
|||
<option name="PATTERN" value="org.thingsboard.server.*" /> |
|||
<option name="ENABLED" value="true" /> |
|||
</pattern> |
|||
</extension> |
|||
<extension name="net.ashald.envfile"> |
|||
<option name="IS_ENABLED" value="false" /> |
|||
<option name="IS_SUBST" value="false" /> |
|||
<option name="IS_PATH_MACRO_SUPPORTED" value="false" /> |
|||
<option name="IS_IGNORE_MISSING_FILES" value="false" /> |
|||
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" /> |
|||
<ENTRIES> |
|||
<ENTRY IS_ENABLED="true" PARSER="runconfig" /> |
|||
</ENTRIES> |
|||
</extension> |
|||
<method v="2"> |
|||
<option name="Make" enabled="true" /> |
|||
</method> |
|||
</configuration> |
|||
</component> |
|||
@ -0,0 +1,31 @@ |
|||
<component name="ProjectRunConfigurationManager"> |
|||
<configuration default="false" name="[CE Edge] Server" type="Application" factoryName="Application"> |
|||
<envs> |
|||
<env name="SPRING_DATASOURCE_URL" value="jdbc:postgresql://localhost:5432/tb_ce_edge_3" /> |
|||
<env name="EDGES_RPC_PORT" value="7070" /> |
|||
<env name="COAP_ENABLED" value="true" /> |
|||
<env name="EDGES_RPC_ENABLED" value="true" /> |
|||
</envs> |
|||
<option name="MAIN_CLASS_NAME" value="org.thingsboard.server.ThingsboardServerApplication" /> |
|||
<module name="application" /> |
|||
<extension name="coverage"> |
|||
<pattern> |
|||
<option name="PATTERN" value="org.thingsboard.server.*" /> |
|||
<option name="ENABLED" value="true" /> |
|||
</pattern> |
|||
</extension> |
|||
<extension name="net.ashald.envfile"> |
|||
<option name="IS_ENABLED" value="false" /> |
|||
<option name="IS_SUBST" value="false" /> |
|||
<option name="IS_PATH_MACRO_SUPPORTED" value="false" /> |
|||
<option name="IS_IGNORE_MISSING_FILES" value="false" /> |
|||
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" /> |
|||
<ENTRIES> |
|||
<ENTRY IS_ENABLED="true" PARSER="runconfig" /> |
|||
</ENTRIES> |
|||
</extension> |
|||
<method v="2"> |
|||
<option name="Make" enabled="true" /> |
|||
</method> |
|||
</configuration> |
|||
</component> |
|||
@ -0,0 +1,29 @@ |
|||
<component name="ProjectRunConfigurationManager"> |
|||
<configuration default="false" name="[CE Edge] Upgrade" type="Application" factoryName="Application"> |
|||
<envs> |
|||
<env name="SPRING_DATASOURCE_URL" value="jdbc:postgresql://localhost:5432/tb_ce_edge_3" /> |
|||
</envs> |
|||
<option name="MAIN_CLASS_NAME" value="org.thingsboard.server.ThingsboardInstallApplication" /> |
|||
<module name="application" /> |
|||
<option name="VM_PARAMETERS" value="-Dinstall.data_dir=$PROJECT_DIR$/application/target/data -Dinstall.load_demo=true -Dspring.jpa.hibernate.ddl-auto=none -Dinstall.upgrade=true -Dinstall.upgrade.from_version=2.4.3" /> |
|||
<extension name="coverage"> |
|||
<pattern> |
|||
<option name="PATTERN" value="org.thingsboard.server.*" /> |
|||
<option name="ENABLED" value="true" /> |
|||
</pattern> |
|||
</extension> |
|||
<extension name="net.ashald.envfile"> |
|||
<option name="IS_ENABLED" value="false" /> |
|||
<option name="IS_SUBST" value="false" /> |
|||
<option name="IS_PATH_MACRO_SUPPORTED" value="false" /> |
|||
<option name="IS_IGNORE_MISSING_FILES" value="false" /> |
|||
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" /> |
|||
<ENTRIES> |
|||
<ENTRY IS_ENABLED="true" PARSER="runconfig" /> |
|||
</ENTRIES> |
|||
</extension> |
|||
<method v="2"> |
|||
<option name="Make" enabled="true" /> |
|||
</method> |
|||
</configuration> |
|||
</component> |
|||
@ -1,12 +0,0 @@ |
|||
before_install: |
|||
- sudo rm -f /etc/mavenrc |
|||
- export M2_HOME=/usr/local/maven |
|||
- export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" |
|||
- export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false |
|||
jdk: |
|||
- openjdk8 |
|||
language: java |
|||
sudo: required |
|||
services: |
|||
- docker |
|||
script: mvn clean verify -Ddockerfile.skip=false |
|||
@ -0,0 +1,22 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ |
|||
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD |
|||
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX |
|||
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y |
|||
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy |
|||
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr |
|||
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr |
|||
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK |
|||
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu |
|||
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy |
|||
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye |
|||
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 |
|||
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 |
|||
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 |
|||
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx |
|||
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 |
|||
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz |
|||
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS |
|||
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp |
|||
-----END CERTIFICATE----- |
|||
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,135 @@ |
|||
{ |
|||
"ruleChain": { |
|||
"additionalInfo": { |
|||
"description": "" |
|||
}, |
|||
"name": "Device Profile Rule Chain Template", |
|||
"firstRuleNodeId": null, |
|||
"root": false, |
|||
"debugMode": false, |
|||
"configuration": null |
|||
}, |
|||
"metadata": { |
|||
"firstNodeIndex": 6, |
|||
"nodes": [ |
|||
{ |
|||
"additionalInfo": { |
|||
"layoutX": 822, |
|||
"layoutY": 294 |
|||
}, |
|||
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", |
|||
"name": "Save Timeseries", |
|||
"debugMode": false, |
|||
"configuration": { |
|||
"defaultTTL": 0 |
|||
} |
|||
}, |
|||
{ |
|||
"additionalInfo": { |
|||
"layoutX": 824, |
|||
"layoutY": 221 |
|||
}, |
|||
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", |
|||
"name": "Save Client Attributes", |
|||
"debugMode": false, |
|||
"configuration": { |
|||
"scope": "CLIENT_SCOPE" |
|||
} |
|||
}, |
|||
{ |
|||
"additionalInfo": { |
|||
"layoutX": 494, |
|||
"layoutY": 309 |
|||
}, |
|||
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", |
|||
"name": "Message Type Switch", |
|||
"debugMode": false, |
|||
"configuration": { |
|||
"version": 0 |
|||
} |
|||
}, |
|||
{ |
|||
"additionalInfo": { |
|||
"layoutX": 824, |
|||
"layoutY": 383 |
|||
}, |
|||
"type": "org.thingsboard.rule.engine.action.TbLogNode", |
|||
"name": "Log RPC from Device", |
|||
"debugMode": false, |
|||
"configuration": { |
|||
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" |
|||
} |
|||
}, |
|||
{ |
|||
"additionalInfo": { |
|||
"layoutX": 823, |
|||
"layoutY": 444 |
|||
}, |
|||
"type": "org.thingsboard.rule.engine.action.TbLogNode", |
|||
"name": "Log Other", |
|||
"debugMode": false, |
|||
"configuration": { |
|||
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" |
|||
} |
|||
}, |
|||
{ |
|||
"additionalInfo": { |
|||
"layoutX": 822, |
|||
"layoutY": 507 |
|||
}, |
|||
"type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", |
|||
"name": "RPC Call Request", |
|||
"debugMode": false, |
|||
"configuration": { |
|||
"timeoutInSeconds": 60 |
|||
} |
|||
}, |
|||
{ |
|||
"additionalInfo": { |
|||
"description": "", |
|||
"layoutX": 209, |
|||
"layoutY": 307 |
|||
}, |
|||
"type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", |
|||
"name": "Device Profile Node", |
|||
"debugMode": false, |
|||
"configuration": { |
|||
"persistAlarmRulesState": false |
|||
} |
|||
} |
|||
], |
|||
"connections": [ |
|||
{ |
|||
"fromIndex": 2, |
|||
"toIndex": 4, |
|||
"type": "Other" |
|||
}, |
|||
{ |
|||
"fromIndex": 2, |
|||
"toIndex": 1, |
|||
"type": "Post attributes" |
|||
}, |
|||
{ |
|||
"fromIndex": 2, |
|||
"toIndex": 0, |
|||
"type": "Post telemetry" |
|||
}, |
|||
{ |
|||
"fromIndex": 2, |
|||
"toIndex": 3, |
|||
"type": "RPC Request from Device" |
|||
}, |
|||
{ |
|||
"fromIndex": 2, |
|||
"toIndex": 5, |
|||
"type": "RPC Request to Device" |
|||
}, |
|||
{ |
|||
"fromIndex": 6, |
|||
"toIndex": 2, |
|||
"type": "Success" |
|||
} |
|||
], |
|||
"ruleChainConnections": null |
|||
} |
|||
} |
|||
@ -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. |
|||
-- |
|||
|
|||
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,878 @@ |
|||
-- |
|||
-- Copyright © 2016-2020 The Thingsboard Authors |
|||
-- |
|||
-- Licensed under the Apache License, Version 2.0 (the "License"); |
|||
-- you may not use this file except in compliance with the License. |
|||
-- You may obtain a copy of the License at |
|||
-- |
|||
-- http://www.apache.org/licenses/LICENSE-2.0 |
|||
-- |
|||
-- Unless required by applicable law or agreed to in writing, software |
|||
-- distributed under the License is distributed on an "AS IS" BASIS, |
|||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
-- See the License for the specific language governing permissions and |
|||
-- limitations under the License. |
|||
-- |
|||
|
|||
CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS |
|||
$$ |
|||
BEGIN |
|||
uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) || |
|||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12); |
|||
END; |
|||
$$ LANGUAGE plpgsql; |
|||
|
|||
CREATE OR REPLACE FUNCTION extract_ts(uuid UUID) RETURNS BIGINT AS |
|||
$$ |
|||
DECLARE |
|||
bytes bytea; |
|||
BEGIN |
|||
bytes := uuid_send(uuid); |
|||
RETURN |
|||
( |
|||
( |
|||
(get_byte(bytes, 0)::bigint << 24) | |
|||
(get_byte(bytes, 1)::bigint << 16) | |
|||
(get_byte(bytes, 2)::bigint << 8) | |
|||
(get_byte(bytes, 3)::bigint << 0) |
|||
) + ( |
|||
((get_byte(bytes, 4)::bigint << 8 | |
|||
get_byte(bytes, 5)::bigint)) << 32 |
|||
) + ( |
|||
(((get_byte(bytes, 6)::bigint & 15) << 8 | get_byte(bytes, 7)::bigint) & 4095) << 48 |
|||
) - 122192928000000000 |
|||
) / 10000::double precision; |
|||
END |
|||
$$ LANGUAGE plpgsql |
|||
IMMUTABLE |
|||
PARALLEL SAFE |
|||
RETURNS NULL ON NULL INPUT; |
|||
|
|||
|
|||
CREATE OR REPLACE FUNCTION column_type_to_uuid(table_name varchar, column_name varchar) RETURNS VOID |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
execute format('ALTER TABLE %s RENAME COLUMN %s TO old_%s;', table_name, column_name, column_name); |
|||
execute format('ALTER TABLE %s ADD COLUMN %s UUID;', table_name, column_name); |
|||
execute format('UPDATE %s SET %s = to_uuid(old_%s) WHERE old_%s is not null;', table_name, column_name, column_name, column_name); |
|||
execute format('ALTER TABLE %s DROP COLUMN old_%s;', table_name, column_name); |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION get_column_type(table_name varchar, column_name varchar, OUT data_type varchar) RETURNS varchar |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
execute (format('SELECT data_type from information_schema.columns where table_name = %L and column_name = %L', |
|||
table_name, column_name)) INTO data_type; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
CREATE OR REPLACE PROCEDURE drop_all_idx() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
DROP INDEX IF EXISTS idx_alarm_originator_alarm_type; |
|||
DROP INDEX IF EXISTS idx_alarm_originator_created_time; |
|||
DROP INDEX IF EXISTS idx_alarm_tenant_created_time; |
|||
DROP INDEX IF EXISTS idx_event_type_entity_id; |
|||
DROP INDEX IF EXISTS idx_relation_to_id; |
|||
DROP INDEX IF EXISTS idx_relation_from_id; |
|||
DROP INDEX IF EXISTS idx_device_customer_id; |
|||
DROP INDEX IF EXISTS idx_device_customer_id_and_type; |
|||
DROP INDEX IF EXISTS idx_device_type; |
|||
DROP INDEX IF EXISTS idx_asset_customer_id; |
|||
DROP INDEX IF EXISTS idx_asset_customer_id_and_type; |
|||
DROP INDEX IF EXISTS idx_asset_type; |
|||
DROP INDEX IF EXISTS idx_attribute_kv_by_key_and_last_update_ts; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE PROCEDURE create_all_idx() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC); |
|||
CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); |
|||
CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); |
|||
CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); |
|||
CREATE INDEX IF NOT EXISTS idx_device_customer_id ON device(tenant_id, customer_id); |
|||
CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id, customer_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id); |
|||
CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_asset_type ON asset(tenant_id, type); |
|||
CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribute_kv(entity_id, attribute_key, last_update_ts desc); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- admin_settings |
|||
CREATE OR REPLACE PROCEDURE update_admin_settings() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'admin_settings'; |
|||
column_id varchar := 'id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE admin_settings DROP CONSTRAINT admin_settings_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE admin_settings ADD CONSTRAINT admin_settings_pkey PRIMARY KEY (id); |
|||
ALTER TABLE admin_settings ADD COLUMN created_time BIGINT; |
|||
UPDATE admin_settings SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- alarm |
|||
CREATE OR REPLACE PROCEDURE update_alarm() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'alarm'; |
|||
column_id varchar := 'id'; |
|||
column_originator_id varchar := 'originator_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE alarm DROP CONSTRAINT alarm_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE alarm ADD COLUMN created_time BIGINT; |
|||
UPDATE alarm SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE alarm ADD CONSTRAINT alarm_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_originator_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_originator_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_originator_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_originator_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- asset |
|||
CREATE OR REPLACE PROCEDURE update_asset() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'asset'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE asset DROP CONSTRAINT asset_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE asset ADD COLUMN created_time BIGINT; |
|||
UPDATE asset SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE asset ADD CONSTRAINT asset_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE asset DROP CONSTRAINT asset_name_unq_key; |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
ALTER TABLE asset ADD CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- attribute_kv |
|||
CREATE OR REPLACE PROCEDURE update_attribute_kv() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'attribute_kv'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE attribute_kv DROP CONSTRAINT attribute_kv_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
ALTER TABLE attribute_kv ADD CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- audit_log |
|||
CREATE OR REPLACE PROCEDURE update_audit_log() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'audit_log'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
column_user_id varchar := 'user_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE audit_log DROP CONSTRAINT audit_log_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE audit_log ADD COLUMN created_time BIGINT; |
|||
UPDATE audit_log SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE audit_log ADD CONSTRAINT audit_log_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_user_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_user_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_user_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- component_descriptor |
|||
CREATE OR REPLACE PROCEDURE update_component_descriptor() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'component_descriptor'; |
|||
column_id varchar := 'id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE component_descriptor DROP CONSTRAINT component_descriptor_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE component_descriptor ADD CONSTRAINT component_descriptor_pkey PRIMARY KEY (id); |
|||
ALTER TABLE component_descriptor ADD COLUMN created_time BIGINT; |
|||
UPDATE component_descriptor SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- customer |
|||
CREATE OR REPLACE PROCEDURE update_customer() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'customer'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE customer DROP CONSTRAINT customer_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE customer ADD CONSTRAINT customer_pkey PRIMARY KEY (id); |
|||
ALTER TABLE customer ADD COLUMN created_time BIGINT; |
|||
UPDATE customer SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- dashboard |
|||
CREATE OR REPLACE PROCEDURE update_dashboard() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'dashboard'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE dashboard DROP CONSTRAINT dashboard_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE dashboard ADD CONSTRAINT dashboard_pkey PRIMARY KEY (id); |
|||
ALTER TABLE dashboard ADD COLUMN created_time BIGINT; |
|||
UPDATE dashboard SET created_time = extract_ts(id) WHERE id is not null; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
-- device |
|||
CREATE OR REPLACE PROCEDURE update_device() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'device'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device DROP CONSTRAINT device_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE device ADD COLUMN created_time BIGINT; |
|||
UPDATE device SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE device ADD CONSTRAINT device_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device DROP CONSTRAINT device_name_unq_key; |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
ALTER TABLE device ADD CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- device_credentials |
|||
CREATE OR REPLACE PROCEDURE update_device_credentials() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'device_credentials'; |
|||
column_id varchar := 'id'; |
|||
column_device_id varchar := 'device_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device_credentials DROP CONSTRAINT device_credentials_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE device_credentials ADD COLUMN created_time BIGINT; |
|||
UPDATE device_credentials SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_device_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE device_credentials DROP CONSTRAINT IF EXISTS device_credentials_device_id_unq_key; |
|||
PERFORM column_type_to_uuid(table_name, column_device_id); |
|||
-- remove duplicate credentials with same device_id |
|||
DELETE from device_credentials where id in ( |
|||
select dc.id |
|||
from ( |
|||
SELECT id, device_id, |
|||
ROW_NUMBER() OVER ( |
|||
PARTITION BY |
|||
device_id |
|||
ORDER BY |
|||
created_time DESC |
|||
) row_num |
|||
FROM |
|||
device_credentials |
|||
) as dc |
|||
WHERE dc.row_num > 1 |
|||
); |
|||
ALTER TABLE device_credentials ADD CONSTRAINT device_credentials_device_id_unq_key UNIQUE (device_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_device_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_device_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- event |
|||
CREATE OR REPLACE PROCEDURE update_event() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'event'; |
|||
column_id varchar := 'id'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE event DROP CONSTRAINT event_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE event ADD COLUMN created_time BIGINT; |
|||
UPDATE event SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE event ADD CONSTRAINT event_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
ALTER TABLE event DROP CONSTRAINT event_unq_key; |
|||
|
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
|
|||
ALTER TABLE event ADD CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- relation |
|||
CREATE OR REPLACE PROCEDURE update_relation() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'relation'; |
|||
column_from_id varchar := 'from_id'; |
|||
column_to_id varchar := 'to_id'; |
|||
BEGIN |
|||
ALTER TABLE relation DROP CONSTRAINT relation_pkey; |
|||
|
|||
data_type := get_column_type(table_name, column_from_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_from_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_from_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_from_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_to_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_to_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_to_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_to_id; |
|||
END IF; |
|||
|
|||
ALTER TABLE relation ADD CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- tb_user |
|||
CREATE OR REPLACE PROCEDURE update_tb_user() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'tb_user'; |
|||
column_id varchar := 'id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE tb_user DROP CONSTRAINT tb_user_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE tb_user ADD COLUMN created_time BIGINT; |
|||
UPDATE tb_user SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE tb_user ADD CONSTRAINT tb_user_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- tenant |
|||
CREATE OR REPLACE PROCEDURE update_tenant() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'tenant'; |
|||
column_id varchar := 'id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE tenant DROP CONSTRAINT tenant_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE tenant ADD COLUMN created_time BIGINT; |
|||
UPDATE tenant SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE tenant ADD CONSTRAINT tenant_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- user_credentials |
|||
CREATE OR REPLACE PROCEDURE update_user_credentials() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'user_credentials'; |
|||
column_id varchar := 'id'; |
|||
column_user_id varchar := 'user_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE user_credentials ADD COLUMN created_time BIGINT; |
|||
UPDATE user_credentials SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE user_credentials ADD CONSTRAINT user_credentials_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_user_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE user_credentials DROP CONSTRAINT user_credentials_user_id_key; |
|||
ALTER TABLE user_credentials RENAME COLUMN user_id TO old_user_id; |
|||
ALTER TABLE user_credentials ADD COLUMN user_id UUID UNIQUE; |
|||
UPDATE user_credentials SET user_id = to_uuid(old_user_id) WHERE old_user_id is not null; |
|||
ALTER TABLE user_credentials DROP COLUMN old_user_id; |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_user_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_user_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- widget_type |
|||
CREATE OR REPLACE PROCEDURE update_widget_type() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'widget_type'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE widget_type DROP CONSTRAINT widget_type_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE widget_type ADD COLUMN created_time BIGINT; |
|||
UPDATE widget_type SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE widget_type ADD CONSTRAINT widget_type_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- widgets_bundle |
|||
CREATE OR REPLACE PROCEDURE update_widgets_bundle() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'widgets_bundle'; |
|||
column_id varchar := 'id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE widgets_bundle DROP CONSTRAINT widgets_bundle_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE widgets_bundle ADD COLUMN created_time BIGINT; |
|||
UPDATE widgets_bundle SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE widgets_bundle ADD CONSTRAINT widgets_bundle_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- rule_chain |
|||
CREATE OR REPLACE PROCEDURE update_rule_chain() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'rule_chain'; |
|||
column_id varchar := 'id'; |
|||
column_first_rule_node_id varchar := 'first_rule_node_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE rule_chain DROP CONSTRAINT rule_chain_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE rule_chain ADD COLUMN created_time BIGINT; |
|||
UPDATE rule_chain SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE rule_chain ADD CONSTRAINT rule_chain_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_first_rule_node_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_first_rule_node_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_first_rule_node_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_first_rule_node_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- rule_node |
|||
CREATE OR REPLACE PROCEDURE update_rule_node() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'rule_node'; |
|||
column_id varchar := 'id'; |
|||
column_rule_chain_id varchar := 'rule_chain_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE rule_node DROP CONSTRAINT rule_node_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE rule_node ADD COLUMN created_time BIGINT; |
|||
UPDATE rule_node SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE rule_node ADD CONSTRAINT rule_node_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_rule_chain_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_rule_chain_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_rule_chain_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_rule_chain_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- entity_view |
|||
CREATE OR REPLACE PROCEDURE update_entity_view() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
DECLARE |
|||
data_type varchar; |
|||
table_name varchar := 'entity_view'; |
|||
column_id varchar := 'id'; |
|||
column_entity_id varchar := 'entity_id'; |
|||
column_tenant_id varchar := 'tenant_id'; |
|||
column_customer_id varchar := 'customer_id'; |
|||
BEGIN |
|||
data_type := get_column_type(table_name, column_id); |
|||
IF data_type = 'character varying' THEN |
|||
ALTER TABLE entity_view DROP CONSTRAINT entity_view_pkey; |
|||
PERFORM column_type_to_uuid(table_name, column_id); |
|||
ALTER TABLE entity_view ADD COLUMN created_time BIGINT; |
|||
UPDATE entity_view SET created_time = extract_ts(id) WHERE id is not null; |
|||
ALTER TABLE entity_view ADD CONSTRAINT entity_view_pkey PRIMARY KEY (id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_entity_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_entity_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_entity_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_entity_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_tenant_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_tenant_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_tenant_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_tenant_id; |
|||
END IF; |
|||
|
|||
data_type := get_column_type(table_name, column_customer_id); |
|||
IF data_type = 'character varying' THEN |
|||
PERFORM column_type_to_uuid(table_name, column_customer_id); |
|||
RAISE NOTICE 'Table % column % updated!', table_name, column_customer_id; |
|||
ELSE |
|||
RAISE NOTICE 'Table % column % already updated!', table_name, column_customer_id; |
|||
END IF; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE TABLE IF NOT EXISTS ts_kv_latest |
|||
( |
|||
entity_id uuid NOT NULL, |
|||
key int NOT NULL, |
|||
ts bigint NOT NULL, |
|||
bool_v boolean, |
|||
str_v varchar(10000000), |
|||
long_v bigint, |
|||
dbl_v double precision, |
|||
json_v json, |
|||
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS ts_kv_dictionary |
|||
( |
|||
key varchar(255) NOT NULL, |
|||
key_id serial UNIQUE, |
|||
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) |
|||
); |
|||
@ -0,0 +1,17 @@ |
|||
-- |
|||
-- 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 INDEX IF NOT EXISTS idx_alarm_tenant_alarm_type_created_time ON alarm(tenant_id, type, created_time DESC); |
|||
@ -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. |
|||
-- |
|||
|
|||
DROP PROCEDURE IF EXISTS update_tenant_profiles; |
|||
DROP PROCEDURE IF EXISTS update_device_profiles; |
|||
|
|||
ALTER TABLE tenant ALTER COLUMN tenant_profile_id SET NOT NULL; |
|||
ALTER TABLE tenant DROP CONSTRAINT IF EXISTS fk_tenant_profile; |
|||
ALTER TABLE tenant ADD CONSTRAINT fk_tenant_profile FOREIGN KEY (tenant_profile_id) REFERENCES tenant_profile(id); |
|||
ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_core; |
|||
ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_rule_engine; |
|||
|
|||
ALTER TABLE device ALTER COLUMN device_profile_id SET NOT NULL; |
|||
ALTER TABLE device DROP CONSTRAINT IF EXISTS fk_device_profile; |
|||
ALTER TABLE device ADD CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id); |
|||
@ -0,0 +1,81 @@ |
|||
-- |
|||
-- 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 TABLE IF NOT EXISTS device_profile ( |
|||
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY, |
|||
created_time bigint NOT NULL, |
|||
name varchar(255), |
|||
type varchar(255), |
|||
transport_type varchar(255), |
|||
profile_data jsonb, |
|||
description varchar, |
|||
search_text varchar(255), |
|||
is_default boolean, |
|||
tenant_id uuid, |
|||
default_rule_chain_id uuid, |
|||
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), |
|||
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id) |
|||
); |
|||
|
|||
CREATE TABLE IF NOT EXISTS tenant_profile ( |
|||
id uuid NOT NULL CONSTRAINT tenant_profile_pkey PRIMARY KEY, |
|||
created_time bigint NOT NULL, |
|||
name varchar(255), |
|||
profile_data jsonb, |
|||
description varchar, |
|||
search_text varchar(255), |
|||
is_default boolean, |
|||
isolated_tb_core boolean, |
|||
isolated_tb_rule_engine boolean, |
|||
CONSTRAINT tenant_profile_name_unq_key UNIQUE (name) |
|||
); |
|||
|
|||
CREATE OR REPLACE PROCEDURE update_tenant_profiles() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
UPDATE tenant as t SET tenant_profile_id = p.id |
|||
FROM |
|||
(SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = false) as p |
|||
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = false; |
|||
|
|||
UPDATE tenant as t SET tenant_profile_id = p.id |
|||
FROM |
|||
(SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = false) as p |
|||
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = false; |
|||
|
|||
UPDATE tenant as t SET tenant_profile_id = p.id |
|||
FROM |
|||
(SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = true) as p |
|||
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = true; |
|||
|
|||
UPDATE tenant as t SET tenant_profile_id = p.id |
|||
FROM |
|||
(SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = true) as p |
|||
WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = true; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE PROCEDURE update_device_profiles() |
|||
LANGUAGE plpgsql AS |
|||
$$ |
|||
BEGIN |
|||
UPDATE device as d SET device_profile_id = p.id, device_data = '{"configuration":{"type":"DEFAULT"}, "transportConfiguration":{"type":"DEFAULT"}}' |
|||
FROM |
|||
(SELECT id, tenant_id, name from device_profile) as p |
|||
WHERE d.device_profile_id IS NULL AND p.tenant_id = d.tenant_id AND d.type = p.name; |
|||
END; |
|||
$$; |
|||
@ -0,0 +1,203 @@ |
|||
/** |
|||
* 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.springframework.http.HttpStatus; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.ResponseStatus; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.DeviceProfileInfo; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.audit.ActionType; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.security.permission.Resource; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
@Slf4j |
|||
public class DeviceProfileController extends BaseController { |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public DeviceProfile getDeviceProfileById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException { |
|||
checkParameter("deviceProfileId", strDeviceProfileId); |
|||
try { |
|||
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
|||
return checkDeviceProfileId(deviceProfileId, Operation.READ); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException { |
|||
checkParameter("deviceProfileId", strDeviceProfileId); |
|||
try { |
|||
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
|||
return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public DeviceProfileInfo getDefaultDeviceProfileInfo() throws ThingsboardException { |
|||
try { |
|||
return checkNotNull(deviceProfileService.findDefaultDeviceProfileInfo(getTenantId())); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/deviceProfile", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public DeviceProfile saveDeviceProfile(@RequestBody DeviceProfile deviceProfile) throws ThingsboardException { |
|||
try { |
|||
boolean created = deviceProfile.getId() == null; |
|||
deviceProfile.setTenantId(getTenantId()); |
|||
|
|||
checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); |
|||
|
|||
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); |
|||
|
|||
deviceProfileCache.put(savedDeviceProfile); |
|||
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); |
|||
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(), |
|||
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
|||
|
|||
logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile, |
|||
null, |
|||
savedDeviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); |
|||
|
|||
return savedDeviceProfile; |
|||
} catch (Exception e) { |
|||
logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile, |
|||
null, deviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE) |
|||
@ResponseStatus(value = HttpStatus.OK) |
|||
public void deleteDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException { |
|||
checkParameter("deviceProfileId", strDeviceProfileId); |
|||
try { |
|||
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
|||
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE); |
|||
deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId); |
|||
deviceProfileCache.evict(deviceProfileId); |
|||
|
|||
tbClusterService.onDeviceProfileDelete(deviceProfile, null); |
|||
tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED); |
|||
|
|||
logEntityAction(deviceProfileId, deviceProfile, |
|||
null, |
|||
ActionType.DELETED, null, strDeviceProfileId); |
|||
|
|||
} catch (Exception e) { |
|||
logEntityAction(emptyId(EntityType.DEVICE_PROFILE), |
|||
null, |
|||
null, |
|||
ActionType.DELETED, e, strDeviceProfileId); |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public DeviceProfile setDefaultDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException { |
|||
checkParameter("deviceProfileId", strDeviceProfileId); |
|||
try { |
|||
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); |
|||
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.WRITE); |
|||
DeviceProfile previousDefaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(getTenantId()); |
|||
if (deviceProfileService.setDefaultDeviceProfile(getTenantId(), deviceProfileId)) { |
|||
if (previousDefaultDeviceProfile != null) { |
|||
previousDefaultDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), previousDefaultDeviceProfile.getId()); |
|||
|
|||
logEntityAction(previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile, |
|||
null, ActionType.UPDATED, null); |
|||
} |
|||
deviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfileId); |
|||
|
|||
logEntityAction(deviceProfile.getId(), deviceProfile, |
|||
null, ActionType.UPDATED, null); |
|||
} |
|||
return deviceProfile; |
|||
} catch (Exception e) { |
|||
logEntityAction(emptyId(EntityType.DEVICE_PROFILE), |
|||
null, |
|||
null, |
|||
ActionType.UPDATED, e, strDeviceProfileId); |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public PageData<DeviceProfile> getDeviceProfiles(@RequestParam int pageSize, |
|||
@RequestParam int page, |
|||
@RequestParam(required = false) String textSearch, |
|||
@RequestParam(required = false) String sortProperty, |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
try { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public PageData<DeviceProfileInfo> getDeviceProfileInfos(@RequestParam int pageSize, |
|||
@RequestParam int page, |
|||
@RequestParam(required = false) String textSearch, |
|||
@RequestParam(required = false) String sortProperty, |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
try { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.controller; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityCountQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.query.EntityQueryService; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
public class EntityQueryController extends BaseController { |
|||
|
|||
@Autowired |
|||
private EntityQueryService entityQueryService; |
|||
|
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/entitiesQuery/count", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public long countEntitiesByQuery(@RequestBody EntityCountQuery query) throws ThingsboardException { |
|||
checkNotNull(query); |
|||
try { |
|||
return this.entityQueryService.countEntitiesByQuery(getCurrentUser(), query); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/entitiesQuery/find", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public PageData<EntityData> findEntityDataByQuery(@RequestBody EntityDataQuery query) throws ThingsboardException { |
|||
checkNotNull(query); |
|||
try { |
|||
return this.entityQueryService.findEntityDataByQuery(getCurrentUser(), query); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@RequestMapping(value = "/alarmsQuery/find", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public PageData<AlarmData> findAlarmDataByQuery(@RequestBody AlarmDataQuery query) throws ThingsboardException { |
|||
checkNotNull(query); |
|||
try { |
|||
return this.entityQueryService.findAlarmDataByQuery(getCurrentUser(), query); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
/** |
|||
* 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.springframework.http.HttpStatus; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.ResponseStatus; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.EntityInfo; |
|||
import org.thingsboard.server.common.data.TenantProfile; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.TenantProfileId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.security.permission.Resource; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
@Slf4j |
|||
public class TenantProfileController extends BaseController { |
|||
|
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public TenantProfile getTenantProfileById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { |
|||
checkParameter("tenantProfileId", strTenantProfileId); |
|||
try { |
|||
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); |
|||
return checkTenantProfileId(tenantProfileId, Operation.READ); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfileInfo/{tenantProfileId}", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public EntityInfo getTenantProfileInfoById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { |
|||
checkParameter("tenantProfileId", strTenantProfileId); |
|||
try { |
|||
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); |
|||
return checkNotNull(tenantProfileService.findTenantProfileInfoById(getTenantId(), tenantProfileId)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfileInfo/default", method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public EntityInfo getDefaultTenantProfileInfo() throws ThingsboardException { |
|||
try { |
|||
return checkNotNull(tenantProfileService.findDefaultTenantProfileInfo(getTenantId())); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfile", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public TenantProfile saveTenantProfile(@RequestBody TenantProfile tenantProfile) throws ThingsboardException { |
|||
try { |
|||
boolean newTenantProfile = tenantProfile.getId() == null; |
|||
if (newTenantProfile) { |
|||
accessControlService |
|||
.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE); |
|||
} else { |
|||
checkEntityId(tenantProfile.getId(), Operation.WRITE); |
|||
} |
|||
|
|||
tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); |
|||
return tenantProfile; |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.DELETE) |
|||
@ResponseStatus(value = HttpStatus.OK) |
|||
public void deleteTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { |
|||
checkParameter("tenantProfileId", strTenantProfileId); |
|||
try { |
|||
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); |
|||
checkTenantProfileId(tenantProfileId, Operation.DELETE); |
|||
tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfile/{tenantProfileId}/default", method = RequestMethod.POST) |
|||
@ResponseBody |
|||
public TenantProfile setDefaultTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { |
|||
checkParameter("tenantProfileId", strTenantProfileId); |
|||
try { |
|||
TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); |
|||
TenantProfile tenantProfile = checkTenantProfileId(tenantProfileId, Operation.WRITE); |
|||
tenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfileId); |
|||
return tenantProfile; |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public PageData<TenantProfile> getTenantProfiles(@RequestParam int pageSize, |
|||
@RequestParam int page, |
|||
@RequestParam(required = false) String textSearch, |
|||
@RequestParam(required = false) String sortProperty, |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
try { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
return checkNotNull(tenantProfileService.findTenantProfiles(getTenantId(), pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('SYS_ADMIN')") |
|||
@RequestMapping(value = "/tenantProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public PageData<EntityInfo> getTenantProfileInfos(@RequestParam int pageSize, |
|||
@RequestParam int page, |
|||
@RequestParam(required = false) String textSearch, |
|||
@RequestParam(required = false) String sortProperty, |
|||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|||
try { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
return checkNotNull(tenantProfileService.findTenantProfileInfos(getTenantId(), pageLink)); |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.install; |
|||
|
|||
import org.springframework.context.annotation.Profile; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.dao.util.NoSqlTsLatestDao; |
|||
|
|||
@Service |
|||
@NoSqlTsLatestDao |
|||
@Profile("install") |
|||
public class CassandraTsLatestDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService |
|||
implements TsLatestDatabaseSchemaService { |
|||
public CassandraTsLatestDatabaseSchemaService() { |
|||
super("schema-ts-latest.cql"); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.install; |
|||
|
|||
public interface TsLatestDatabaseSchemaService extends DatabaseSchemaService { |
|||
} |
|||
@ -0,0 +1,233 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.install.migrate; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.hibernate.exception.ConstraintViolationException; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.annotation.Profile; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.UUIDConverter; |
|||
import org.thingsboard.server.dao.cassandra.CassandraCluster; |
|||
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; |
|||
import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; |
|||
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; |
|||
import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; |
|||
import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository; |
|||
import org.thingsboard.server.dao.util.NoSqlTsDao; |
|||
import org.thingsboard.server.dao.util.SqlTsLatestDao; |
|||
import org.thingsboard.server.service.install.InstallScripts; |
|||
|
|||
import java.nio.charset.Charset; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
import java.nio.file.Paths; |
|||
import java.sql.Connection; |
|||
import java.sql.DriverManager; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.locks.ReentrantLock; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.bigintColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.booleanColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.doubleColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.idColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.jsonColumn; |
|||
import static org.thingsboard.server.service.install.migrate.CassandraToSqlColumn.stringColumn; |
|||
|
|||
@Service |
|||
@Profile("install") |
|||
@NoSqlTsDao |
|||
@SqlTsLatestDao |
|||
@Slf4j |
|||
public class CassandraTsLatestToSqlMigrateService implements TsLatestMigrateService { |
|||
|
|||
private static final int MAX_KEY_LENGTH = 255; |
|||
private static final int MAX_STR_V_LENGTH = 10000000; |
|||
|
|||
@Autowired |
|||
private InsertLatestTsRepository insertLatestTsRepository; |
|||
|
|||
@Autowired |
|||
protected CassandraCluster cluster; |
|||
|
|||
@Autowired |
|||
protected TsKvDictionaryRepository dictionaryRepository; |
|||
|
|||
@Autowired |
|||
private InstallScripts installScripts; |
|||
|
|||
@Value("${spring.datasource.url}") |
|||
protected String dbUrl; |
|||
|
|||
@Value("${spring.datasource.username}") |
|||
protected String dbUserName; |
|||
|
|||
@Value("${spring.datasource.password}") |
|||
protected String dbPassword; |
|||
|
|||
private final ConcurrentMap<String, Integer> tsKvDictionaryMap = new ConcurrentHashMap<>(); |
|||
|
|||
protected static final ReentrantLock tsCreationLock = new ReentrantLock(); |
|||
|
|||
@Override |
|||
public void migrate() throws Exception { |
|||
log.info("Performing migration of latest timeseries data from cassandra to SQL database ..."); |
|||
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { |
|||
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.0.1", "schema_ts_latest.sql"); |
|||
loadSql(schemaUpdateFile, conn); |
|||
conn.setAutoCommit(false); |
|||
for (CassandraToSqlTable table : tables) { |
|||
table.migrateToSql(cluster.getSession(), conn); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("Unexpected error during ThingsBoard entities data migration!", e); |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
private List<CassandraToSqlTable> tables = Arrays.asList( |
|||
new CassandraToSqlTable("ts_kv_latest_cf", "ts_kv_latest", |
|||
idColumn("entity_id"), |
|||
stringColumn("key"), |
|||
bigintColumn("ts"), |
|||
booleanColumn("bool_v"), |
|||
stringColumn("str_v"), |
|||
bigintColumn("long_v"), |
|||
doubleColumn("dbl_v"), |
|||
jsonColumn("json_v")) { |
|||
|
|||
@Override |
|||
protected void batchInsert(List<CassandraToSqlColumnData[]> batchData, Connection conn) { |
|||
insertLatestTsRepository |
|||
.saveOrUpdate(batchData.stream().map(data -> getTsKvLatestEntity(data)).collect(Collectors.toList())); |
|||
} |
|||
|
|||
@Override |
|||
protected CassandraToSqlColumnData[] validateColumnData(CassandraToSqlColumnData[] data) { |
|||
return data; |
|||
} |
|||
}); |
|||
|
|||
private TsKvLatestEntity getTsKvLatestEntity(CassandraToSqlColumnData[] data) { |
|||
TsKvLatestEntity latestEntity = new TsKvLatestEntity(); |
|||
latestEntity.setEntityId(UUIDConverter.fromString(data[0].getValue())); |
|||
latestEntity.setKey(getOrSaveKeyId(data[1].getValue())); |
|||
latestEntity.setTs(Long.parseLong(data[2].getValue())); |
|||
|
|||
String strV = data[4].getValue(); |
|||
if (strV != null) { |
|||
if (strV.length() > MAX_STR_V_LENGTH) { |
|||
log.warn("[ts_kv_latest] Value size [{}] exceeds maximum size [{}] of column [str_v] and will be truncated!", |
|||
strV.length(), MAX_STR_V_LENGTH); |
|||
log.warn("Affected data:\n{}", strV); |
|||
strV = strV.substring(0, MAX_STR_V_LENGTH); |
|||
} |
|||
latestEntity.setStrValue(strV); |
|||
} else { |
|||
Long longV = null; |
|||
try { |
|||
longV = Long.parseLong(data[5].getValue()); |
|||
} catch (Exception e) { |
|||
} |
|||
if (longV != null) { |
|||
latestEntity.setLongValue(longV); |
|||
} else { |
|||
Double doubleV = null; |
|||
try { |
|||
doubleV = Double.parseDouble(data[6].getValue()); |
|||
} catch (Exception e) { |
|||
} |
|||
if (doubleV != null) { |
|||
latestEntity.setDoubleValue(doubleV); |
|||
} else { |
|||
|
|||
String jsonV = data[7].getValue(); |
|||
if (StringUtils.isNoneEmpty(jsonV)) { |
|||
latestEntity.setJsonValue(jsonV); |
|||
} else { |
|||
Boolean boolV = null; |
|||
try { |
|||
boolV = Boolean.parseBoolean(data[3].getValue()); |
|||
} catch (Exception e) { |
|||
} |
|||
if (boolV != null) { |
|||
latestEntity.setBooleanValue(boolV); |
|||
} else { |
|||
log.warn("All values in key-value row are nullable "); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return latestEntity; |
|||
} |
|||
|
|||
protected Integer getOrSaveKeyId(String strKey) { |
|||
if (strKey.length() > MAX_KEY_LENGTH) { |
|||
log.warn("[ts_kv_latest] Value size [{}] exceeds maximum size [{}] of column [key] and will be truncated!", |
|||
strKey.length(), MAX_KEY_LENGTH); |
|||
log.warn("Affected data:\n{}", strKey); |
|||
strKey = strKey.substring(0, MAX_KEY_LENGTH); |
|||
} |
|||
|
|||
Integer keyId = tsKvDictionaryMap.get(strKey); |
|||
if (keyId == null) { |
|||
Optional<TsKvDictionary> tsKvDictionaryOptional; |
|||
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
|||
if (!tsKvDictionaryOptional.isPresent()) { |
|||
tsCreationLock.lock(); |
|||
try { |
|||
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
|||
if (!tsKvDictionaryOptional.isPresent()) { |
|||
TsKvDictionary tsKvDictionary = new TsKvDictionary(); |
|||
tsKvDictionary.setKey(strKey); |
|||
try { |
|||
TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); |
|||
tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); |
|||
keyId = saved.getKeyId(); |
|||
} catch (ConstraintViolationException e) { |
|||
tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); |
|||
TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); |
|||
tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); |
|||
keyId = dictionary.getKeyId(); |
|||
} |
|||
} else { |
|||
keyId = tsKvDictionaryOptional.get().getKeyId(); |
|||
} |
|||
} finally { |
|||
tsCreationLock.unlock(); |
|||
} |
|||
} else { |
|||
keyId = tsKvDictionaryOptional.get().getKeyId(); |
|||
tsKvDictionaryMap.put(strKey, keyId); |
|||
} |
|||
} |
|||
return keyId; |
|||
} |
|||
|
|||
private void loadSql(Path sqlFile, Connection conn) throws Exception { |
|||
String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8")); |
|||
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
|
|||
Thread.sleep(5000); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.install.migrate; |
|||
|
|||
public interface TsLatestMigrateService { |
|||
|
|||
void migrate() throws Exception; |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
/** |
|||
* 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.profile; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.device.DeviceProfileService; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
|
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.locks.Lock; |
|||
import java.util.concurrent.locks.ReentrantLock; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache { |
|||
|
|||
private final Lock deviceProfileFetchLock = new ReentrantLock(); |
|||
private final DeviceProfileService deviceProfileService; |
|||
private final DeviceService deviceService; |
|||
|
|||
private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfilesMap = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<DeviceId, DeviceProfileId> devicesMap = new ConcurrentHashMap<>(); |
|||
|
|||
public DefaultTbDeviceProfileCache(DeviceProfileService deviceProfileService, DeviceService deviceService) { |
|||
this.deviceProfileService = deviceProfileService; |
|||
this.deviceService = deviceService; |
|||
} |
|||
|
|||
@Override |
|||
public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) { |
|||
DeviceProfile profile = deviceProfilesMap.get(deviceProfileId); |
|||
if (profile == null) { |
|||
profile = deviceProfilesMap.get(deviceProfileId); |
|||
if (profile == null) { |
|||
deviceProfileFetchLock.lock(); |
|||
try { |
|||
profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); |
|||
if (profile != null) { |
|||
deviceProfilesMap.put(deviceProfileId, profile); |
|||
} |
|||
} finally { |
|||
deviceProfileFetchLock.unlock(); |
|||
} |
|||
} |
|||
} |
|||
return profile; |
|||
} |
|||
|
|||
@Override |
|||
public DeviceProfile get(TenantId tenantId, DeviceId deviceId) { |
|||
DeviceProfileId profileId = devicesMap.get(deviceId); |
|||
if (profileId == null) { |
|||
Device device = deviceService.findDeviceById(tenantId, deviceId); |
|||
if (device != null) { |
|||
profileId = device.getDeviceProfileId(); |
|||
devicesMap.put(deviceId, profileId); |
|||
} |
|||
} |
|||
return get(tenantId, profileId); |
|||
} |
|||
|
|||
@Override |
|||
public void put(DeviceProfile profile) { |
|||
if (profile.getId() != null) { |
|||
deviceProfilesMap.put(profile.getId(), profile); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void evict(DeviceProfileId profileId) { |
|||
deviceProfilesMap.remove(profileId); |
|||
} |
|||
|
|||
@Override |
|||
public void evict(DeviceId deviceId) { |
|||
devicesMap.remove(deviceId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.profile; |
|||
|
|||
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
|
|||
public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache { |
|||
|
|||
void put(DeviceProfile profile); |
|||
|
|||
void evict(DeviceProfileId id); |
|||
|
|||
void evict(DeviceId id); |
|||
|
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.query; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityCountQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
|
|||
import java.util.LinkedHashMap; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
@TbCoreComponent |
|||
public class DefaultEntityQueryService implements EntityQueryService { |
|||
|
|||
@Autowired |
|||
private EntityService entityService; |
|||
|
|||
@Autowired |
|||
private AlarmService alarmService; |
|||
|
|||
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") |
|||
private int maxEntitiesPerAlarmSubscription; |
|||
|
|||
@Override |
|||
public long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query) { |
|||
return entityService.countEntitiesByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) { |
|||
return entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) { |
|||
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); |
|||
PageData<EntityData> entities = entityService.findEntityDataByQuery(securityUser.getTenantId(), |
|||
securityUser.getCustomerId(), entityDataQuery); |
|||
if (entities.getTotalElements() > 0) { |
|||
LinkedHashMap<EntityId, EntityData> entitiesMap = new LinkedHashMap<>(); |
|||
for (EntityData entityData : entities.getData()) { |
|||
entitiesMap.put(entityData.getEntityId(), entityData); |
|||
} |
|||
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(securityUser.getTenantId(), |
|||
securityUser.getCustomerId(), query, entitiesMap.keySet()); |
|||
for (AlarmData alarmData : alarms.getData()) { |
|||
EntityId entityId = alarmData.getEntityId(); |
|||
if (entityId != null) { |
|||
EntityData entityData = entitiesMap.get(entityId); |
|||
if (entityData != null) { |
|||
alarmData.getLatest().putAll(entityData.getLatest()); |
|||
} |
|||
} |
|||
} |
|||
return alarms; |
|||
} else { |
|||
return new PageData<>(); |
|||
} |
|||
} |
|||
|
|||
private EntityDataQuery buildEntityDataQuery(AlarmDataQuery query) { |
|||
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); |
|||
EntityDataSortOrder entitiesSortOrder; |
|||
if (sortOrder == null || sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) { |
|||
entitiesSortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, ModelConstants.CREATED_TIME_PROPERTY)); |
|||
} else { |
|||
entitiesSortOrder = sortOrder; |
|||
} |
|||
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder); |
|||
return new EntityDataQuery(query.getEntityFilter(), edpl, query.getEntityFields(), query.getLatestValues(), query.getKeyFilters()); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.query; |
|||
|
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityCountQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
|
|||
public interface EntityQueryService { |
|||
|
|||
long countEntitiesByQuery(SecurityUser securityUser, EntityCountQuery query); |
|||
|
|||
PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query); |
|||
|
|||
PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query); |
|||
|
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
/** |
|||
* 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.queue; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.RuleNodeId; |
|||
import org.thingsboard.server.common.msg.queue.RuleNodeInfo; |
|||
|
|||
import java.util.AbstractMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
import java.util.concurrent.locks.Lock; |
|||
import java.util.concurrent.locks.ReentrantLock; |
|||
|
|||
@Slf4j |
|||
public class TbMsgProfilerInfo { |
|||
private final UUID msgId; |
|||
private AtomicLong totalProcessingTime = new AtomicLong(); |
|||
private Lock stateLock = new ReentrantLock(); |
|||
private RuleNodeId currentRuleNodeId; |
|||
private long stateChangeTime; |
|||
|
|||
public TbMsgProfilerInfo(UUID msgId) { |
|||
this.msgId = msgId; |
|||
} |
|||
|
|||
public void onStart(RuleNodeId ruleNodeId) { |
|||
long currentTime = System.currentTimeMillis(); |
|||
stateLock.lock(); |
|||
try { |
|||
currentRuleNodeId = ruleNodeId; |
|||
stateChangeTime = currentTime; |
|||
} finally { |
|||
stateLock.unlock(); |
|||
} |
|||
} |
|||
|
|||
public long onEnd(RuleNodeId ruleNodeId) { |
|||
long currentTime = System.currentTimeMillis(); |
|||
stateLock.lock(); |
|||
try { |
|||
if (ruleNodeId.equals(currentRuleNodeId)) { |
|||
long processingTime = currentTime - stateChangeTime; |
|||
stateChangeTime = currentTime; |
|||
totalProcessingTime.addAndGet(processingTime); |
|||
currentRuleNodeId = null; |
|||
return processingTime; |
|||
} else { |
|||
log.trace("[{}] Invalid sequence of rule node processing detected. Expected [{}] but was [{}]", msgId, currentRuleNodeId, ruleNodeId); |
|||
return 0; |
|||
} |
|||
} finally { |
|||
stateLock.unlock(); |
|||
} |
|||
} |
|||
|
|||
public Map.Entry<UUID, Long> onTimeout() { |
|||
long currentTime = System.currentTimeMillis(); |
|||
stateLock.lock(); |
|||
try { |
|||
if (currentRuleNodeId != null && stateChangeTime > 0) { |
|||
long timeoutTime = currentTime - stateChangeTime; |
|||
totalProcessingTime.addAndGet(timeoutTime); |
|||
return new AbstractMap.SimpleEntry<>(currentRuleNodeId.getId(), timeoutTime); |
|||
} |
|||
} finally { |
|||
stateLock.unlock(); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
/** |
|||
* 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.queue; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.msg.queue.RuleNodeInfo; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
|
|||
public class TbRuleNodeProfilerInfo { |
|||
@Getter |
|||
private final UUID ruleNodeId; |
|||
@Getter |
|||
private final String label; |
|||
private AtomicInteger executionCount = new AtomicInteger(0); |
|||
private AtomicLong executionTime = new AtomicLong(0); |
|||
private AtomicLong maxExecutionTime = new AtomicLong(0); |
|||
|
|||
public TbRuleNodeProfilerInfo(RuleNodeInfo ruleNodeInfo) { |
|||
this.ruleNodeId = ruleNodeInfo.getRuleNodeId().getId(); |
|||
this.label = ruleNodeInfo.toString(); |
|||
} |
|||
|
|||
public TbRuleNodeProfilerInfo(UUID ruleNodeId) { |
|||
this.ruleNodeId = ruleNodeId; |
|||
this.label = ""; |
|||
} |
|||
|
|||
public void record(long processingTime) { |
|||
executionCount.incrementAndGet(); |
|||
executionTime.addAndGet(processingTime); |
|||
while (true) { |
|||
long value = maxExecutionTime.get(); |
|||
if (value >= processingTime) { |
|||
break; |
|||
} |
|||
if (maxExecutionTime.compareAndSet(value, processingTime)) { |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
int getExecutionCount() { |
|||
return executionCount.get(); |
|||
} |
|||
|
|||
long getMaxExecutionTime() { |
|||
return maxExecutionTime.get(); |
|||
} |
|||
|
|||
double getAvgExecutionTime() { |
|||
double executionCnt = (double) executionCount.get(); |
|||
if (executionCnt > 0) { |
|||
return executionTime.get() / executionCnt; |
|||
} else { |
|||
return 0.0; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.stats; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.actors.JsInvokeStats; |
|||
import org.thingsboard.server.common.stats.StatsCounter; |
|||
import org.thingsboard.server.common.stats.StatsFactory; |
|||
import org.thingsboard.server.common.stats.StatsType; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
|
|||
@Service |
|||
public class DefaultJsInvokeStats implements JsInvokeStats { |
|||
private static final String REQUESTS = "requests"; |
|||
private static final String RESPONSES = "responses"; |
|||
private static final String FAILURES = "failures"; |
|||
|
|||
private StatsCounter requestsCounter; |
|||
private StatsCounter responsesCounter; |
|||
private StatsCounter failuresCounter; |
|||
|
|||
@Autowired |
|||
private StatsFactory statsFactory; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
String key = StatsType.JS_INVOKE.getName(); |
|||
this.requestsCounter = statsFactory.createStatsCounter(key, REQUESTS); |
|||
this.responsesCounter = statsFactory.createStatsCounter(key, RESPONSES); |
|||
this.failuresCounter = statsFactory.createStatsCounter(key, FAILURES); |
|||
} |
|||
|
|||
@Override |
|||
public void incrementRequests(int amount) { |
|||
requestsCounter.add(amount); |
|||
} |
|||
|
|||
@Override |
|||
public void incrementResponses(int amount) { |
|||
responsesCounter.add(amount); |
|||
} |
|||
|
|||
@Override |
|||
public void incrementFailures(int amount) { |
|||
failuresCounter.add(amount); |
|||
} |
|||
|
|||
@Override |
|||
public int getRequests() { |
|||
return requestsCounter.get(); |
|||
} |
|||
|
|||
@Override |
|||
public int getResponses() { |
|||
return responsesCounter.get(); |
|||
} |
|||
|
|||
@Override |
|||
public int getFailures() { |
|||
return failuresCounter.get(); |
|||
} |
|||
|
|||
@Override |
|||
public void reset() { |
|||
requestsCounter.clear(); |
|||
responsesCounter.clear(); |
|||
failuresCounter.clear(); |
|||
} |
|||
} |
|||
@ -0,0 +1,484 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.subscription; |
|||
|
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.checkerframework.checker.nullness.qual.Nullable; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; |
|||
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
|||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AlarmDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
import org.thingsboard.server.dao.alarm.AlarmService; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.executors.DbCallbackExecutorService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.GetTsCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; |
|||
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; |
|||
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.LinkedHashMap; |
|||
import java.util.LinkedHashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutionException; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.ScheduledFuture; |
|||
import java.util.concurrent.ThreadFactory; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
@TbCoreComponent |
|||
@Service |
|||
public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService { |
|||
|
|||
private static final int DEFAULT_LIMIT = 100; |
|||
private final Map<String, Map<Integer, TbAbstractDataSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>(); |
|||
|
|||
@Autowired |
|||
private TelemetryWebSocketService wsService; |
|||
|
|||
@Autowired |
|||
private EntityService entityService; |
|||
|
|||
@Autowired |
|||
private AlarmService alarmService; |
|||
|
|||
@Autowired |
|||
private AttributesService attributesService; |
|||
|
|||
@Autowired |
|||
@Lazy |
|||
private TbLocalSubscriptionService localSubscriptionService; |
|||
|
|||
@Autowired |
|||
private TimeseriesService tsService; |
|||
|
|||
@Autowired |
|||
private TbServiceInfoProvider serviceInfoProvider; |
|||
|
|||
@Autowired |
|||
@Getter |
|||
private DbCallbackExecutorService dbCallbackExecutor; |
|||
|
|||
private ScheduledExecutorService scheduler; |
|||
|
|||
@Value("${database.ts.type}") |
|||
private String databaseTsType; |
|||
@Value("${server.ws.dynamic_page_link.refresh_interval:6}") |
|||
private long dynamicPageLinkRefreshInterval; |
|||
@Value("${server.ws.dynamic_page_link.refresh_pool_size:1}") |
|||
private int dynamicPageLinkRefreshPoolSize; |
|||
@Value("${server.ws.max_entities_per_data_subscription:1000}") |
|||
private int maxEntitiesPerDataSubscription; |
|||
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") |
|||
private int maxEntitiesPerAlarmSubscription; |
|||
|
|||
private ExecutorService wsCallBackExecutor; |
|||
private boolean tsInSqlDB; |
|||
private String serviceId; |
|||
private SubscriptionServiceStatistics stats = new SubscriptionServiceStatistics(); |
|||
|
|||
@PostConstruct |
|||
public void initExecutor() { |
|||
serviceId = serviceInfoProvider.getServiceId(); |
|||
wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-entity-sub-callback")); |
|||
tsInSqlDB = databaseTsType.equalsIgnoreCase("sql") || databaseTsType.equalsIgnoreCase("timescale"); |
|||
ThreadFactory tbThreadFactory = ThingsBoardThreadFactory.forName("ws-entity-sub-scheduler"); |
|||
if (dynamicPageLinkRefreshPoolSize == 1) { |
|||
scheduler = Executors.newSingleThreadScheduledExecutor(tbThreadFactory); |
|||
} else { |
|||
scheduler = Executors.newScheduledThreadPool(dynamicPageLinkRefreshPoolSize, tbThreadFactory); |
|||
} |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void shutdownExecutor() { |
|||
if (wsCallBackExecutor != null) { |
|||
wsCallBackExecutor.shutdownNow(); |
|||
} |
|||
if (scheduler != null) { |
|||
scheduler.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void handleCmd(TelemetryWebSocketSessionRef session, EntityDataCmd cmd) { |
|||
TbEntityDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); |
|||
if (ctx != null) { |
|||
log.debug("[{}][{}] Updating existing subscriptions using: {}", session.getSessionId(), cmd.getCmdId(), cmd); |
|||
if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null || cmd.getHistoryCmd() != null) { |
|||
ctx.clearEntitySubscriptions(); |
|||
} |
|||
} else { |
|||
log.debug("[{}][{}] Creating new subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd); |
|||
ctx = createSubCtx(session, cmd); |
|||
} |
|||
ctx.setCurrentCmd(cmd); |
|||
if (cmd.getQuery() != null) { |
|||
if (ctx.getQuery() == null) { |
|||
log.debug("[{}][{}] Initializing data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery()); |
|||
} else { |
|||
log.debug("[{}][{}] Updating data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery()); |
|||
} |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
TenantId tenantId = ctx.getTenantId(); |
|||
CustomerId customerId = ctx.getCustomerId(); |
|||
EntityDataQuery query = ctx.getQuery(); |
|||
//Step 1. Update existing query with the contents of LatestValueCmd
|
|||
if (cmd.getLatestCmd() != null) { |
|||
cmd.getLatestCmd().getKeys().forEach(key -> { |
|||
if (!query.getLatestValues().contains(key)) { |
|||
query.getLatestValues().add(key); |
|||
} |
|||
}); |
|||
} |
|||
long start = System.currentTimeMillis(); |
|||
ctx.fetchData(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getRegularQueryInvocationCnt().incrementAndGet(); |
|||
stats.getRegularQueryTimeSpent().addAndGet(end - start); |
|||
ctx.cancelTasks(); |
|||
if (ctx.getQuery().getPageLink().isDynamic()) { |
|||
//TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached.
|
|||
TbEntityDataSubCtx finalCtx = ctx; |
|||
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( |
|||
() -> refreshDynamicQuery(tenantId, customerId, finalCtx), |
|||
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); |
|||
finalCtx.setRefreshTask(task); |
|||
} |
|||
} |
|||
ListenableFuture<TbEntityDataSubCtx> historyFuture; |
|||
if (cmd.getHistoryCmd() != null) { |
|||
log.trace("[{}][{}] Going to process history command: {}", session.getSessionId(), cmd.getCmdId(), cmd.getHistoryCmd()); |
|||
historyFuture = handleHistoryCmd(ctx, cmd.getHistoryCmd()); |
|||
} else { |
|||
historyFuture = Futures.immediateFuture(ctx); |
|||
} |
|||
Futures.addCallback(historyFuture, new FutureCallback<TbEntityDataSubCtx>() { |
|||
@Override |
|||
public void onSuccess(@Nullable TbEntityDataSubCtx theCtx) { |
|||
if (cmd.getLatestCmd() != null) { |
|||
handleLatestCmd(theCtx, cmd.getLatestCmd()); |
|||
} else if (cmd.getTsCmd() != null) { |
|||
handleTimeSeriesCmd(theCtx, cmd.getTsCmd()); |
|||
} else if (!theCtx.isInitialDataSent()) { |
|||
EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription()); |
|||
wsService.sendWsMsg(theCtx.getSessionId(), update); |
|||
theCtx.setInitialDataSent(true); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.warn("[{}][{}] Failed to process command", session.getSessionId(), cmd.getCmdId()); |
|||
} |
|||
}, wsCallBackExecutor); |
|||
} |
|||
|
|||
@Override |
|||
public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) { |
|||
TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); |
|||
if (ctx == null) { |
|||
log.debug("[{}][{}] Creating new alarm subscription using: {}", session.getSessionId(), cmd.getCmdId(), cmd); |
|||
ctx = createSubCtx(session, cmd); |
|||
} |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
AlarmDataQuery adq = ctx.getQuery(); |
|||
long start = System.currentTimeMillis(); |
|||
ctx.fetchData(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getRegularQueryInvocationCnt().incrementAndGet(); |
|||
stats.getRegularQueryTimeSpent().addAndGet(end - start); |
|||
List<EntityData> entities = ctx.getEntitiesData(); |
|||
ctx.cancelTasks(); |
|||
ctx.clearEntitySubscriptions(); |
|||
if (entities.isEmpty()) { |
|||
AlarmDataUpdate update = new AlarmDataUpdate(cmd.getCmdId(), new PageData<>(), null, 0, 0); |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
} else { |
|||
ctx.fetchAlarms(); |
|||
ctx.createSubscriptions(cmd.getQuery().getLatestValues(), true); |
|||
if (adq.getPageLink().getTimeWindow() > 0) { |
|||
TbAlarmDataSubCtx finalCtx = ctx; |
|||
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( |
|||
finalCtx::cleanupOldAlarms, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); |
|||
finalCtx.setRefreshTask(task); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void refreshDynamicQuery(TenantId tenantId, CustomerId customerId, TbEntityDataSubCtx finalCtx) { |
|||
try { |
|||
long start = System.currentTimeMillis(); |
|||
finalCtx.update(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getDynamicQueryInvocationCnt().incrementAndGet(); |
|||
stats.getDynamicQueryTimeSpent().addAndGet(end - start); |
|||
} catch (Exception e) { |
|||
log.warn("[{}][{}] Failed to refresh query", finalCtx.getSessionId(), finalCtx.getCmdId(), e); |
|||
} |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${server.ws.dynamic_page_link.stats:10000}") |
|||
public void printStats() { |
|||
int alarmQueryInvocationCntValue = stats.getAlarmQueryInvocationCnt().getAndSet(0); |
|||
long alarmQueryInvocationTimeValue = stats.getAlarmQueryTimeSpent().getAndSet(0); |
|||
int regularQueryInvocationCntValue = stats.getRegularQueryInvocationCnt().getAndSet(0); |
|||
long regularQueryInvocationTimeValue = stats.getRegularQueryTimeSpent().getAndSet(0); |
|||
int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0); |
|||
long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0); |
|||
long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count(); |
|||
if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0 || alarmQueryInvocationCntValue > 0) { |
|||
log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " + |
|||
"dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " + |
|||
"alarmQueryInvocationCnt = [{}], alarmQueryInvocationTime = [{}]", |
|||
regularQueryInvocationCntValue, regularQueryInvocationTimeValue, |
|||
dynamicQueryCnt, dynamicQueryInvocationCntValue, dynamicQueryInvocationTimeValue, |
|||
alarmQueryInvocationCntValue, alarmQueryInvocationTimeValue); |
|||
} |
|||
} |
|||
|
|||
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); |
|||
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, |
|||
attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
sessionSubs.put(cmd.getCmdId(), ctx); |
|||
return ctx; |
|||
} |
|||
|
|||
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); |
|||
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, |
|||
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); |
|||
ctx.setAndResolveQuery(cmd.getQuery()); |
|||
sessionSubs.put(cmd.getCmdId(), ctx); |
|||
return ctx; |
|||
} |
|||
|
|||
private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId); |
|||
if (sessionSubs != null) { |
|||
return (T) sessionSubs.get(cmdId); |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private ListenableFuture<TbEntityDataSubCtx> handleTimeSeriesCmd(TbEntityDataSubCtx ctx, TimeSeriesCmd cmd) { |
|||
log.debug("[{}][{}] Fetching time-series data for last {} ms for keys: ({})", ctx.getSessionId(), ctx.getCmdId(), cmd.getTimeWindow(), cmd.getKeys()); |
|||
return handleGetTsCmd(ctx, cmd, true); |
|||
} |
|||
|
|||
|
|||
private ListenableFuture<TbEntityDataSubCtx> handleHistoryCmd(TbEntityDataSubCtx ctx, EntityHistoryCmd cmd) { |
|||
log.debug("[{}][{}] Fetching history data for start {} and end {} ms for keys: ({})", ctx.getSessionId(), ctx.getCmdId(), cmd.getStartTs(), cmd.getEndTs(), cmd.getKeys()); |
|||
return handleGetTsCmd(ctx, cmd, false); |
|||
} |
|||
|
|||
private ListenableFuture<TbEntityDataSubCtx> handleGetTsCmd(TbEntityDataSubCtx ctx, GetTsCmd cmd, boolean subscribe) { |
|||
List<String> keys = cmd.getKeys(); |
|||
List<ReadTsKvQuery> finalTsKvQueryList; |
|||
List<ReadTsKvQuery> tsKvQueryList = cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery( |
|||
key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), cmd.getAgg() |
|||
)).collect(Collectors.toList()); |
|||
if (cmd.isFetchLatestPreviousPoint()) { |
|||
finalTsKvQueryList = new ArrayList<>(tsKvQueryList); |
|||
finalTsKvQueryList.addAll(cmd.getKeys().stream().map(key -> new BaseReadTsKvQuery( |
|||
key, cmd.getStartTs() - TimeUnit.DAYS.toMillis(365), cmd.getStartTs(), cmd.getInterval(), 1, cmd.getAgg() |
|||
)).collect(Collectors.toList())); |
|||
} else { |
|||
finalTsKvQueryList = tsKvQueryList; |
|||
} |
|||
Map<EntityData, ListenableFuture<List<TsKvEntry>>> fetchResultMap = new HashMap<>(); |
|||
ctx.getData().getData().forEach(entityData -> fetchResultMap.put(entityData, |
|||
tsService.findAll(ctx.getTenantId(), entityData.getEntityId(), finalTsKvQueryList))); |
|||
return Futures.transform(Futures.allAsList(fetchResultMap.values()), f -> { |
|||
fetchResultMap.forEach((entityData, future) -> { |
|||
Map<String, List<TsValue>> keyData = new LinkedHashMap<>(); |
|||
cmd.getKeys().forEach(key -> keyData.put(key, new ArrayList<>())); |
|||
try { |
|||
List<TsKvEntry> entityTsData = future.get(); |
|||
if (entityTsData != null) { |
|||
entityTsData.forEach(entry -> keyData.get(entry.getKey()).add(new TsValue(entry.getTs(), entry.getValueAsString()))); |
|||
} |
|||
keyData.forEach((k, v) -> entityData.getTimeseries().put(k, v.toArray(new TsValue[v.size()]))); |
|||
if (cmd.isFetchLatestPreviousPoint()) { |
|||
entityData.getTimeseries().values().forEach(dataArray -> { |
|||
Arrays.sort(dataArray, (o1, o2) -> Long.compare(o2.getTs(), o1.getTs())); |
|||
}); |
|||
} |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e); |
|||
wsService.sendWsMsg(ctx.getSessionId(), |
|||
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to fetch historical data!")); |
|||
} |
|||
}); |
|||
EntityDataUpdate update; |
|||
if (!ctx.isInitialDataSent()) { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); |
|||
ctx.setInitialDataSent(true); |
|||
} else { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); |
|||
} |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
if (subscribe) { |
|||
ctx.createSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), false); |
|||
} |
|||
ctx.getData().getData().forEach(ed -> ed.getTimeseries().clear()); |
|||
return ctx; |
|||
}, wsCallBackExecutor); |
|||
} |
|||
|
|||
private void handleLatestCmd(TbEntityDataSubCtx ctx, LatestValueCmd latestCmd) { |
|||
log.trace("[{}][{}] Going to process latest command: {}", ctx.getSessionId(), ctx.getCmdId(), latestCmd); |
|||
//Fetch the latest values for telemetry keys (in case they are not copied from NoSQL to SQL DB in hybrid mode.
|
|||
if (!tsInSqlDB) { |
|||
log.trace("[{}][{}] Going to fetch missing latest values: {}", ctx.getSessionId(), ctx.getCmdId(), latestCmd); |
|||
List<String> allTsKeys = latestCmd.getKeys().stream() |
|||
.filter(key -> key.getType().equals(EntityKeyType.TIME_SERIES)) |
|||
.map(EntityKey::getKey).collect(Collectors.toList()); |
|||
|
|||
Map<EntityData, ListenableFuture<Map<String, TsValue>>> missingTelemetryFutures = new HashMap<>(); |
|||
for (EntityData entityData : ctx.getData().getData()) { |
|||
Map<EntityKeyType, Map<String, TsValue>> latestEntityData = entityData.getLatest(); |
|||
Map<String, TsValue> tsEntityData = latestEntityData.get(EntityKeyType.TIME_SERIES); |
|||
Set<String> missingTsKeys = new LinkedHashSet<>(allTsKeys); |
|||
if (tsEntityData != null) { |
|||
missingTsKeys.removeAll(tsEntityData.keySet()); |
|||
} else { |
|||
tsEntityData = new HashMap<>(); |
|||
latestEntityData.put(EntityKeyType.TIME_SERIES, tsEntityData); |
|||
} |
|||
|
|||
ListenableFuture<List<TsKvEntry>> missingTsData = tsService.findLatest(ctx.getTenantId(), entityData.getEntityId(), missingTsKeys); |
|||
missingTelemetryFutures.put(entityData, Futures.transform(missingTsData, this::toTsValue, MoreExecutors.directExecutor())); |
|||
} |
|||
Futures.addCallback(Futures.allAsList(missingTelemetryFutures.values()), new FutureCallback<List<Map<String, TsValue>>>() { |
|||
@Override |
|||
public void onSuccess(@Nullable List<Map<String, TsValue>> result) { |
|||
missingTelemetryFutures.forEach((key, value) -> { |
|||
try { |
|||
key.getLatest().get(EntityKeyType.TIME_SERIES).putAll(value.get()); |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
log.warn("[{}][{}] Failed to lookup latest telemetry: {}:{}", ctx.getSessionId(), ctx.getCmdId(), key.getEntityId(), allTsKeys, e); |
|||
} |
|||
}); |
|||
EntityDataUpdate update; |
|||
if (!ctx.isInitialDataSent()) { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); |
|||
ctx.setInitialDataSent(true); |
|||
} else { |
|||
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); |
|||
} |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
ctx.createSubscriptions(latestCmd.getKeys(), true); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.warn("[{}][{}] Failed to process websocket command: {}:{}", ctx.getSessionId(), ctx.getCmdId(), ctx.getQuery(), latestCmd, t); |
|||
wsService.sendWsMsg(ctx.getSessionId(), |
|||
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to process websocket command!")); |
|||
} |
|||
}, wsCallBackExecutor); |
|||
} else { |
|||
if (!ctx.isInitialDataSent()) { |
|||
EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); |
|||
wsService.sendWsMsg(ctx.getSessionId(), update); |
|||
ctx.setInitialDataSent(true); |
|||
} |
|||
ctx.createSubscriptions(latestCmd.getKeys(), true); |
|||
} |
|||
} |
|||
|
|||
private Map<String, TsValue> toTsValue(List<TsKvEntry> data) { |
|||
return data.stream().collect(Collectors.toMap(TsKvEntry::getKey, value -> new TsValue(value.getTs(), value.getValueAsString()))); |
|||
} |
|||
|
|||
@Override |
|||
public void cancelSubscription(String sessionId, UnsubscribeCmd cmd) { |
|||
cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId())); |
|||
} |
|||
|
|||
private void cleanupAndCancel(TbAbstractDataSubCtx ctx) { |
|||
if (ctx != null) { |
|||
ctx.cancelTasks(); |
|||
ctx.clearEntitySubscriptions(); |
|||
ctx.clearDynamicValueSubscriptions(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void cancelAllSessionSubscriptions(String sessionId) { |
|||
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId); |
|||
if (sessionSubs != null) { |
|||
sessionSubs.values().stream().filter(sub -> sub instanceof TbEntityDataSubCtx).map(sub -> (TbEntityDataSubCtx) sub).forEach(this::cleanupAndCancel); |
|||
} |
|||
} |
|||
|
|||
private int getLimit(int limit) { |
|||
return limit == 0 ? DEFAULT_LIMIT : limit; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.subscription; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
|
|||
@Data |
|||
public class SubscriptionServiceStatistics { |
|||
private AtomicInteger alarmQueryInvocationCnt = new AtomicInteger(); |
|||
private AtomicInteger regularQueryInvocationCnt = new AtomicInteger(); |
|||
private AtomicInteger dynamicQueryInvocationCnt = new AtomicInteger(); |
|||
private AtomicLong alarmQueryTimeSpent = new AtomicLong(); |
|||
private AtomicLong regularQueryTimeSpent = new AtomicLong(); |
|||
private AtomicLong dynamicQueryTimeSpent = new AtomicLong(); |
|||
} |
|||
@ -0,0 +1,470 @@ |
|||
/** |
|||
* Copyright © 2016-2020 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.subscription; |
|||
|
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import lombok.Data; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.AbstractDataQuery; |
|||
import org.thingsboard.server.common.data.query.ComplexFilterPredicate; |
|||
import org.thingsboard.server.common.data.query.DynamicValue; |
|||
import org.thingsboard.server.common.data.query.DynamicValueSourceType; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.FilterPredicateType; |
|||
import org.thingsboard.server.common.data.query.KeyFilter; |
|||
import org.thingsboard.server.common.data.query.KeyFilterPredicate; |
|||
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
import org.thingsboard.server.dao.attributes.AttributesService; |
|||
import org.thingsboard.server.dao.entity.EntityService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; |
|||
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
|||
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutionException; |
|||
import java.util.concurrent.ScheduledFuture; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> { |
|||
|
|||
protected final String serviceId; |
|||
protected final SubscriptionServiceStatistics stats; |
|||
protected final TelemetryWebSocketService wsService; |
|||
protected final EntityService entityService; |
|||
protected final TbLocalSubscriptionService localSubscriptionService; |
|||
protected final AttributesService attributesService; |
|||
protected final TelemetryWebSocketSessionRef sessionRef; |
|||
protected final int cmdId; |
|||
protected final Map<Integer, EntityId> subToEntityIdMap; |
|||
protected final Set<Integer> subToDynamicValueKeySet; |
|||
@Getter |
|||
protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues; |
|||
@Getter |
|||
protected PageData<EntityData> data; |
|||
@Getter |
|||
@Setter |
|||
protected T query; |
|||
@Setter |
|||
protected volatile ScheduledFuture<?> refreshTask; |
|||
|
|||
public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService, |
|||
EntityService entityService, TbLocalSubscriptionService localSubscriptionService, |
|||
AttributesService attributesService, SubscriptionServiceStatistics stats, |
|||
TelemetryWebSocketSessionRef sessionRef, int cmdId) { |
|||
this.serviceId = serviceId; |
|||
this.wsService = wsService; |
|||
this.entityService = entityService; |
|||
this.localSubscriptionService = localSubscriptionService; |
|||
this.attributesService = attributesService; |
|||
this.stats = stats; |
|||
this.sessionRef = sessionRef; |
|||
this.cmdId = cmdId; |
|||
this.subToEntityIdMap = new ConcurrentHashMap<>(); |
|||
this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet(); |
|||
this.dynamicValues = new ConcurrentHashMap<>(); |
|||
} |
|||
|
|||
public void setAndResolveQuery(T query) { |
|||
dynamicValues.clear(); |
|||
this.query = query; |
|||
if (query.getKeyFilters() != null) { |
|||
for (KeyFilter filter : query.getKeyFilters()) { |
|||
registerDynamicValues(filter.getPredicate()); |
|||
} |
|||
} |
|||
resolve(getTenantId(), getCustomerId(), getUserId()); |
|||
} |
|||
|
|||
public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) { |
|||
List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>(); |
|||
for (DynamicValueKey key : dynamicValues.keySet()) { |
|||
switch (key.getSourceType()) { |
|||
case CURRENT_TENANT: |
|||
futures.add(resolveEntityValue(tenantId, tenantId, key)); |
|||
break; |
|||
case CURRENT_CUSTOMER: |
|||
if (customerId != null && !customerId.isNullUid()) { |
|||
futures.add(resolveEntityValue(tenantId, customerId, key)); |
|||
} |
|||
break; |
|||
case CURRENT_USER: |
|||
if (userId != null && !userId.isNullUid()) { |
|||
futures.add(resolveEntityValue(tenantId, userId, key)); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
try { |
|||
Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>(); |
|||
for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) { |
|||
tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub); |
|||
} |
|||
for (EntityId entityId : tmpSubMap.keySet()) { |
|||
Map<String, Long> keyStates = new HashMap<>(); |
|||
Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId); |
|||
dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs())); |
|||
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); |
|||
TbAttributeSubscription sub = TbAttributeSubscription.builder() |
|||
.serviceId(serviceId) |
|||
.sessionId(sessionRef.getSessionId()) |
|||
.subscriptionId(subIdx) |
|||
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
|||
.entityId(entityId) |
|||
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap)) |
|||
.allKeys(false) |
|||
.keyStates(keyStates) |
|||
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE) |
|||
.build(); |
|||
subToDynamicValueKeySet.add(subIdx); |
|||
localSubscriptionService.addSubscription(sub); |
|||
} |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet()); |
|||
} |
|||
|
|||
} |
|||
|
|||
private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, |
|||
Map<String, DynamicValueKeySub> dynamicValueKeySubMap) { |
|||
Map<String, TsValue> latestUpdate = new HashMap<>(); |
|||
subscriptionUpdate.getData().forEach((k, v) -> { |
|||
Object[] data = (Object[]) v.get(0); |
|||
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); |
|||
}); |
|||
|
|||
boolean invalidateFilter = false; |
|||
for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) { |
|||
String k = entry.getKey(); |
|||
TsValue tsValue = entry.getValue(); |
|||
DynamicValueKeySub sub = dynamicValueKeySubMap.get(k); |
|||
if (sub.updateValue(tsValue)) { |
|||
invalidateFilter = true; |
|||
updateDynamicValuesByKey(sub, tsValue); |
|||
} |
|||
} |
|||
|
|||
if (invalidateFilter) { |
|||
update(); |
|||
} |
|||
} |
|||
|
|||
public void fetchData() { |
|||
this.data = findEntityData(); |
|||
} |
|||
|
|||
protected PageData<EntityData> findEntityData() { |
|||
PageData<EntityData> result = entityService.findEntityDataByQuery(getTenantId(), getCustomerId(), buildEntityDataQuery()); |
|||
if (log.isTraceEnabled()) { |
|||
result.getData().forEach(ed -> { |
|||
log.trace("[{}][{}] EntityData: {}", getSessionId(), getCmdId(), ed); |
|||
}); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
protected synchronized void update() { |
|||
long start = System.currentTimeMillis(); |
|||
PageData<EntityData> newData = findEntityData(); |
|||
long end = System.currentTimeMillis(); |
|||
stats.getRegularQueryInvocationCnt().incrementAndGet(); |
|||
stats.getRegularQueryTimeSpent().addAndGet(end - start); |
|||
Map<EntityId, EntityData> oldDataMap; |
|||
if (data != null && !data.getData().isEmpty()) { |
|||
oldDataMap = data.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a, b) -> a)); |
|||
} else { |
|||
oldDataMap = Collections.emptyMap(); |
|||
} |
|||
Map<EntityId, EntityData> newDataMap = newData.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a,b)-> a)); |
|||
if (oldDataMap.size() == newDataMap.size() && oldDataMap.keySet().equals(newDataMap.keySet())) { |
|||
log.trace("[{}][{}] No updates to entity data found", sessionRef.getSessionId(), cmdId); |
|||
} else { |
|||
this.data = newData; |
|||
doUpdate(newDataMap); |
|||
} |
|||
} |
|||
|
|||
protected abstract void doUpdate(Map<EntityId, EntityData> newDataMap); |
|||
|
|||
protected abstract EntityDataQuery buildEntityDataQuery(); |
|||
|
|||
public List<EntityData> getEntitiesData() { |
|||
return data.getData(); |
|||
} |
|||
|
|||
@Data |
|||
private static class DynamicValueKeySub { |
|||
private final DynamicValueKey key; |
|||
private final EntityId entityId; |
|||
private long lastUpdateTs; |
|||
private String lastUpdateValue; |
|||
|
|||
boolean updateValue(TsValue value) { |
|||
if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) { |
|||
this.lastUpdateTs = value.getTs(); |
|||
this.lastUpdateValue = value.getValue(); |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) { |
|||
ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId, |
|||
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute()); |
|||
return Futures.transform(entry, attributeOpt -> { |
|||
DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId); |
|||
if (attributeOpt.isPresent()) { |
|||
AttributeKvEntry attribute = attributeOpt.get(); |
|||
sub.setLastUpdateTs(attribute.getLastUpdateTs()); |
|||
sub.setLastUpdateValue(attribute.getValueAsString()); |
|||
updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString())); |
|||
} |
|||
return sub; |
|||
}, MoreExecutors.directExecutor()); |
|||
} |
|||
|
|||
private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) { |
|||
DynamicValueKey dvk = sub.getKey(); |
|||
switch (dvk.getPredicateType()) { |
|||
case STRING: |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue())); |
|||
break; |
|||
case NUMERIC: |
|||
try { |
|||
Double dValue = Double.parseDouble(tsValue.getValue()); |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue)); |
|||
} catch (NumberFormatException e) { |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null)); |
|||
} |
|||
break; |
|||
case BOOLEAN: |
|||
Boolean bValue = Boolean.parseBoolean(tsValue.getValue()); |
|||
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue)); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private void registerDynamicValues(KeyFilterPredicate predicate) { |
|||
switch (predicate.getType()) { |
|||
case STRING: |
|||
case NUMERIC: |
|||
case BOOLEAN: |
|||
Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate); |
|||
if (value.isPresent()) { |
|||
DynamicValue dynamicValue = value.get(); |
|||
DynamicValueKey key = new DynamicValueKey( |
|||
predicate.getType(), |
|||
dynamicValue.getSourceType(), |
|||
dynamicValue.getSourceAttribute()); |
|||
dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue); |
|||
} |
|||
break; |
|||
case COMPLEX: |
|||
((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues); |
|||
} |
|||
} |
|||
|
|||
private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) { |
|||
if (predicate.getValue().getUserValue() == null) { |
|||
return Optional.ofNullable(predicate.getValue().getDynamicValue()); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
public String getSessionId() { |
|||
return sessionRef.getSessionId(); |
|||
} |
|||
|
|||
public TenantId getTenantId() { |
|||
return sessionRef.getSecurityCtx().getTenantId(); |
|||
} |
|||
|
|||
public CustomerId getCustomerId() { |
|||
return sessionRef.getSecurityCtx().getCustomerId(); |
|||
} |
|||
|
|||
public UserId getUserId() { |
|||
return sessionRef.getSecurityCtx().getId(); |
|||
} |
|||
|
|||
public void clearEntitySubscriptions() { |
|||
if (subToEntityIdMap != null) { |
|||
for (Integer subId : subToEntityIdMap.keySet()) { |
|||
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); |
|||
} |
|||
subToEntityIdMap.clear(); |
|||
} |
|||
} |
|||
|
|||
public void clearDynamicValueSubscriptions() { |
|||
if (subToDynamicValueKeySet != null) { |
|||
for (Integer subId : subToDynamicValueKeySet) { |
|||
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); |
|||
} |
|||
subToDynamicValueKeySet.clear(); |
|||
} |
|||
} |
|||
|
|||
public void setRefreshTask(ScheduledFuture<?> task) { |
|||
this.refreshTask = task; |
|||
} |
|||
|
|||
public void cancelTasks() { |
|||
if (this.refreshTask != null) { |
|||
log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId); |
|||
this.refreshTask.cancel(true); |
|||
} |
|||
} |
|||
|
|||
public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) { |
|||
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys); |
|||
for (EntityData entityData : data.getData()) { |
|||
List<TbSubscription> entitySubscriptions = addSubscriptions(entityData, keysByType, resultToLatestValues); |
|||
entitySubscriptions.forEach(localSubscriptionService::addSubscription); |
|||
} |
|||
} |
|||
|
|||
protected Map<EntityKeyType, List<EntityKey>> getEntityKeyByTypeMap(List<EntityKey> keys) { |
|||
Map<EntityKeyType, List<EntityKey>> keysByType = new HashMap<>(); |
|||
keys.forEach(key -> keysByType.computeIfAbsent(key.getType(), k -> new ArrayList<>()).add(key)); |
|||
return keysByType; |
|||
} |
|||
|
|||
protected List<TbSubscription> addSubscriptions(EntityData entityData, Map<EntityKeyType, List<EntityKey>> keysByType, boolean resultToLatestValues) { |
|||
List<TbSubscription> subscriptionList = new ArrayList<>(); |
|||
keysByType.forEach((keysType, keysList) -> { |
|||
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); |
|||
subToEntityIdMap.put(subIdx, entityData.getEntityId()); |
|||
switch (keysType) { |
|||
case TIME_SERIES: |
|||
subscriptionList.add(createTsSub(entityData, subIdx, keysList, resultToLatestValues)); |
|||
break; |
|||
case CLIENT_ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.CLIENT_SCOPE, keysList)); |
|||
break; |
|||
case SHARED_ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.SHARED_SCOPE, keysList)); |
|||
break; |
|||
case SERVER_ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.SERVER_SCOPE, keysList)); |
|||
break; |
|||
case ATTRIBUTE: |
|||
subscriptionList.add(createAttrSub(entityData, subIdx, keysType, TbAttributeSubscriptionScope.ANY_SCOPE, keysList)); |
|||
break; |
|||
} |
|||
}); |
|||
return subscriptionList; |
|||
} |
|||
|
|||
private TbSubscription createAttrSub(EntityData entityData, int subIdx, EntityKeyType keysType, TbAttributeSubscriptionScope scope, List<EntityKey> subKeys) { |
|||
Map<String, Long> keyStates = buildKeyStats(entityData, keysType, subKeys); |
|||
log.trace("[{}][{}][{}] Creating attributes subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates); |
|||
return TbAttributeSubscription.builder() |
|||
.serviceId(serviceId) |
|||
.sessionId(sessionRef.getSessionId()) |
|||
.subscriptionId(subIdx) |
|||
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
|||
.entityId(entityData.getEntityId()) |
|||
.updateConsumer((s, subscriptionUpdate) -> sendWsMsg(s, subscriptionUpdate, keysType)) |
|||
.allKeys(false) |
|||
.keyStates(keyStates) |
|||
.scope(scope) |
|||
.build(); |
|||
} |
|||
|
|||
private TbSubscription createTsSub(EntityData entityData, int subIdx, List<EntityKey> subKeys, boolean resultToLatestValues) { |
|||
Map<String, Long> keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys); |
|||
if (entityData.getTimeseries() != null) { |
|||
entityData.getTimeseries().forEach((k, v) -> { |
|||
long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L); |
|||
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, ts); |
|||
keyStates.put(k, ts); |
|||
}); |
|||
} |
|||
log.trace("[{}][{}][{}] Creating time-series subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates); |
|||
return TbTimeseriesSubscription.builder() |
|||
.serviceId(serviceId) |
|||
.sessionId(sessionRef.getSessionId()) |
|||
.subscriptionId(subIdx) |
|||
.tenantId(sessionRef.getSecurityCtx().getTenantId()) |
|||
.entityId(entityData.getEntityId()) |
|||
.updateConsumer((sessionId, subscriptionUpdate) -> sendWsMsg(sessionId, subscriptionUpdate, EntityKeyType.TIME_SERIES, resultToLatestValues)) |
|||
.allKeys(false) |
|||
.keyStates(keyStates) |
|||
.build(); |
|||
} |
|||
|
|||
private void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) { |
|||
sendWsMsg(sessionId, subscriptionUpdate, keyType, true); |
|||
} |
|||
|
|||
private Map<String, Long> buildKeyStats(EntityData entityData, EntityKeyType keysType, List<EntityKey> subKeys) { |
|||
Map<String, Long> keyStates = new HashMap<>(); |
|||
subKeys.forEach(key -> keyStates.put(key.getKey(), 0L)); |
|||
if (entityData.getLatest() != null) { |
|||
Map<String, TsValue> currentValues = entityData.getLatest().get(keysType); |
|||
if (currentValues != null) { |
|||
currentValues.forEach((k, v) -> { |
|||
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs()); |
|||
keyStates.put(k, v.getTs()); |
|||
}); |
|||
} |
|||
} |
|||
return keyStates; |
|||
} |
|||
|
|||
abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues); |
|||
|
|||
@Data |
|||
private static class DynamicValueKey { |
|||
@Getter |
|||
private final FilterPredicateType predicateType; |
|||
@Getter |
|||
private final DynamicValueSourceType sourceType; |
|||
@Getter |
|||
private final String sourceAttribute; |
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue