Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Yassmine Mestiri
thingsboard
Commits
98ad3b98
Commit
98ad3b98
authored
Mar 10, 2023
by
Yassmine Mestiri
Browse files
iot
parent
aea55d6d
Pipeline
#2009
failed with stages
in 0 seconds
Changes
273
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Too many changes to show.
To preserve performance only
20 of 273+
files are displayed.
Plain diff
Email patch
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallConfiguration.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.install
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Profile
;
import
org.thingsboard.server.dao.audit.AuditLogLevelFilter
;
import
org.thingsboard.server.dao.audit.AuditLogLevelProperties
;
import
java.util.HashMap
;
@Configuration
@Profile
(
"install"
)
public
class
ThingsboardInstallConfiguration
{
@Bean
public
AuditLogLevelFilter
emptyAuditLogLevelFilter
()
{
var
props
=
new
AuditLogLevelProperties
();
props
.
setMask
(
new
HashMap
<>());
return
new
AuditLogLevelFilter
(
props
);
}
}
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallException.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.install
;
import
org.springframework.boot.ExitCodeGenerator
;
public
class
ThingsboardInstallException
extends
RuntimeException
implements
ExitCodeGenerator
{
public
ThingsboardInstallException
(
String
message
,
Throwable
cause
)
{
super
(
message
,
cause
);
}
public
int
getExitCode
()
{
return
1
;
}
}
\ No newline at end of file
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.install
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.annotation.Profile
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.server.service.component.ComponentDiscoveryService
;
import
org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService
;
import
org.thingsboard.server.service.install.DatabaseTsUpgradeService
;
import
org.thingsboard.server.service.install.EntityDatabaseSchemaService
;
import
org.thingsboard.server.service.install.NoSqlKeyspaceService
;
import
org.thingsboard.server.service.install.SystemDataLoaderService
;
import
org.thingsboard.server.service.install.TsDatabaseSchemaService
;
import
org.thingsboard.server.service.install.TsLatestDatabaseSchemaService
;
import
org.thingsboard.server.service.install.migrate.EntitiesMigrateService
;
import
org.thingsboard.server.service.install.migrate.TsLatestMigrateService
;
import
org.thingsboard.server.service.install.update.CacheCleanupService
;
import
org.thingsboard.server.service.install.update.DataUpdateService
;
@Service
@Profile
(
"install"
)
@Slf4j
public
class
ThingsboardInstallService
{
@Value
(
"${install.upgrade:false}"
)
private
Boolean
isUpgrade
;
@Value
(
"${install.upgrade.from_version:1.2.3}"
)
private
String
upgradeFromVersion
;
@Value
(
"${install.load_demo:false}"
)
private
Boolean
loadDemo
;
@Autowired
private
EntityDatabaseSchemaService
entityDatabaseSchemaService
;
@Autowired
(
required
=
false
)
private
NoSqlKeyspaceService
noSqlKeyspaceService
;
@Autowired
private
TsDatabaseSchemaService
tsDatabaseSchemaService
;
@Autowired
(
required
=
false
)
private
TsLatestDatabaseSchemaService
tsLatestDatabaseSchemaService
;
@Autowired
private
DatabaseEntitiesUpgradeService
databaseEntitiesUpgradeService
;
@Autowired
(
required
=
false
)
private
DatabaseTsUpgradeService
databaseTsUpgradeService
;
@Autowired
private
ComponentDiscoveryService
componentDiscoveryService
;
@Autowired
private
ApplicationContext
context
;
@Autowired
private
SystemDataLoaderService
systemDataLoaderService
;
@Autowired
private
DataUpdateService
dataUpdateService
;
@Autowired
private
CacheCleanupService
cacheCleanupService
;
@Autowired
(
required
=
false
)
private
EntitiesMigrateService
entitiesMigrateService
;
@Autowired
(
required
=
false
)
private
TsLatestMigrateService
latestMigrateService
;
public
void
performInstall
()
{
try
{
if
(
isUpgrade
)
{
log
.
info
(
"Starting ThingsBoard Upgrade from version {} ..."
,
upgradeFromVersion
);
cacheCleanupService
.
clearCache
(
upgradeFromVersion
);
if
(
"2.5.0-cassandra"
.
equals
(
upgradeFromVersion
))
{
log
.
info
(
"Migrating ThingsBoard entities data from cassandra to SQL database ..."
);
entitiesMigrateService
.
migrate
();
log
.
info
(
"Updating system data..."
);
systemDataLoaderService
.
updateSystemWidgets
();
}
else
if
(
"3.0.1-cassandra"
.
equals
(
upgradeFromVersion
))
{
log
.
info
(
"Migrating ThingsBoard latest timeseries data from cassandra to SQL database ..."
);
latestMigrateService
.
migrate
();
}
else
{
switch
(
upgradeFromVersion
)
{
case
"1.2.3"
:
//NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log
.
info
(
"Upgrading ThingsBoard from version 1.2.3 to 1.3.0 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"1.2.3"
);
case
"1.3.0"
:
//NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log
.
info
(
"Upgrading ThingsBoard from version 1.3.0 to 1.3.1 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"1.3.0"
);
case
"1.3.1"
:
//NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
log
.
info
(
"Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"1.3.1"
);
case
"1.4.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 1.4.0 to 2.0.0 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"1.4.0"
);
dataUpdateService
.
updateData
(
"1.4.0"
);
case
"2.0.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.0.0"
);
case
"2.1.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.1.1"
);
case
"2.1.3"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.1.3 to 2.2.0 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.1.3"
);
case
"2.3.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.3.0 to 2.3.1 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.3.0"
);
case
"2.3.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.3.1 to 2.4.0 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.3.1"
);
case
"2.4.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ..."
);
case
"2.4.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.4.1"
);
case
"2.4.2"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.4.2 to 2.4.3 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.4.2"
);
case
"2.4.3"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."
);
if
(
databaseTsUpgradeService
!=
null
)
{
databaseTsUpgradeService
.
upgradeDatabase
(
"2.4.3"
);
}
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"2.4.3"
);
case
"2.5.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.5.0 to 2.5.1 ..."
);
if
(
databaseTsUpgradeService
!=
null
)
{
databaseTsUpgradeService
.
upgradeDatabase
(
"2.5.0"
);
}
case
"2.5.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 2.5.1 to 3.0.0 ..."
);
case
"3.0.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.0.1 to 3.1.0 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.0.1"
);
dataUpdateService
.
updateData
(
"3.0.1"
);
case
"3.1.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.1.0 to 3.1.1 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.1.0"
);
case
"3.1.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.1.1 to 3.2.0 ..."
);
if
(
databaseTsUpgradeService
!=
null
)
{
databaseTsUpgradeService
.
upgradeDatabase
(
"3.1.1"
);
}
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.1.1"
);
dataUpdateService
.
updateData
(
"3.1.1"
);
systemDataLoaderService
.
createOAuth2Templates
();
case
"3.2.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.2.0"
);
case
"3.2.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.2.1 to 3.2.2 ..."
);
if
(
databaseTsUpgradeService
!=
null
)
{
databaseTsUpgradeService
.
upgradeDatabase
(
"3.2.1"
);
}
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.2.1"
);
case
"3.2.2"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ..."
);
if
(
databaseTsUpgradeService
!=
null
)
{
databaseTsUpgradeService
.
upgradeDatabase
(
"3.2.2"
);
}
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.2.2"
);
dataUpdateService
.
updateData
(
"3.2.2"
);
systemDataLoaderService
.
createOAuth2Templates
();
case
"3.3.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ..."
);
case
"3.3.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ..."
);
case
"3.3.2"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.3.2"
);
dataUpdateService
.
updateData
(
"3.3.2"
);
case
"3.3.3"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.3.3 to 3.3.4 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.3.3"
);
case
"3.3.4"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.3.4 to 3.4.0 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.3.4"
);
dataUpdateService
.
updateData
(
"3.3.4"
);
case
"3.4.0"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.4.0 to 3.4.1 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.4.0"
);
dataUpdateService
.
updateData
(
"3.4.0"
);
case
"3.4.1"
:
log
.
info
(
"Upgrading ThingsBoard from version 3.4.1 to 3.4.2 ..."
);
databaseEntitiesUpgradeService
.
upgradeDatabase
(
"3.4.1"
);
dataUpdateService
.
updateData
(
"3.4.1"
);
log
.
info
(
"Updating system data..."
);
systemDataLoaderService
.
updateSystemWidgets
();
break
;
//TODO update CacheCleanupService on the next version upgrade
default
:
throw
new
RuntimeException
(
"Unable to upgrade ThingsBoard, unsupported fromVersion: "
+
upgradeFromVersion
);
}
}
log
.
info
(
"Upgrade finished successfully!"
);
}
else
{
log
.
info
(
"Starting ThingsBoard Installation..."
);
log
.
info
(
"Installing DataBase schema for entities..."
);
entityDatabaseSchemaService
.
createDatabaseSchema
();
log
.
info
(
"Installing DataBase schema for timeseries..."
);
if
(
noSqlKeyspaceService
!=
null
)
{
noSqlKeyspaceService
.
createDatabaseSchema
();
}
tsDatabaseSchemaService
.
createDatabaseSchema
();
if
(
tsLatestDatabaseSchemaService
!=
null
)
{
tsLatestDatabaseSchemaService
.
createDatabaseSchema
();
}
log
.
info
(
"Loading system data..."
);
componentDiscoveryService
.
discoverComponents
();
systemDataLoaderService
.
createSysAdmin
();
systemDataLoaderService
.
createDefaultTenantProfiles
();
systemDataLoaderService
.
createAdminSettings
();
systemDataLoaderService
.
createRandomJwtSettings
();
systemDataLoaderService
.
loadSystemWidgets
();
systemDataLoaderService
.
createOAuth2Templates
();
systemDataLoaderService
.
createQueues
();
// systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules();
if
(
loadDemo
)
{
log
.
info
(
"Loading demo data..."
);
systemDataLoaderService
.
loadDemoData
();
}
log
.
info
(
"Installation finished successfully!"
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"Unexpected error during ThingsBoard installation!"
,
e
);
throw
new
ThingsboardInstallException
(
"Unexpected error during ThingsBoard installation!"
,
e
);
}
finally
{
SpringApplication
.
exit
(
context
);
}
}
}
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.action
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.node.ArrayNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.common.util.JacksonUtil
;
import
org.thingsboard.server.cluster.TbClusterService
;
import
org.thingsboard.server.common.data.DataConstants
;
import
org.thingsboard.server.common.data.EntityType
;
import
org.thingsboard.server.common.data.HasName
;
import
org.thingsboard.server.common.data.HasTenantId
;
import
org.thingsboard.server.common.data.StringUtils
;
import
org.thingsboard.server.common.data.User
;
import
org.thingsboard.server.common.data.audit.ActionType
;
import
org.thingsboard.server.common.data.edge.EdgeEventActionType
;
import
org.thingsboard.server.common.data.id.CustomerId
;
import
org.thingsboard.server.common.data.id.EntityId
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.kv.AttributeKvEntry
;
import
org.thingsboard.server.common.data.kv.TsKvEntry
;
import
org.thingsboard.server.common.data.relation.EntityRelation
;
import
org.thingsboard.server.common.msg.TbMsg
;
import
org.thingsboard.server.common.msg.TbMsgDataType
;
import
org.thingsboard.server.common.msg.TbMsgMetaData
;
import
org.thingsboard.server.dao.audit.AuditLogService
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
@Service
@RequiredArgsConstructor
@Slf4j
public
class
EntityActionService
{
private
final
TbClusterService
tbClusterService
;
private
final
AuditLogService
auditLogService
;
private
static
final
ObjectMapper
json
=
new
ObjectMapper
();
public
void
pushEntityActionToRuleEngine
(
EntityId
entityId
,
HasName
entity
,
TenantId
tenantId
,
CustomerId
customerId
,
ActionType
actionType
,
User
user
,
Object
...
additionalInfo
)
{
String
msgType
=
null
;
switch
(
actionType
)
{
case
ADDED:
msgType
=
DataConstants
.
ENTITY_CREATED
;
break
;
case
DELETED:
msgType
=
DataConstants
.
ENTITY_DELETED
;
break
;
case
UPDATED:
msgType
=
DataConstants
.
ENTITY_UPDATED
;
break
;
case
ASSIGNED_TO_CUSTOMER:
msgType
=
DataConstants
.
ENTITY_ASSIGNED
;
break
;
case
UNASSIGNED_FROM_CUSTOMER:
msgType
=
DataConstants
.
ENTITY_UNASSIGNED
;
break
;
case
ATTRIBUTES_UPDATED:
msgType
=
DataConstants
.
ATTRIBUTES_UPDATED
;
break
;
case
ATTRIBUTES_DELETED:
msgType
=
DataConstants
.
ATTRIBUTES_DELETED
;
break
;
case
ALARM_ACK:
msgType
=
DataConstants
.
ALARM_ACK
;
break
;
case
ALARM_CLEAR:
msgType
=
DataConstants
.
ALARM_CLEAR
;
break
;
case
ALARM_DELETE:
msgType
=
DataConstants
.
ALARM_DELETE
;
break
;
case
ASSIGNED_FROM_TENANT:
msgType
=
DataConstants
.
ENTITY_ASSIGNED_FROM_TENANT
;
break
;
case
ASSIGNED_TO_TENANT:
msgType
=
DataConstants
.
ENTITY_ASSIGNED_TO_TENANT
;
break
;
case
PROVISION_SUCCESS:
msgType
=
DataConstants
.
PROVISION_SUCCESS
;
break
;
case
PROVISION_FAILURE:
msgType
=
DataConstants
.
PROVISION_FAILURE
;
break
;
case
TIMESERIES_UPDATED:
msgType
=
DataConstants
.
TIMESERIES_UPDATED
;
break
;
case
TIMESERIES_DELETED:
msgType
=
DataConstants
.
TIMESERIES_DELETED
;
break
;
case
ASSIGNED_TO_EDGE:
msgType
=
DataConstants
.
ENTITY_ASSIGNED_TO_EDGE
;
break
;
case
UNASSIGNED_FROM_EDGE:
msgType
=
DataConstants
.
ENTITY_UNASSIGNED_FROM_EDGE
;
break
;
case
RELATION_ADD_OR_UPDATE:
msgType
=
DataConstants
.
RELATION_ADD_OR_UPDATE
;
break
;
case
RELATION_DELETED:
msgType
=
DataConstants
.
RELATION_DELETED
;
break
;
case
RELATIONS_DELETED:
msgType
=
DataConstants
.
RELATIONS_DELETED
;
break
;
}
if
(!
StringUtils
.
isEmpty
(
msgType
))
{
try
{
TbMsgMetaData
metaData
=
new
TbMsgMetaData
();
if
(
user
!=
null
)
{
metaData
.
putValue
(
"userId"
,
user
.
getId
().
toString
());
metaData
.
putValue
(
"userName"
,
user
.
getName
());
}
if
(
customerId
!=
null
&&
!
customerId
.
isNullUid
())
{
metaData
.
putValue
(
"customerId"
,
customerId
.
toString
());
}
if
(
actionType
==
ActionType
.
ASSIGNED_TO_CUSTOMER
)
{
String
strCustomerId
=
extractParameter
(
String
.
class
,
1
,
additionalInfo
);
String
strCustomerName
=
extractParameter
(
String
.
class
,
2
,
additionalInfo
);
metaData
.
putValue
(
"assignedCustomerId"
,
strCustomerId
);
metaData
.
putValue
(
"assignedCustomerName"
,
strCustomerName
);
}
else
if
(
actionType
==
ActionType
.
UNASSIGNED_FROM_CUSTOMER
)
{
String
strCustomerId
=
extractParameter
(
String
.
class
,
1
,
additionalInfo
);
String
strCustomerName
=
extractParameter
(
String
.
class
,
2
,
additionalInfo
);
metaData
.
putValue
(
"unassignedCustomerId"
,
strCustomerId
);
metaData
.
putValue
(
"unassignedCustomerName"
,
strCustomerName
);
}
else
if
(
actionType
==
ActionType
.
ASSIGNED_FROM_TENANT
)
{
String
strTenantId
=
extractParameter
(
String
.
class
,
0
,
additionalInfo
);
String
strTenantName
=
extractParameter
(
String
.
class
,
1
,
additionalInfo
);
metaData
.
putValue
(
"assignedFromTenantId"
,
strTenantId
);
metaData
.
putValue
(
"assignedFromTenantName"
,
strTenantName
);
}
else
if
(
actionType
==
ActionType
.
ASSIGNED_TO_TENANT
)
{
String
strTenantId
=
extractParameter
(
String
.
class
,
0
,
additionalInfo
);
String
strTenantName
=
extractParameter
(
String
.
class
,
1
,
additionalInfo
);
metaData
.
putValue
(
"assignedToTenantId"
,
strTenantId
);
metaData
.
putValue
(
"assignedToTenantName"
,
strTenantName
);
}
else
if
(
actionType
==
ActionType
.
ASSIGNED_TO_EDGE
)
{
String
strEdgeId
=
extractParameter
(
String
.
class
,
1
,
additionalInfo
);
String
strEdgeName
=
extractParameter
(
String
.
class
,
2
,
additionalInfo
);
metaData
.
putValue
(
"assignedEdgeId"
,
strEdgeId
);
metaData
.
putValue
(
"assignedEdgeName"
,
strEdgeName
);
}
else
if
(
actionType
==
ActionType
.
UNASSIGNED_FROM_EDGE
)
{
String
strEdgeId
=
extractParameter
(
String
.
class
,
1
,
additionalInfo
);
String
strEdgeName
=
extractParameter
(
String
.
class
,
2
,
additionalInfo
);
metaData
.
putValue
(
"unassignedEdgeId"
,
strEdgeId
);
metaData
.
putValue
(
"unassignedEdgeName"
,
strEdgeName
);
}
ObjectNode
entityNode
;
if
(
entity
!=
null
)
{
entityNode
=
json
.
valueToTree
(
entity
);
if
(
entityId
.
getEntityType
()
==
EntityType
.
DASHBOARD
)
{
entityNode
.
put
(
"configuration"
,
""
);
}
}
else
{
entityNode
=
json
.
createObjectNode
();
if
(
actionType
==
ActionType
.
ATTRIBUTES_UPDATED
)
{
String
scope
=
extractParameter
(
String
.
class
,
0
,
additionalInfo
);
@SuppressWarnings
(
"unchecked"
)
List
<
AttributeKvEntry
>
attributes
=
extractParameter
(
List
.
class
,
1
,
additionalInfo
);
metaData
.
putValue
(
DataConstants
.
SCOPE
,
scope
);
if
(
attributes
!=
null
)
{
for
(
AttributeKvEntry
attr
:
attributes
)
{
JacksonUtil
.
addKvEntry
(
entityNode
,
attr
);
}
}
}
else
if
(
actionType
==
ActionType
.
ATTRIBUTES_DELETED
)
{
String
scope
=
extractParameter
(
String
.
class
,
0
,
additionalInfo
);
@SuppressWarnings
(
"unchecked"
)
List
<
String
>
keys
=
extractParameter
(
List
.
class
,
1
,
additionalInfo
);
metaData
.
putValue
(
DataConstants
.
SCOPE
,
scope
);
ArrayNode
attrsArrayNode
=
entityNode
.
putArray
(
"attributes"
);
if
(
keys
!=
null
)
{
keys
.
forEach
(
attrsArrayNode:
:
add
);
}
}
else
if
(
actionType
==
ActionType
.
TIMESERIES_UPDATED
)
{
@SuppressWarnings
(
"unchecked"
)
List
<
TsKvEntry
>
timeseries
=
extractParameter
(
List
.
class
,
0
,
additionalInfo
);
addTimeseries
(
entityNode
,
timeseries
);
}
else
if
(
actionType
==
ActionType
.
TIMESERIES_DELETED
)
{
@SuppressWarnings
(
"unchecked"
)
List
<
String
>
keys
=
extractParameter
(
List
.
class
,
0
,
additionalInfo
);
if
(
keys
!=
null
)
{
ArrayNode
timeseriesArrayNode
=
entityNode
.
putArray
(
"timeseries"
);
keys
.
forEach
(
timeseriesArrayNode:
:
add
);
}
entityNode
.
put
(
"startTs"
,
extractParameter
(
Long
.
class
,
1
,
additionalInfo
));
entityNode
.
put
(
"endTs"
,
extractParameter
(
Long
.
class
,
2
,
additionalInfo
));
}
else
if
(
ActionType
.
RELATION_ADD_OR_UPDATE
.
equals
(
actionType
)
||
ActionType
.
RELATION_DELETED
.
equals
(
actionType
))
{
entityNode
=
json
.
valueToTree
(
extractParameter
(
EntityRelation
.
class
,
0
,
additionalInfo
));
}
}
TbMsg
tbMsg
=
TbMsg
.
newMsg
(
msgType
,
entityId
,
customerId
,
metaData
,
TbMsgDataType
.
JSON
,
json
.
writeValueAsString
(
entityNode
));
if
(
tenantId
==
null
||
tenantId
.
isNullUid
())
{
if
(
entity
instanceof
HasTenantId
)
{
tenantId
=
((
HasTenantId
)
entity
).
getTenantId
();
}
}
tbClusterService
.
pushMsgToRuleEngine
(
tenantId
,
entityId
,
tbMsg
,
null
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"[{}] Failed to push entity action to rule engine: {}"
,
entityId
,
actionType
,
e
);
}
}
}
public
<
E
extends
HasName
,
I
extends
EntityId
>
void
logEntityAction
(
User
user
,
I
entityId
,
E
entity
,
CustomerId
customerId
,
ActionType
actionType
,
Exception
e
,
Object
...
additionalInfo
)
{
if
(
customerId
==
null
||
customerId
.
isNullUid
())
{
customerId
=
user
.
getCustomerId
();
}
if
(
e
==
null
)
{
pushEntityActionToRuleEngine
(
entityId
,
entity
,
user
.
getTenantId
(),
customerId
,
actionType
,
user
,
additionalInfo
);
}
auditLogService
.
logEntityAction
(
user
.
getTenantId
(),
customerId
,
user
.
getId
(),
user
.
getName
(),
entityId
,
entity
,
actionType
,
e
,
additionalInfo
);
}
public
void
sendEntityNotificationMsgToEdge
(
TenantId
tenantId
,
EntityId
entityId
,
EdgeEventActionType
action
)
{
tbClusterService
.
sendNotificationMsgToEdge
(
tenantId
,
null
,
entityId
,
null
,
null
,
action
);
}
private
<
T
>
T
extractParameter
(
Class
<
T
>
clazz
,
int
index
,
Object
...
additionalInfo
)
{
T
result
=
null
;
if
(
additionalInfo
!=
null
&&
additionalInfo
.
length
>
index
)
{
Object
paramObject
=
additionalInfo
[
index
];
if
(
clazz
.
isInstance
(
paramObject
))
{
result
=
clazz
.
cast
(
paramObject
);
}
}
return
result
;
}
private
void
addTimeseries
(
ObjectNode
entityNode
,
List
<
TsKvEntry
>
timeseries
)
throws
Exception
{
if
(
timeseries
!=
null
&&
!
timeseries
.
isEmpty
())
{
ArrayNode
result
=
entityNode
.
putArray
(
"timeseries"
);
Map
<
Long
,
List
<
TsKvEntry
>>
groupedTelemetry
=
timeseries
.
stream
()
.
collect
(
Collectors
.
groupingBy
(
TsKvEntry:
:
getTs
));
for
(
Map
.
Entry
<
Long
,
List
<
TsKvEntry
>>
entry
:
groupedTelemetry
.
entrySet
())
{
ObjectNode
element
=
json
.
createObjectNode
();
element
.
put
(
"ts"
,
entry
.
getKey
());
ObjectNode
values
=
element
.
putObject
(
"values"
);
for
(
TsKvEntry
tsKvEntry
:
entry
.
getValue
())
{
JacksonUtil
.
addKvEntry
(
values
,
tsKvEntry
);
}
result
.
add
(
element
);
}
}
}
}
application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.apiusage
;
import
lombok.Getter
;
import
org.springframework.data.util.Pair
;
import
org.thingsboard.server.common.data.ApiFeature
;
import
org.thingsboard.server.common.data.ApiUsageRecordKey
;
import
org.thingsboard.server.common.data.ApiUsageState
;
import
org.thingsboard.server.common.data.ApiUsageStateValue
;
import
org.thingsboard.server.common.data.EntityType
;
import
org.thingsboard.server.common.data.id.EntityId
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.msg.tools.SchedulerUtils
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.concurrent.ConcurrentHashMap
;
public
abstract
class
BaseApiUsageState
{
private
final
Map
<
ApiUsageRecordKey
,
Long
>
currentCycleValues
=
new
ConcurrentHashMap
<>();
private
final
Map
<
ApiUsageRecordKey
,
Long
>
currentHourValues
=
new
ConcurrentHashMap
<>();
@Getter
private
final
ApiUsageState
apiUsageState
;
@Getter
private
volatile
long
currentCycleTs
;
@Getter
private
volatile
long
nextCycleTs
;
@Getter
private
volatile
long
currentHourTs
;
public
BaseApiUsageState
(
ApiUsageState
apiUsageState
)
{
this
.
apiUsageState
=
apiUsageState
;
this
.
currentCycleTs
=
SchedulerUtils
.
getStartOfCurrentMonth
();
this
.
nextCycleTs
=
SchedulerUtils
.
getStartOfNextMonth
();
this
.
currentHourTs
=
SchedulerUtils
.
getStartOfCurrentHour
();
}
public
void
put
(
ApiUsageRecordKey
key
,
Long
value
)
{
currentCycleValues
.
put
(
key
,
value
);
}
public
void
putHourly
(
ApiUsageRecordKey
key
,
Long
value
)
{
currentHourValues
.
put
(
key
,
value
);
}
public
long
add
(
ApiUsageRecordKey
key
,
long
value
)
{
long
result
=
currentCycleValues
.
getOrDefault
(
key
,
0L
)
+
value
;
currentCycleValues
.
put
(
key
,
result
);
return
result
;
}
public
long
get
(
ApiUsageRecordKey
key
)
{
return
currentCycleValues
.
getOrDefault
(
key
,
0L
);
}
public
long
addToHourly
(
ApiUsageRecordKey
key
,
long
value
)
{
long
result
=
currentHourValues
.
getOrDefault
(
key
,
0L
)
+
value
;
currentHourValues
.
put
(
key
,
result
);
return
result
;
}
public
void
setHour
(
long
currentHourTs
)
{
this
.
currentHourTs
=
currentHourTs
;
for
(
ApiUsageRecordKey
key
:
ApiUsageRecordKey
.
values
())
{
currentHourValues
.
put
(
key
,
0L
);
}
}
public
void
setCycles
(
long
currentCycleTs
,
long
nextCycleTs
)
{
this
.
currentCycleTs
=
currentCycleTs
;
this
.
nextCycleTs
=
nextCycleTs
;
for
(
ApiUsageRecordKey
key
:
ApiUsageRecordKey
.
values
())
{
currentCycleValues
.
put
(
key
,
0L
);
}
}
public
ApiUsageStateValue
getFeatureValue
(
ApiFeature
feature
)
{
switch
(
feature
)
{
case
TRANSPORT:
return
apiUsageState
.
getTransportState
();
case
RE:
return
apiUsageState
.
getReExecState
();
case
DB:
return
apiUsageState
.
getDbStorageState
();
case
JS:
return
apiUsageState
.
getJsExecState
();
case
EMAIL:
return
apiUsageState
.
getEmailExecState
();
case
SMS:
return
apiUsageState
.
getSmsExecState
();
case
ALARM:
return
apiUsageState
.
getAlarmExecState
();
default
:
return
ApiUsageStateValue
.
ENABLED
;
}
}
public
boolean
setFeatureValue
(
ApiFeature
feature
,
ApiUsageStateValue
value
)
{
ApiUsageStateValue
currentValue
=
getFeatureValue
(
feature
);
switch
(
feature
)
{
case
TRANSPORT:
apiUsageState
.
setTransportState
(
value
);
break
;
case
RE:
apiUsageState
.
setReExecState
(
value
);
break
;
case
DB:
apiUsageState
.
setDbStorageState
(
value
);
break
;
case
JS:
apiUsageState
.
setJsExecState
(
value
);
break
;
case
EMAIL:
apiUsageState
.
setEmailExecState
(
value
);
break
;
case
SMS:
apiUsageState
.
setSmsExecState
(
value
);
break
;
case
ALARM:
apiUsageState
.
setAlarmExecState
(
value
);
break
;
}
return
!
currentValue
.
equals
(
value
);
}
public
abstract
EntityType
getEntityType
();
public
TenantId
getTenantId
()
{
return
getApiUsageState
().
getTenantId
();
}
public
EntityId
getEntityId
()
{
return
getApiUsageState
().
getEntityId
();
}
}
application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.apiusage
;
import
org.thingsboard.server.common.data.ApiUsageState
;
import
org.thingsboard.server.common.data.EntityType
;
public
class
CustomerApiUsageState
extends
BaseApiUsageState
{
public
CustomerApiUsageState
(
ApiUsageState
apiUsageState
)
{
super
(
apiUsageState
);
}
@Override
public
EntityType
getEntityType
()
{
return
EntityType
.
CUSTOMER
;
}
}
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.apiusage
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.server.common.data.StringUtils
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration
;
import
org.thingsboard.server.common.msg.tools.TbRateLimits
;
import
org.thingsboard.server.dao.tenant.TbTenantProfileCache
;
import
java.util.Map
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.function.Function
;
@Service
@RequiredArgsConstructor
public
class
DefaultRateLimitService
implements
RateLimitService
{
private
final
TbTenantProfileCache
tenantProfileCache
;
private
final
Map
<
String
,
Map
<
TenantId
,
TbRateLimits
>>
rateLimits
=
new
ConcurrentHashMap
<>();
@Override
public
boolean
checkEntityExportLimit
(
TenantId
tenantId
)
{
return
checkLimit
(
tenantId
,
"entityExport"
,
DefaultTenantProfileConfiguration:
:
getTenantEntityExportRateLimit
);
}
@Override
public
boolean
checkEntityImportLimit
(
TenantId
tenantId
)
{
return
checkLimit
(
tenantId
,
"entityImport"
,
DefaultTenantProfileConfiguration:
:
getTenantEntityImportRateLimit
);
}
private
boolean
checkLimit
(
TenantId
tenantId
,
String
rateLimitsKey
,
Function
<
DefaultTenantProfileConfiguration
,
String
>
rateLimitConfigExtractor
)
{
String
rateLimitConfig
=
tenantProfileCache
.
get
(
tenantId
).
getProfileConfiguration
()
.
map
(
rateLimitConfigExtractor
).
orElse
(
null
);
Map
<
TenantId
,
TbRateLimits
>
rateLimits
=
this
.
rateLimits
.
get
(
rateLimitsKey
);
if
(
StringUtils
.
isEmpty
(
rateLimitConfig
))
{
if
(
rateLimits
!=
null
)
{
rateLimits
.
remove
(
tenantId
);
if
(
rateLimits
.
isEmpty
())
{
this
.
rateLimits
.
remove
(
rateLimitsKey
);
}
}
return
true
;
}
if
(
rateLimits
==
null
)
{
rateLimits
=
new
ConcurrentHashMap
<>();
this
.
rateLimits
.
put
(
rateLimitsKey
,
rateLimits
);
}
TbRateLimits
rateLimit
=
rateLimits
.
get
(
tenantId
);
if
(
rateLimit
==
null
||
!
rateLimit
.
getConfiguration
().
equals
(
rateLimitConfig
))
{
rateLimit
=
new
TbRateLimits
(
rateLimitConfig
);
rateLimits
.
put
(
tenantId
,
rateLimit
);
}
return
rateLimit
.
tryConsume
();
}
}
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.apiusage
;
import
com.google.common.util.concurrent.FutureCallback
;
import
com.google.common.util.concurrent.ListenableFuture
;
import
lombok.extern.slf4j.Slf4j
;
import
org.checkerframework.checker.nullness.qual.Nullable
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.context.annotation.Lazy
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.common.util.ThingsBoardThreadFactory
;
import
org.thingsboard.rule.engine.api.MailService
;
import
org.thingsboard.server.cluster.TbClusterService
;
import
org.thingsboard.server.common.data.ApiFeature
;
import
org.thingsboard.server.common.data.ApiUsageRecordKey
;
import
org.thingsboard.server.common.data.ApiUsageState
;
import
org.thingsboard.server.common.data.ApiUsageStateMailMessage
;
import
org.thingsboard.server.common.data.ApiUsageStateValue
;
import
org.thingsboard.server.common.data.EntityType
;
import
org.thingsboard.server.common.data.StringUtils
;
import
org.thingsboard.server.common.data.Tenant
;
import
org.thingsboard.server.common.data.TenantProfile
;
import
org.thingsboard.server.common.data.exception.ThingsboardException
;
import
org.thingsboard.server.common.data.id.ApiUsageStateId
;
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.TenantProfileId
;
import
org.thingsboard.server.common.data.kv.BasicTsKvEntry
;
import
org.thingsboard.server.common.data.kv.LongDataEntry
;
import
org.thingsboard.server.common.data.kv.StringDataEntry
;
import
org.thingsboard.server.common.data.kv.TsKvEntry
;
import
org.thingsboard.server.common.data.page.PageDataIterable
;
import
org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration
;
import
org.thingsboard.server.common.data.tenant.profile.TenantProfileData
;
import
org.thingsboard.server.common.msg.queue.ServiceType
;
import
org.thingsboard.server.common.msg.queue.TbCallback
;
import
org.thingsboard.server.common.msg.queue.TopicPartitionInfo
;
import
org.thingsboard.server.common.msg.tools.SchedulerUtils
;
import
org.thingsboard.server.dao.tenant.TbTenantProfileCache
;
import
org.thingsboard.server.dao.tenant.TenantService
;
import
org.thingsboard.server.dao.timeseries.TimeseriesService
;
import
org.thingsboard.server.dao.usagerecord.ApiUsageStateService
;
import
org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg
;
import
org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto
;
import
org.thingsboard.server.queue.common.TbProtoQueueMsg
;
import
org.thingsboard.server.queue.discovery.PartitionService
;
import
org.thingsboard.server.service.executors.DbCallbackExecutorService
;
import
org.thingsboard.server.service.partition.AbstractPartitionBasedService
;
import
org.thingsboard.server.service.telemetry.InternalTelemetryService
;
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.HashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.UUID
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.concurrent.ExecutionException
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.TimeUnit
;
import
java.util.concurrent.locks.Lock
;
import
java.util.concurrent.locks.ReentrantLock
;
import
java.util.stream.Collectors
;
@Slf4j
@Service
public
class
DefaultTbApiUsageStateService
extends
AbstractPartitionBasedService
<
EntityId
>
implements
TbApiUsageStateService
{
public
static
final
String
HOURLY
=
"Hourly"
;
public
static
final
FutureCallback
<
Integer
>
VOID_CALLBACK
=
new
FutureCallback
<
Integer
>()
{
@Override
public
void
onSuccess
(
@Nullable
Integer
result
)
{
}
@Override
public
void
onFailure
(
Throwable
t
)
{
}
};
private
final
TbClusterService
clusterService
;
private
final
PartitionService
partitionService
;
private
final
TenantService
tenantService
;
private
final
TimeseriesService
tsService
;
private
final
ApiUsageStateService
apiUsageStateService
;
private
final
TbTenantProfileCache
tenantProfileCache
;
private
final
MailService
mailService
;
private
final
DbCallbackExecutorService
dbExecutor
;
@Lazy
@Autowired
private
InternalTelemetryService
tsWsService
;
// Entities that should be processed on this server
final
Map
<
EntityId
,
BaseApiUsageState
>
myUsageStates
=
new
ConcurrentHashMap
<>();
// Entities that should be processed on other servers
final
Map
<
EntityId
,
ApiUsageState
>
otherUsageStates
=
new
ConcurrentHashMap
<>();
final
Set
<
EntityId
>
deletedEntities
=
Collections
.
newSetFromMap
(
new
ConcurrentHashMap
<>());
@Value
(
"${usage.stats.report.enabled:true}"
)
private
boolean
enabled
;
@Value
(
"${usage.stats.check.cycle:60000}"
)
private
long
nextCycleCheckInterval
;
private
final
Lock
updateLock
=
new
ReentrantLock
();
private
final
ExecutorService
mailExecutor
;
public
DefaultTbApiUsageStateService
(
TbClusterService
clusterService
,
PartitionService
partitionService
,
TenantService
tenantService
,
TimeseriesService
tsService
,
ApiUsageStateService
apiUsageStateService
,
TbTenantProfileCache
tenantProfileCache
,
MailService
mailService
,
DbCallbackExecutorService
dbExecutor
)
{
this
.
clusterService
=
clusterService
;
this
.
partitionService
=
partitionService
;
this
.
tenantService
=
tenantService
;
this
.
tsService
=
tsService
;
this
.
apiUsageStateService
=
apiUsageStateService
;
this
.
tenantProfileCache
=
tenantProfileCache
;
this
.
mailService
=
mailService
;
this
.
mailExecutor
=
Executors
.
newSingleThreadExecutor
(
ThingsBoardThreadFactory
.
forName
(
"api-usage-svc-mail"
));
this
.
dbExecutor
=
dbExecutor
;
}
@PostConstruct
public
void
init
()
{
super
.
init
();
if
(
enabled
)
{
log
.
info
(
"Starting api usage service."
);
scheduledExecutor
.
scheduleAtFixedRate
(
this
::
checkStartOfNextCycle
,
nextCycleCheckInterval
,
nextCycleCheckInterval
,
TimeUnit
.
MILLISECONDS
);
log
.
info
(
"Started api usage service."
);
}
}
@Override
protected
String
getServiceName
()
{
return
"API Usage"
;
}
@Override
protected
String
getSchedulerExecutorName
()
{
return
"api-usage-scheduled"
;
}
@Override
public
void
process
(
TbProtoQueueMsg
<
ToUsageStatsServiceMsg
>
msg
,
TbCallback
callback
)
{
ToUsageStatsServiceMsg
statsMsg
=
msg
.
getValue
();
TenantId
tenantId
=
TenantId
.
fromUUID
(
new
UUID
(
statsMsg
.
getTenantIdMSB
(),
statsMsg
.
getTenantIdLSB
()));
EntityId
entityId
;
if
(
statsMsg
.
getCustomerIdMSB
()
!=
0
&&
statsMsg
.
getCustomerIdLSB
()
!=
0
)
{
entityId
=
new
CustomerId
(
new
UUID
(
statsMsg
.
getCustomerIdMSB
(),
statsMsg
.
getCustomerIdLSB
()));
}
else
{
entityId
=
tenantId
;
}
processEntityUsageStats
(
tenantId
,
entityId
,
statsMsg
.
getValuesList
());
callback
.
onSuccess
();
}
private
void
processEntityUsageStats
(
TenantId
tenantId
,
EntityId
entityId
,
List
<
UsageStatsKVProto
>
values
)
{
if
(
deletedEntities
.
contains
(
entityId
))
return
;
BaseApiUsageState
usageState
;
List
<
TsKvEntry
>
updatedEntries
;
Map
<
ApiFeature
,
ApiUsageStateValue
>
result
;
updateLock
.
lock
();
try
{
usageState
=
getOrFetchState
(
tenantId
,
entityId
);
long
ts
=
usageState
.
getCurrentCycleTs
();
long
hourTs
=
usageState
.
getCurrentHourTs
();
long
newHourTs
=
SchedulerUtils
.
getStartOfCurrentHour
();
if
(
newHourTs
!=
hourTs
)
{
usageState
.
setHour
(
newHourTs
);
}
updatedEntries
=
new
ArrayList
<>(
ApiUsageRecordKey
.
values
().
length
);
Set
<
ApiFeature
>
apiFeatures
=
new
HashSet
<>();
for
(
UsageStatsKVProto
kvProto
:
values
)
{
ApiUsageRecordKey
recordKey
=
ApiUsageRecordKey
.
valueOf
(
kvProto
.
getKey
());
long
newValue
=
usageState
.
add
(
recordKey
,
kvProto
.
getValue
());
updatedEntries
.
add
(
new
BasicTsKvEntry
(
ts
,
new
LongDataEntry
(
recordKey
.
getApiCountKey
(),
newValue
)));
long
newHourlyValue
=
usageState
.
addToHourly
(
recordKey
,
kvProto
.
getValue
());
updatedEntries
.
add
(
new
BasicTsKvEntry
(
newHourTs
,
new
LongDataEntry
(
recordKey
.
getApiCountKey
()
+
HOURLY
,
newHourlyValue
)));
apiFeatures
.
add
(
recordKey
.
getApiFeature
());
}
if
(
usageState
.
getEntityType
()
==
EntityType
.
TENANT
&&
!
usageState
.
getEntityId
().
equals
(
TenantId
.
SYS_TENANT_ID
))
{
result
=
((
TenantApiUsageState
)
usageState
).
checkStateUpdatedDueToThreshold
(
apiFeatures
);
}
else
{
result
=
Collections
.
emptyMap
();
}
}
finally
{
updateLock
.
unlock
();
}
tsWsService
.
saveAndNotifyInternal
(
tenantId
,
usageState
.
getApiUsageState
().
getId
(),
updatedEntries
,
VOID_CALLBACK
);
if
(!
result
.
isEmpty
())
{
persistAndNotify
(
usageState
,
result
);
}
}
@Override
public
ApiUsageState
getApiUsageState
(
TenantId
tenantId
)
{
TenantApiUsageState
tenantState
=
(
TenantApiUsageState
)
myUsageStates
.
get
(
tenantId
);
if
(
tenantState
!=
null
)
{
return
tenantState
.
getApiUsageState
();
}
else
{
ApiUsageState
state
=
otherUsageStates
.
get
(
tenantId
);
if
(
state
!=
null
)
{
return
state
;
}
else
{
if
(
partitionService
.
resolve
(
ServiceType
.
TB_CORE
,
tenantId
,
tenantId
).
isMyPartition
())
{
return
getOrFetchState
(
tenantId
,
tenantId
).
getApiUsageState
();
}
else
{
state
=
otherUsageStates
.
get
(
tenantId
);
if
(
state
==
null
)
{
state
=
apiUsageStateService
.
findTenantApiUsageState
(
tenantId
);
if
(
state
!=
null
)
{
otherUsageStates
.
put
(
tenantId
,
state
);
}
}
return
state
;
}
}
}
}
@Override
public
void
onApiUsageStateUpdate
(
TenantId
tenantId
)
{
otherUsageStates
.
remove
(
tenantId
);
}
@Override
public
void
onTenantProfileUpdate
(
TenantProfileId
tenantProfileId
)
{
log
.
info
(
"[{}] On Tenant Profile Update"
,
tenantProfileId
);
TenantProfile
tenantProfile
=
tenantProfileCache
.
get
(
tenantProfileId
);
updateLock
.
lock
();
try
{
myUsageStates
.
values
().
stream
()
.
filter
(
state
->
state
.
getEntityType
()
==
EntityType
.
TENANT
)
.
map
(
state
->
(
TenantApiUsageState
)
state
)
.
forEach
(
state
->
{
if
(
tenantProfile
.
getId
().
equals
(
state
.
getTenantProfileId
()))
{
updateTenantState
(
state
,
tenantProfile
);
}
});
}
finally
{
updateLock
.
unlock
();
}
}
@Override
public
void
onTenantUpdate
(
TenantId
tenantId
)
{
log
.
info
(
"[{}] On Tenant Update."
,
tenantId
);
TenantProfile
tenantProfile
=
tenantProfileCache
.
get
(
tenantId
);
updateLock
.
lock
();
try
{
TenantApiUsageState
state
=
(
TenantApiUsageState
)
myUsageStates
.
get
(
tenantId
);
if
(
state
!=
null
&&
!
state
.
getTenantProfileId
().
equals
(
tenantProfile
.
getId
()))
{
updateTenantState
(
state
,
tenantProfile
);
}
}
finally
{
updateLock
.
unlock
();
}
}
private
void
updateTenantState
(
TenantApiUsageState
state
,
TenantProfile
profile
)
{
TenantProfileData
oldProfileData
=
state
.
getTenantProfileData
();
state
.
setTenantProfileId
(
profile
.
getId
());
state
.
setTenantProfileData
(
profile
.
getProfileData
());
Map
<
ApiFeature
,
ApiUsageStateValue
>
result
=
state
.
checkStateUpdatedDueToThresholds
();
if
(!
result
.
isEmpty
())
{
persistAndNotify
(
state
,
result
);
}
updateProfileThresholds
(
state
.
getTenantId
(),
state
.
getApiUsageState
().
getId
(),
oldProfileData
.
getConfiguration
(),
profile
.
getProfileData
().
getConfiguration
());
}
private
void
addEntityState
(
TopicPartitionInfo
tpi
,
BaseApiUsageState
state
)
{
EntityId
entityId
=
state
.
getEntityId
();
Set
<
EntityId
>
entityIds
=
partitionedEntities
.
get
(
tpi
);
if
(
entityIds
!=
null
)
{
entityIds
.
add
(
entityId
);
myUsageStates
.
put
(
entityId
,
state
);
}
else
{
log
.
debug
(
"[{}] belongs to external partition {}"
,
entityId
,
tpi
.
getFullTopicName
());
throw
new
RuntimeException
(
entityId
.
getEntityType
()
+
" belongs to external partition "
+
tpi
.
getFullTopicName
()
+
"!"
);
}
}
private
void
updateProfileThresholds
(
TenantId
tenantId
,
ApiUsageStateId
id
,
TenantProfileConfiguration
oldData
,
TenantProfileConfiguration
newData
)
{
long
ts
=
System
.
currentTimeMillis
();
List
<
TsKvEntry
>
profileThresholds
=
new
ArrayList
<>();
for
(
ApiUsageRecordKey
key
:
ApiUsageRecordKey
.
values
())
{
long
newProfileThreshold
=
newData
.
getProfileThreshold
(
key
);
if
(
oldData
==
null
||
oldData
.
getProfileThreshold
(
key
)
!=
newProfileThreshold
)
{
log
.
info
(
"[{}] Updating profile threshold [{}]:[{}]"
,
tenantId
,
key
,
newProfileThreshold
);
profileThresholds
.
add
(
new
BasicTsKvEntry
(
ts
,
new
LongDataEntry
(
key
.
getApiLimitKey
(),
newProfileThreshold
)));
}
}
if
(!
profileThresholds
.
isEmpty
())
{
tsWsService
.
saveAndNotifyInternal
(
tenantId
,
id
,
profileThresholds
,
VOID_CALLBACK
);
}
}
public
void
onTenantDelete
(
TenantId
tenantId
)
{
deletedEntities
.
add
(
tenantId
);
myUsageStates
.
remove
(
tenantId
);
otherUsageStates
.
remove
(
tenantId
);
}
@Override
public
void
onCustomerDelete
(
CustomerId
customerId
)
{
deletedEntities
.
add
(
customerId
);
myUsageStates
.
remove
(
customerId
);
}
@Override
protected
void
cleanupEntityOnPartitionRemoval
(
EntityId
entityId
)
{
myUsageStates
.
remove
(
entityId
);
}
private
void
persistAndNotify
(
BaseApiUsageState
state
,
Map
<
ApiFeature
,
ApiUsageStateValue
>
result
)
{
log
.
info
(
"[{}] Detected update of the API state for {}: {}"
,
state
.
getEntityId
(),
state
.
getEntityType
(),
result
);
apiUsageStateService
.
update
(
state
.
getApiUsageState
());
clusterService
.
onApiStateChange
(
state
.
getApiUsageState
(),
null
);
long
ts
=
System
.
currentTimeMillis
();
List
<
TsKvEntry
>
stateTelemetry
=
new
ArrayList
<>();
result
.
forEach
((
apiFeature
,
aState
)
->
stateTelemetry
.
add
(
new
BasicTsKvEntry
(
ts
,
new
StringDataEntry
(
apiFeature
.
getApiStateKey
(),
aState
.
name
()))));
tsWsService
.
saveAndNotifyInternal
(
state
.
getTenantId
(),
state
.
getApiUsageState
().
getId
(),
stateTelemetry
,
VOID_CALLBACK
);
if
(
state
.
getEntityType
()
==
EntityType
.
TENANT
&&
!
state
.
getEntityId
().
equals
(
TenantId
.
SYS_TENANT_ID
))
{
String
email
=
tenantService
.
findTenantById
(
state
.
getTenantId
()).
getEmail
();
if
(
StringUtils
.
isNotEmpty
(
email
))
{
result
.
forEach
((
apiFeature
,
stateValue
)
->
{
mailExecutor
.
submit
(()
->
{
try
{
mailService
.
sendApiFeatureStateEmail
(
apiFeature
,
stateValue
,
email
,
createStateMailMessage
((
TenantApiUsageState
)
state
,
apiFeature
,
stateValue
));
}
catch
(
ThingsboardException
e
)
{
log
.
warn
(
"[{}] Can't send update of the API state to tenant with provided email [{}]"
,
state
.
getTenantId
(),
email
,
e
);
}
});
});
}
else
{
log
.
warn
(
"[{}] Can't send update of the API state to tenant with empty email!"
,
state
.
getTenantId
());
}
}
}
private
ApiUsageStateMailMessage
createStateMailMessage
(
TenantApiUsageState
state
,
ApiFeature
apiFeature
,
ApiUsageStateValue
stateValue
)
{
StateChecker
checker
=
getStateChecker
(
stateValue
);
for
(
ApiUsageRecordKey
apiUsageRecordKey
:
ApiUsageRecordKey
.
getKeys
(
apiFeature
))
{
long
threshold
=
state
.
getProfileThreshold
(
apiUsageRecordKey
);
long
warnThreshold
=
state
.
getProfileWarnThreshold
(
apiUsageRecordKey
);
long
value
=
state
.
get
(
apiUsageRecordKey
);
if
(
checker
.
check
(
threshold
,
warnThreshold
,
value
))
{
return
new
ApiUsageStateMailMessage
(
apiUsageRecordKey
,
threshold
,
value
);
}
}
return
null
;
}
private
StateChecker
getStateChecker
(
ApiUsageStateValue
stateValue
)
{
if
(
ApiUsageStateValue
.
ENABLED
.
equals
(
stateValue
))
{
return
(
t
,
wt
,
v
)
->
true
;
}
else
if
(
ApiUsageStateValue
.
WARNING
.
equals
(
stateValue
))
{
return
(
t
,
wt
,
v
)
->
v
<
t
&&
v
>=
wt
;
}
else
{
return
(
t
,
wt
,
v
)
->
v
>=
t
;
}
}
@Override
public
ApiUsageState
findApiUsageStateById
(
TenantId
tenantId
,
ApiUsageStateId
id
)
{
return
apiUsageStateService
.
findApiUsageStateById
(
tenantId
,
id
);
}
private
interface
StateChecker
{
boolean
check
(
long
threshold
,
long
warnThreshold
,
long
value
);
}
private
void
checkStartOfNextCycle
()
{
updateLock
.
lock
();
try
{
long
now
=
System
.
currentTimeMillis
();
myUsageStates
.
values
().
forEach
(
state
->
{
if
((
state
.
getNextCycleTs
()
<
now
)
&&
(
now
-
state
.
getNextCycleTs
()
<
TimeUnit
.
HOURS
.
toMillis
(
1
)))
{
state
.
setCycles
(
state
.
getNextCycleTs
(),
SchedulerUtils
.
getStartOfNextNextMonth
());
saveNewCounts
(
state
,
Arrays
.
asList
(
ApiUsageRecordKey
.
values
()));
if
(
state
.
getEntityType
()
==
EntityType
.
TENANT
&&
!
state
.
getEntityId
().
equals
(
TenantId
.
SYS_TENANT_ID
))
{
TenantId
tenantId
=
state
.
getTenantId
();
updateTenantState
((
TenantApiUsageState
)
state
,
tenantProfileCache
.
get
(
tenantId
));
}
}
});
}
finally
{
updateLock
.
unlock
();
}
}
private
void
saveNewCounts
(
BaseApiUsageState
state
,
List
<
ApiUsageRecordKey
>
keys
)
{
List
<
TsKvEntry
>
counts
=
keys
.
stream
()
.
map
(
key
->
new
BasicTsKvEntry
(
state
.
getCurrentCycleTs
(),
new
LongDataEntry
(
key
.
getApiCountKey
(),
0L
)))
.
collect
(
Collectors
.
toList
());
tsWsService
.
saveAndNotifyInternal
(
state
.
getTenantId
(),
state
.
getApiUsageState
().
getId
(),
counts
,
VOID_CALLBACK
);
}
BaseApiUsageState
getOrFetchState
(
TenantId
tenantId
,
EntityId
entityId
)
{
if
(
entityId
==
null
||
entityId
.
isNullUid
())
{
entityId
=
tenantId
;
}
BaseApiUsageState
state
=
myUsageStates
.
get
(
entityId
);
if
(
state
!=
null
)
{
return
state
;
}
ApiUsageState
storedState
=
apiUsageStateService
.
findApiUsageStateByEntityId
(
entityId
);
if
(
storedState
==
null
)
{
try
{
storedState
=
apiUsageStateService
.
createDefaultApiUsageState
(
tenantId
,
entityId
);
}
catch
(
Exception
e
)
{
storedState
=
apiUsageStateService
.
findApiUsageStateByEntityId
(
entityId
);
}
}
if
(
entityId
.
getEntityType
()
==
EntityType
.
TENANT
)
{
if
(!
entityId
.
equals
(
TenantId
.
SYS_TENANT_ID
))
{
state
=
new
TenantApiUsageState
(
tenantProfileCache
.
get
((
TenantId
)
entityId
),
storedState
);
}
else
{
state
=
new
TenantApiUsageState
(
storedState
);
}
}
else
{
state
=
new
CustomerApiUsageState
(
storedState
);
}
List
<
ApiUsageRecordKey
>
newCounts
=
new
ArrayList
<>();
try
{
List
<
TsKvEntry
>
dbValues
=
tsService
.
findAllLatest
(
tenantId
,
storedState
.
getId
()).
get
();
for
(
ApiUsageRecordKey
key
:
ApiUsageRecordKey
.
values
())
{
boolean
cycleEntryFound
=
false
;
boolean
hourlyEntryFound
=
false
;
for
(
TsKvEntry
tsKvEntry
:
dbValues
)
{
if
(
tsKvEntry
.
getKey
().
equals
(
key
.
getApiCountKey
()))
{
cycleEntryFound
=
true
;
boolean
oldCount
=
tsKvEntry
.
getTs
()
==
state
.
getCurrentCycleTs
();
state
.
put
(
key
,
oldCount
?
tsKvEntry
.
getLongValue
().
get
()
:
0L
);
if
(!
oldCount
)
{
newCounts
.
add
(
key
);
}
}
else
if
(
tsKvEntry
.
getKey
().
equals
(
key
.
getApiCountKey
()
+
HOURLY
))
{
hourlyEntryFound
=
true
;
state
.
putHourly
(
key
,
tsKvEntry
.
getTs
()
==
state
.
getCurrentHourTs
()
?
tsKvEntry
.
getLongValue
().
get
()
:
0L
);
}
if
(
cycleEntryFound
&&
hourlyEntryFound
)
{
break
;
}
}
}
log
.
debug
(
"[{}] Initialized state: {}"
,
entityId
,
storedState
);
TopicPartitionInfo
tpi
=
partitionService
.
resolve
(
ServiceType
.
TB_CORE
,
tenantId
,
entityId
);
if
(
tpi
.
isMyPartition
())
{
addEntityState
(
tpi
,
state
);
}
else
{
otherUsageStates
.
put
(
entityId
,
state
.
getApiUsageState
());
}
saveNewCounts
(
state
,
newCounts
);
}
catch
(
InterruptedException
|
ExecutionException
e
)
{
log
.
warn
(
"[{}] Failed to fetch api usage state from db."
,
tenantId
,
e
);
}
return
state
;
}
@Override
protected
void
onRepartitionEvent
()
{
otherUsageStates
.
entrySet
().
removeIf
(
entry
->
partitionService
.
resolve
(
ServiceType
.
TB_CORE
,
entry
.
getValue
().
getTenantId
(),
entry
.
getKey
()).
isMyPartition
());
}
@Override
protected
Map
<
TopicPartitionInfo
,
List
<
ListenableFuture
<?>>>
onAddedPartitions
(
Set
<
TopicPartitionInfo
>
addedPartitions
)
{
var
result
=
new
HashMap
<
TopicPartitionInfo
,
List
<
ListenableFuture
<?>>>();
try
{
log
.
info
(
"Initializing tenant states."
);
updateLock
.
lock
();
try
{
PageDataIterable
<
Tenant
>
tenantIterator
=
new
PageDataIterable
<>(
tenantService:
:
findTenants
,
1024
);
for
(
Tenant
tenant
:
tenantIterator
)
{
TopicPartitionInfo
tpi
=
partitionService
.
resolve
(
ServiceType
.
TB_CORE
,
tenant
.
getId
(),
tenant
.
getId
());
if
(
addedPartitions
.
contains
(
tpi
))
{
if
(!
myUsageStates
.
containsKey
(
tenant
.
getId
())
&&
tpi
.
isMyPartition
())
{
log
.
debug
(
"[{}] Initializing tenant state."
,
tenant
.
getId
());
result
.
computeIfAbsent
(
tpi
,
tmp
->
new
ArrayList
<>()).
add
(
dbExecutor
.
submit
(()
->
{
try
{
updateTenantState
((
TenantApiUsageState
)
getOrFetchState
(
tenant
.
getId
(),
tenant
.
getId
()),
tenantProfileCache
.
get
(
tenant
.
getTenantProfileId
()));
log
.
debug
(
"[{}] Initialized tenant state."
,
tenant
.
getId
());
}
catch
(
Exception
e
)
{
log
.
warn
(
"[{}] Failed to initialize tenant API state"
,
tenant
.
getId
(),
e
);
}
return
null
;
}));
}
}
else
{
log
.
debug
(
"[{}][{}] Tenant doesn't belong to current partition. tpi [{}]"
,
tenant
.
getName
(),
tenant
.
getId
(),
tpi
);
}
}
}
finally
{
updateLock
.
unlock
();
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"Unknown failure"
,
e
);
}
return
result
;
}
@PreDestroy
private
void
destroy
()
{
super
.
stop
();
if
(
mailExecutor
!=
null
)
{
mailExecutor
.
shutdownNow
();
}
}
}
application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.apiusage
;
import
org.thingsboard.server.common.data.id.TenantId
;
public
interface
RateLimitService
{
boolean
checkEntityExportLimit
(
TenantId
tenantId
);
boolean
checkEntityImportLimit
(
TenantId
tenantId
);
}
application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.apiusage
;
import
org.springframework.context.ApplicationListener
;
import
org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService
;
import
org.thingsboard.server.common.data.id.CustomerId
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.id.TenantProfileId
;
import
org.thingsboard.server.common.msg.queue.TbCallback
;
import
org.thingsboard.server.common.stats.TbApiUsageStateClient
;
import
org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg
;
import
org.thingsboard.server.queue.common.TbProtoQueueMsg
;
import
org.thingsboard.server.queue.discovery.event.PartitionChangeEvent
;
public
interface
TbApiUsageStateService
extends
TbApiUsageStateClient
,
RuleEngineApiUsageStateService
,
ApplicationListener
<
PartitionChangeEvent
>
{
void
process
(
TbProtoQueueMsg
<
ToUsageStatsServiceMsg
>
msg
,
TbCallback
callback
);
void
onTenantProfileUpdate
(
TenantProfileId
tenantProfileId
);
void
onTenantUpdate
(
TenantId
tenantId
);
void
onTenantDelete
(
TenantId
tenantId
);
void
onCustomerDelete
(
CustomerId
customerId
);
void
onApiUsageStateUpdate
(
TenantId
tenantId
);
}
application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.apiusage
;
import
lombok.Getter
;
import
lombok.Setter
;
import
org.springframework.data.util.Pair
;
import
org.thingsboard.server.common.data.ApiFeature
;
import
org.thingsboard.server.common.data.ApiUsageRecordKey
;
import
org.thingsboard.server.common.data.ApiUsageState
;
import
org.thingsboard.server.common.data.ApiUsageStateValue
;
import
org.thingsboard.server.common.data.EntityType
;
import
org.thingsboard.server.common.data.TenantProfile
;
import
org.thingsboard.server.common.data.id.TenantProfileId
;
import
org.thingsboard.server.common.data.tenant.profile.TenantProfileData
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.Map
;
import
java.util.Set
;
public
class
TenantApiUsageState
extends
BaseApiUsageState
{
@Getter
@Setter
private
TenantProfileId
tenantProfileId
;
@Getter
@Setter
private
TenantProfileData
tenantProfileData
;
public
TenantApiUsageState
(
TenantProfile
tenantProfile
,
ApiUsageState
apiUsageState
)
{
super
(
apiUsageState
);
this
.
tenantProfileId
=
tenantProfile
.
getId
();
this
.
tenantProfileData
=
tenantProfile
.
getProfileData
();
}
public
TenantApiUsageState
(
ApiUsageState
apiUsageState
)
{
super
(
apiUsageState
);
}
public
long
getProfileThreshold
(
ApiUsageRecordKey
key
)
{
return
tenantProfileData
.
getConfiguration
().
getProfileThreshold
(
key
);
}
public
long
getProfileWarnThreshold
(
ApiUsageRecordKey
key
)
{
return
tenantProfileData
.
getConfiguration
().
getWarnThreshold
(
key
);
}
private
Pair
<
ApiFeature
,
ApiUsageStateValue
>
checkStateUpdatedDueToThreshold
(
ApiFeature
feature
)
{
ApiUsageStateValue
featureValue
=
ApiUsageStateValue
.
ENABLED
;
for
(
ApiUsageRecordKey
recordKey
:
ApiUsageRecordKey
.
getKeys
(
feature
))
{
long
value
=
get
(
recordKey
);
long
threshold
=
getProfileThreshold
(
recordKey
);
long
warnThreshold
=
getProfileWarnThreshold
(
recordKey
);
ApiUsageStateValue
tmpValue
;
if
(
threshold
==
0
||
value
==
0
||
value
<
warnThreshold
)
{
tmpValue
=
ApiUsageStateValue
.
ENABLED
;
}
else
if
(
value
<
threshold
)
{
tmpValue
=
ApiUsageStateValue
.
WARNING
;
}
else
{
tmpValue
=
ApiUsageStateValue
.
DISABLED
;
}
featureValue
=
ApiUsageStateValue
.
toMoreRestricted
(
featureValue
,
tmpValue
);
}
return
setFeatureValue
(
feature
,
featureValue
)
?
Pair
.
of
(
feature
,
featureValue
)
:
null
;
}
public
Map
<
ApiFeature
,
ApiUsageStateValue
>
checkStateUpdatedDueToThresholds
()
{
return
checkStateUpdatedDueToThreshold
(
new
HashSet
<>(
Arrays
.
asList
(
ApiFeature
.
values
())));
}
public
Map
<
ApiFeature
,
ApiUsageStateValue
>
checkStateUpdatedDueToThreshold
(
Set
<
ApiFeature
>
features
)
{
Map
<
ApiFeature
,
ApiUsageStateValue
>
result
=
new
HashMap
<>();
for
(
ApiFeature
feature
:
features
)
{
Pair
<
ApiFeature
,
ApiUsageStateValue
>
tmp
=
checkStateUpdatedDueToThreshold
(
feature
);
if
(
tmp
!=
null
)
{
result
.
put
(
tmp
.
getFirst
(),
tmp
.
getSecond
());
}
}
return
result
;
}
@Override
public
EntityType
getEntityType
()
{
return
EntityType
.
TENANT
;
}
}
application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.asset
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
com.fasterxml.jackson.databind.node.TextNode
;
import
lombok.RequiredArgsConstructor
;
import
lombok.SneakyThrows
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.server.common.data.EntityType
;
import
org.thingsboard.server.common.data.StringUtils
;
import
org.thingsboard.server.common.data.asset.Asset
;
import
org.thingsboard.server.common.data.asset.AssetProfile
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType
;
import
org.thingsboard.server.dao.asset.AssetProfileService
;
import
org.thingsboard.server.dao.asset.AssetService
;
import
org.thingsboard.server.queue.util.TbCoreComponent
;
import
org.thingsboard.server.service.entitiy.asset.TbAssetService
;
import
org.thingsboard.server.service.security.model.SecurityUser
;
import
org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService
;
import
java.util.Map
;
import
java.util.Optional
;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public
class
AssetBulkImportService
extends
AbstractBulkImportService
<
Asset
>
{
private
final
AssetService
assetService
;
private
final
TbAssetService
tbAssetService
;
private
final
AssetProfileService
assetProfileService
;
@Override
protected
void
setEntityFields
(
Asset
entity
,
Map
<
BulkImportColumnType
,
String
>
fields
)
{
ObjectNode
additionalInfo
=
getOrCreateAdditionalInfoObj
(
entity
);
fields
.
forEach
((
columnType
,
value
)
->
{
switch
(
columnType
)
{
case
NAME:
entity
.
setName
(
value
);
break
;
case
TYPE:
entity
.
setType
(
value
);
break
;
case
LABEL:
entity
.
setLabel
(
value
);
break
;
case
DESCRIPTION:
additionalInfo
.
set
(
"description"
,
new
TextNode
(
value
));
break
;
}
});
entity
.
setAdditionalInfo
(
additionalInfo
);
}
@Override
@SneakyThrows
protected
Asset
saveEntity
(
SecurityUser
user
,
Asset
entity
,
Map
<
BulkImportColumnType
,
String
>
fields
)
{
AssetProfile
assetProfile
;
if
(
StringUtils
.
isNotEmpty
(
entity
.
getType
()))
{
assetProfile
=
assetProfileService
.
findOrCreateAssetProfile
(
entity
.
getTenantId
(),
entity
.
getType
());
}
else
{
assetProfile
=
assetProfileService
.
findDefaultAssetProfile
(
entity
.
getTenantId
());
}
entity
.
setAssetProfileId
(
assetProfile
.
getId
());
return
tbAssetService
.
save
(
entity
,
user
);
}
@Override
protected
Asset
findOrCreateEntity
(
TenantId
tenantId
,
String
name
)
{
return
Optional
.
ofNullable
(
assetService
.
findAssetByTenantIdAndName
(
tenantId
,
name
))
.
orElseGet
(
Asset:
:
new
);
}
@Override
protected
void
setOwners
(
Asset
entity
,
SecurityUser
user
)
{
entity
.
setTenantId
(
user
.
getTenantId
());
entity
.
setCustomerId
(
user
.
getCustomerId
());
}
@Override
protected
EntityType
getEntityType
()
{
return
EntityType
.
ASSET
;
}
}
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.component
;
import
com.fasterxml.jackson.databind.JsonNode
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.beans.factory.config.BeanDefinition
;
import
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
;
import
org.springframework.core.env.Environment
;
import
org.springframework.core.env.Profiles
;
import
org.springframework.core.type.filter.AnnotationTypeFilter
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.rule.engine.api.NodeConfiguration
;
import
org.thingsboard.rule.engine.api.NodeDefinition
;
import
org.thingsboard.rule.engine.api.RuleNode
;
import
org.thingsboard.rule.engine.api.TbRelationTypes
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.plugin.ComponentDescriptor
;
import
org.thingsboard.server.common.data.plugin.ComponentType
;
import
org.thingsboard.server.common.data.rule.RuleChainType
;
import
org.thingsboard.server.dao.component.ComponentDescriptorService
;
import
javax.annotation.PostConstruct
;
import
java.lang.annotation.Annotation
;
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
;
@Service
@Slf4j
public
class
AnnotationComponentDiscoveryService
implements
ComponentDiscoveryService
{
public
static
final
int
MAX_OPTIMISITC_RETRIES
=
3
;
@Value
(
"${plugins.scan_packages}"
)
private
String
[]
scanPackages
;
@Autowired
private
Environment
environment
;
@Autowired
private
ComponentDescriptorService
componentDescriptorService
;
private
Map
<
String
,
ComponentDescriptor
>
components
=
new
HashMap
<>();
private
Map
<
ComponentType
,
List
<
ComponentDescriptor
>>
coreComponentsMap
=
new
HashMap
<>();
private
Map
<
ComponentType
,
List
<
ComponentDescriptor
>>
edgeComponentsMap
=
new
HashMap
<>();
private
ObjectMapper
mapper
=
new
ObjectMapper
();
private
boolean
isInstall
()
{
return
environment
.
acceptsProfiles
(
Profiles
.
of
(
"install"
));
}
@PostConstruct
public
void
init
()
{
if
(!
isInstall
())
{
discoverComponents
();
}
}
private
void
registerRuleNodeComponents
()
{
Set
<
BeanDefinition
>
ruleNodeBeanDefinitions
=
getBeanDefinitions
(
RuleNode
.
class
);
for
(
BeanDefinition
def
:
ruleNodeBeanDefinitions
)
{
int
retryCount
=
0
;
Exception
cause
=
null
;
while
(
retryCount
<
MAX_OPTIMISITC_RETRIES
)
{
try
{
String
clazzName
=
def
.
getBeanClassName
();
Class
<?>
clazz
=
Class
.
forName
(
clazzName
);
RuleNode
ruleNodeAnnotation
=
clazz
.
getAnnotation
(
RuleNode
.
class
);
ComponentType
type
=
ruleNodeAnnotation
.
type
();
ComponentDescriptor
component
=
scanAndPersistComponent
(
def
,
type
);
components
.
put
(
component
.
getClazz
(),
component
);
putComponentIntoMaps
(
type
,
ruleNodeAnnotation
,
component
);
break
;
}
catch
(
Exception
e
)
{
log
.
trace
(
"Can't initialize component {}, due to {}"
,
def
.
getBeanClassName
(),
e
.
getMessage
(),
e
);
cause
=
e
;
retryCount
++;
try
{
Thread
.
sleep
(
1000
);
}
catch
(
InterruptedException
e1
)
{
throw
new
RuntimeException
(
e1
);
}
}
}
if
(
cause
!=
null
&&
retryCount
==
MAX_OPTIMISITC_RETRIES
)
{
log
.
error
(
"Can't initialize component {}, due to {}"
,
def
.
getBeanClassName
(),
cause
.
getMessage
(),
cause
);
throw
new
RuntimeException
(
cause
);
}
}
}
private
void
putComponentIntoMaps
(
ComponentType
type
,
RuleNode
ruleNodeAnnotation
,
ComponentDescriptor
component
)
{
boolean
ruleChainTypesMethodAvailable
;
try
{
ruleNodeAnnotation
.
getClass
().
getMethod
(
"ruleChainTypes"
);
ruleChainTypesMethodAvailable
=
true
;
}
catch
(
NoSuchMethodException
exception
)
{
log
.
warn
(
"[{}] does not have ruleChainTypes. Probably extension class compiled before 3.3 release. "
+
"Please update your extensions and compile using latest 3.3 release dependency"
,
ruleNodeAnnotation
.
name
());
ruleChainTypesMethodAvailable
=
false
;
}
if
(
ruleChainTypesMethodAvailable
)
{
if
(
ruleChainTypeContainsArray
(
RuleChainType
.
CORE
,
ruleNodeAnnotation
.
ruleChainTypes
()))
{
coreComponentsMap
.
computeIfAbsent
(
type
,
k
->
new
ArrayList
<>()).
add
(
component
);
}
if
(
ruleChainTypeContainsArray
(
RuleChainType
.
EDGE
,
ruleNodeAnnotation
.
ruleChainTypes
()))
{
edgeComponentsMap
.
computeIfAbsent
(
type
,
k
->
new
ArrayList
<>()).
add
(
component
);
}
}
else
{
coreComponentsMap
.
computeIfAbsent
(
type
,
k
->
new
ArrayList
<>()).
add
(
component
);
}
}
private
boolean
ruleChainTypeContainsArray
(
RuleChainType
ruleChainType
,
RuleChainType
[]
array
)
{
for
(
RuleChainType
tmp
:
array
)
{
if
(
ruleChainType
.
equals
(
tmp
))
{
return
true
;
}
}
return
false
;
}
private
ComponentDescriptor
scanAndPersistComponent
(
BeanDefinition
def
,
ComponentType
type
)
{
ComponentDescriptor
scannedComponent
=
new
ComponentDescriptor
();
String
clazzName
=
def
.
getBeanClassName
();
try
{
scannedComponent
.
setType
(
type
);
Class
<?>
clazz
=
Class
.
forName
(
clazzName
);
RuleNode
ruleNodeAnnotation
=
clazz
.
getAnnotation
(
RuleNode
.
class
);
scannedComponent
.
setName
(
ruleNodeAnnotation
.
name
());
scannedComponent
.
setScope
(
ruleNodeAnnotation
.
scope
());
NodeDefinition
nodeDefinition
=
prepareNodeDefinition
(
ruleNodeAnnotation
);
ObjectNode
configurationDescriptor
=
mapper
.
createObjectNode
();
JsonNode
node
=
mapper
.
valueToTree
(
nodeDefinition
);
configurationDescriptor
.
set
(
"nodeDefinition"
,
node
);
scannedComponent
.
setConfigurationDescriptor
(
configurationDescriptor
);
scannedComponent
.
setClazz
(
clazzName
);
log
.
debug
(
"Processing scanned component: {}"
,
scannedComponent
);
}
catch
(
Exception
e
)
{
log
.
error
(
"Can't initialize component {}, due to {}"
,
def
.
getBeanClassName
(),
e
.
getMessage
(),
e
);
throw
new
RuntimeException
(
e
);
}
ComponentDescriptor
persistedComponent
=
componentDescriptorService
.
findByClazz
(
TenantId
.
SYS_TENANT_ID
,
clazzName
);
if
(
persistedComponent
==
null
)
{
log
.
debug
(
"Persisting new component: {}"
,
scannedComponent
);
scannedComponent
=
componentDescriptorService
.
saveComponent
(
TenantId
.
SYS_TENANT_ID
,
scannedComponent
);
}
else
if
(
scannedComponent
.
equals
(
persistedComponent
))
{
log
.
debug
(
"Component is already persisted: {}"
,
persistedComponent
);
scannedComponent
=
persistedComponent
;
}
else
{
log
.
debug
(
"Component {} will be updated to {}"
,
persistedComponent
,
scannedComponent
);
componentDescriptorService
.
deleteByClazz
(
TenantId
.
SYS_TENANT_ID
,
persistedComponent
.
getClazz
());
scannedComponent
.
setId
(
persistedComponent
.
getId
());
scannedComponent
=
componentDescriptorService
.
saveComponent
(
TenantId
.
SYS_TENANT_ID
,
scannedComponent
);
}
return
scannedComponent
;
}
private
NodeDefinition
prepareNodeDefinition
(
RuleNode
nodeAnnotation
)
throws
Exception
{
NodeDefinition
nodeDefinition
=
new
NodeDefinition
();
nodeDefinition
.
setDetails
(
nodeAnnotation
.
nodeDetails
());
nodeDefinition
.
setDescription
(
nodeAnnotation
.
nodeDescription
());
nodeDefinition
.
setInEnabled
(
nodeAnnotation
.
inEnabled
());
nodeDefinition
.
setOutEnabled
(
nodeAnnotation
.
outEnabled
());
nodeDefinition
.
setRelationTypes
(
getRelationTypesWithFailureRelation
(
nodeAnnotation
));
nodeDefinition
.
setCustomRelations
(
nodeAnnotation
.
customRelations
());
nodeDefinition
.
setRuleChainNode
(
nodeAnnotation
.
ruleChainNode
());
Class
<?
extends
NodeConfiguration
>
configClazz
=
nodeAnnotation
.
configClazz
();
NodeConfiguration
config
=
configClazz
.
getDeclaredConstructor
().
newInstance
();
NodeConfiguration
defaultConfiguration
=
config
.
defaultConfiguration
();
nodeDefinition
.
setDefaultConfiguration
(
mapper
.
valueToTree
(
defaultConfiguration
));
nodeDefinition
.
setUiResources
(
nodeAnnotation
.
uiResources
());
nodeDefinition
.
setConfigDirective
(
nodeAnnotation
.
configDirective
());
nodeDefinition
.
setIcon
(
nodeAnnotation
.
icon
());
nodeDefinition
.
setIconUrl
(
nodeAnnotation
.
iconUrl
());
nodeDefinition
.
setDocUrl
(
nodeAnnotation
.
docUrl
());
return
nodeDefinition
;
}
private
String
[]
getRelationTypesWithFailureRelation
(
RuleNode
nodeAnnotation
)
{
List
<
String
>
relationTypes
=
new
ArrayList
<>(
Arrays
.
asList
(
nodeAnnotation
.
relationTypes
()));
if
(!
relationTypes
.
contains
(
TbRelationTypes
.
FAILURE
))
{
relationTypes
.
add
(
TbRelationTypes
.
FAILURE
);
}
return
relationTypes
.
toArray
(
new
String
[
relationTypes
.
size
()]);
}
private
Set
<
BeanDefinition
>
getBeanDefinitions
(
Class
<?
extends
Annotation
>
componentType
)
{
ClassPathScanningCandidateComponentProvider
scanner
=
new
ClassPathScanningCandidateComponentProvider
(
false
);
scanner
.
addIncludeFilter
(
new
AnnotationTypeFilter
(
componentType
));
Set
<
BeanDefinition
>
defs
=
new
HashSet
<>();
for
(
String
scanPackage
:
scanPackages
)
{
defs
.
addAll
(
scanner
.
findCandidateComponents
(
scanPackage
));
}
return
defs
;
}
@Override
public
void
discoverComponents
()
{
registerRuleNodeComponents
();
log
.
debug
(
"Found following definitions: {}"
,
components
.
values
());
}
@Override
public
List
<
ComponentDescriptor
>
getComponents
(
ComponentType
type
,
RuleChainType
ruleChainType
)
{
if
(
RuleChainType
.
CORE
.
equals
(
ruleChainType
))
{
if
(
coreComponentsMap
.
containsKey
(
type
))
{
return
Collections
.
unmodifiableList
(
coreComponentsMap
.
get
(
type
));
}
else
{
return
Collections
.
emptyList
();
}
}
else
if
(
RuleChainType
.
EDGE
.
equals
(
ruleChainType
))
{
if
(
edgeComponentsMap
.
containsKey
(
type
))
{
return
Collections
.
unmodifiableList
(
edgeComponentsMap
.
get
(
type
));
}
else
{
return
Collections
.
emptyList
();
}
}
else
{
log
.
error
(
"Unsupported rule chain type {}"
,
ruleChainType
);
throw
new
RuntimeException
(
"Unsupported rule chain type "
+
ruleChainType
);
}
}
@Override
public
List
<
ComponentDescriptor
>
getComponents
(
Set
<
ComponentType
>
types
,
RuleChainType
ruleChainType
)
{
if
(
RuleChainType
.
CORE
.
equals
(
ruleChainType
))
{
return
getComponents
(
types
,
coreComponentsMap
);
}
else
if
(
RuleChainType
.
EDGE
.
equals
(
ruleChainType
))
{
return
getComponents
(
types
,
edgeComponentsMap
);
}
else
{
log
.
error
(
"Unsupported rule chain type {}"
,
ruleChainType
);
throw
new
RuntimeException
(
"Unsupported rule chain type "
+
ruleChainType
);
}
}
@Override
public
Optional
<
ComponentDescriptor
>
getComponent
(
String
clazz
)
{
return
Optional
.
ofNullable
(
components
.
get
(
clazz
));
}
private
List
<
ComponentDescriptor
>
getComponents
(
Set
<
ComponentType
>
types
,
Map
<
ComponentType
,
List
<
ComponentDescriptor
>>
componentsMap
)
{
List
<
ComponentDescriptor
>
result
=
new
ArrayList
<>();
types
.
stream
().
filter
(
componentsMap:
:
containsKey
).
forEach
(
type
->
{
result
.
addAll
(
componentsMap
.
get
(
type
));
});
return
Collections
.
unmodifiableList
(
result
);
}
}
application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.component
;
import
org.thingsboard.server.common.data.plugin.ComponentDescriptor
;
import
org.thingsboard.server.common.data.plugin.ComponentType
;
import
org.thingsboard.server.common.data.rule.RuleChainType
;
import
java.util.List
;
import
java.util.Optional
;
import
java.util.Set
;
/**
* @author Andrew Shvayka
*/
public
interface
ComponentDiscoveryService
{
void
discoverComponents
();
List
<
ComponentDescriptor
>
getComponents
(
ComponentType
type
,
RuleChainType
ruleChainType
);
List
<
ComponentDescriptor
>
getComponents
(
Set
<
ComponentType
>
types
,
RuleChainType
ruleChainType
);
Optional
<
ComponentDescriptor
>
getComponent
(
String
clazz
);
}
application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.device
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
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
com.google.common.util.concurrent.SettableFuture
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.cache.Cache
;
import
org.springframework.cache.CacheManager
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.common.util.JacksonUtil
;
import
org.thingsboard.rule.engine.api.RuleEngineTelemetryService
;
import
org.thingsboard.server.cluster.TbClusterService
;
import
org.thingsboard.server.common.data.Customer
;
import
org.thingsboard.server.common.data.DataConstants
;
import
org.thingsboard.server.common.data.Device
;
import
org.thingsboard.server.common.data.StringUtils
;
import
org.thingsboard.server.common.data.id.CustomerId
;
import
org.thingsboard.server.common.data.id.DeviceId
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.kv.AttributeKvEntry
;
import
org.thingsboard.server.common.data.kv.BaseAttributeKvEntry
;
import
org.thingsboard.server.common.data.kv.BooleanDataEntry
;
import
org.thingsboard.server.dao.attributes.AttributesService
;
import
org.thingsboard.server.dao.customer.CustomerService
;
import
org.thingsboard.server.dao.device.ClaimDataInfo
;
import
org.thingsboard.server.dao.device.ClaimDevicesService
;
import
org.thingsboard.server.dao.device.DeviceService
;
import
org.thingsboard.server.dao.device.claim.ClaimData
;
import
org.thingsboard.server.dao.device.claim.ClaimResponse
;
import
org.thingsboard.server.dao.device.claim.ClaimResult
;
import
org.thingsboard.server.dao.device.claim.ReclaimResult
;
import
org.thingsboard.server.dao.model.ModelConstants
;
import
org.thingsboard.server.queue.util.TbCoreComponent
;
import
javax.annotation.Nullable
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Optional
;
import
static
org
.
thingsboard
.
server
.
common
.
data
.
CacheConstants
.
CLAIM_DEVICES_CACHE
;
@Service
@Slf4j
@TbCoreComponent
public
class
ClaimDevicesServiceImpl
implements
ClaimDevicesService
{
private
static
final
String
CLAIM_ATTRIBUTE_NAME
=
"claimingAllowed"
;
private
static
final
String
CLAIM_DATA_ATTRIBUTE_NAME
=
"claimingData"
;
private
static
final
ObjectMapper
mapper
=
new
ObjectMapper
();
@Autowired
private
TbClusterService
clusterService
;
@Autowired
private
DeviceService
deviceService
;
@Autowired
private
AttributesService
attributesService
;
@Autowired
private
RuleEngineTelemetryService
telemetryService
;
@Autowired
private
CustomerService
customerService
;
@Autowired
private
CacheManager
cacheManager
;
@Value
(
"${security.claim.allowClaimingByDefault}"
)
private
boolean
isAllowedClaimingByDefault
;
@Value
(
"${security.claim.duration}"
)
private
long
systemDurationMs
;
@Override
public
ListenableFuture
<
Void
>
registerClaimingInfo
(
TenantId
tenantId
,
DeviceId
deviceId
,
String
secretKey
,
long
durationMs
)
{
ListenableFuture
<
Device
>
deviceFuture
=
deviceService
.
findDeviceByIdAsync
(
tenantId
,
deviceId
);
return
Futures
.
transformAsync
(
deviceFuture
,
device
->
{
Cache
cache
=
cacheManager
.
getCache
(
CLAIM_DEVICES_CACHE
);
List
<
Object
>
key
=
constructCacheKey
(
device
.
getId
());
if
(
isAllowedClaimingByDefault
)
{
if
(
device
.
getCustomerId
().
getId
().
equals
(
ModelConstants
.
NULL_UUID
))
{
persistInCache
(
secretKey
,
durationMs
,
cache
,
key
);
return
Futures
.
immediateFuture
(
null
);
}
log
.
warn
(
"The device [{}] has been already claimed!"
,
device
.
getName
());
throw
new
IllegalArgumentException
();
}
else
{
ListenableFuture
<
List
<
AttributeKvEntry
>>
claimingAllowedFuture
=
attributesService
.
find
(
tenantId
,
device
.
getId
(),
DataConstants
.
SERVER_SCOPE
,
Collections
.
singletonList
(
CLAIM_ATTRIBUTE_NAME
));
return
Futures
.
transform
(
claimingAllowedFuture
,
list
->
{
if
(
list
!=
null
&&
!
list
.
isEmpty
())
{
Optional
<
Boolean
>
claimingAllowedOptional
=
list
.
get
(
0
).
getBooleanValue
();
if
(
claimingAllowedOptional
.
isPresent
()
&&
claimingAllowedOptional
.
get
()
&&
device
.
getCustomerId
().
getId
().
equals
(
ModelConstants
.
NULL_UUID
))
{
persistInCache
(
secretKey
,
durationMs
,
cache
,
key
);
return
null
;
}
}
log
.
warn
(
"Failed to find claimingAllowed attribute for device or it is already claimed![{}]"
,
device
.
getName
());
throw
new
IllegalArgumentException
();
},
MoreExecutors
.
directExecutor
());
}
},
MoreExecutors
.
directExecutor
());
}
private
ListenableFuture
<
ClaimDataInfo
>
getClaimData
(
Cache
cache
,
Device
device
)
{
List
<
Object
>
key
=
constructCacheKey
(
device
.
getId
());
ClaimData
claimDataFromCache
=
cache
.
get
(
key
,
ClaimData
.
class
);
if
(
claimDataFromCache
!=
null
)
{
return
Futures
.
immediateFuture
(
new
ClaimDataInfo
(
true
,
key
,
claimDataFromCache
));
}
else
{
ListenableFuture
<
Optional
<
AttributeKvEntry
>>
claimDataAttrFuture
=
attributesService
.
find
(
device
.
getTenantId
(),
device
.
getId
(),
DataConstants
.
SERVER_SCOPE
,
CLAIM_DATA_ATTRIBUTE_NAME
);
return
Futures
.
transform
(
claimDataAttrFuture
,
claimDataAttr
->
{
if
(
claimDataAttr
.
isPresent
())
{
ClaimData
claimDataFromAttribute
=
JacksonUtil
.
fromString
(
claimDataAttr
.
get
().
getValueAsString
(),
ClaimData
.
class
);
return
new
ClaimDataInfo
(
false
,
key
,
claimDataFromAttribute
);
}
return
null
;
},
MoreExecutors
.
directExecutor
());
}
}
@Override
public
ListenableFuture
<
ClaimResult
>
claimDevice
(
Device
device
,
CustomerId
customerId
,
String
secretKey
)
{
Cache
cache
=
cacheManager
.
getCache
(
CLAIM_DEVICES_CACHE
);
ListenableFuture
<
ClaimDataInfo
>
claimDataFuture
=
getClaimData
(
cache
,
device
);
return
Futures
.
transformAsync
(
claimDataFuture
,
claimData
->
{
if
(
claimData
!=
null
)
{
long
currTs
=
System
.
currentTimeMillis
();
if
(
currTs
>
claimData
.
getData
().
getExpirationTime
()
||
!
secretKeyIsEmptyOrEqual
(
secretKey
,
claimData
.
getData
().
getSecretKey
()))
{
log
.
warn
(
"The claiming timeout occurred or wrong 'secretKey' provided for the device [{}]"
,
device
.
getName
());
if
(
claimData
.
isFromCache
())
{
cache
.
evict
(
claimData
.
getKey
());
}
return
Futures
.
immediateFuture
(
new
ClaimResult
(
null
,
ClaimResponse
.
FAILURE
));
}
else
{
if
(
device
.
getCustomerId
().
getId
().
equals
(
ModelConstants
.
NULL_UUID
))
{
device
.
setCustomerId
(
customerId
);
Device
savedDevice
=
deviceService
.
saveDevice
(
device
);
clusterService
.
onDeviceUpdated
(
savedDevice
,
device
);
return
Futures
.
transform
(
removeClaimingSavedData
(
cache
,
claimData
,
device
),
result
->
new
ClaimResult
(
savedDevice
,
ClaimResponse
.
SUCCESS
),
MoreExecutors
.
directExecutor
());
}
return
Futures
.
transform
(
removeClaimingSavedData
(
cache
,
claimData
,
device
),
result
->
new
ClaimResult
(
null
,
ClaimResponse
.
CLAIMED
),
MoreExecutors
.
directExecutor
());
}
}
else
{
log
.
warn
(
"Failed to find the device's claiming message![{}]"
,
device
.
getName
());
if
(
device
.
getCustomerId
().
getId
().
equals
(
ModelConstants
.
NULL_UUID
))
{
return
Futures
.
immediateFuture
(
new
ClaimResult
(
null
,
ClaimResponse
.
FAILURE
));
}
else
{
return
Futures
.
immediateFuture
(
new
ClaimResult
(
null
,
ClaimResponse
.
CLAIMED
));
}
}
},
MoreExecutors
.
directExecutor
());
}
private
boolean
secretKeyIsEmptyOrEqual
(
String
secretKeyA
,
String
secretKeyB
)
{
return
(
StringUtils
.
isEmpty
(
secretKeyA
)
&&
StringUtils
.
isEmpty
(
secretKeyB
))
||
secretKeyA
.
equals
(
secretKeyB
);
}
@Override
public
ListenableFuture
<
ReclaimResult
>
reClaimDevice
(
TenantId
tenantId
,
Device
device
)
{
if
(!
device
.
getCustomerId
().
getId
().
equals
(
ModelConstants
.
NULL_UUID
))
{
cacheEviction
(
device
.
getId
());
Customer
unassignedCustomer
=
customerService
.
findCustomerById
(
tenantId
,
device
.
getCustomerId
());
device
.
setCustomerId
(
null
);
Device
savedDevice
=
deviceService
.
saveDevice
(
device
);
clusterService
.
onDeviceUpdated
(
savedDevice
,
device
);
if
(
isAllowedClaimingByDefault
)
{
return
Futures
.
immediateFuture
(
new
ReclaimResult
(
unassignedCustomer
));
}
SettableFuture
<
ReclaimResult
>
result
=
SettableFuture
.
create
();
telemetryService
.
saveAndNotify
(
tenantId
,
savedDevice
.
getId
(),
DataConstants
.
SERVER_SCOPE
,
Collections
.
singletonList
(
new
BaseAttributeKvEntry
(
new
BooleanDataEntry
(
CLAIM_ATTRIBUTE_NAME
,
true
),
System
.
currentTimeMillis
())
),
new
FutureCallback
<>()
{
@Override
public
void
onSuccess
(
@Nullable
Void
tmp
)
{
result
.
set
(
new
ReclaimResult
(
unassignedCustomer
));
}
@Override
public
void
onFailure
(
Throwable
t
)
{
result
.
setException
(
t
);
}
});
return
result
;
}
cacheEviction
(
device
.
getId
());
return
Futures
.
immediateFuture
(
new
ReclaimResult
(
null
));
}
private
List
<
Object
>
constructCacheKey
(
DeviceId
deviceId
)
{
return
Collections
.
singletonList
(
deviceId
);
}
private
void
persistInCache
(
String
secretKey
,
long
durationMs
,
Cache
cache
,
List
<
Object
>
key
)
{
ClaimData
claimData
=
new
ClaimData
(
secretKey
,
System
.
currentTimeMillis
()
+
validateDurationMs
(
durationMs
));
cache
.
putIfAbsent
(
key
,
claimData
);
}
private
long
validateDurationMs
(
long
durationMs
)
{
if
(
durationMs
>
0L
)
{
return
durationMs
;
}
return
systemDurationMs
;
}
private
ListenableFuture
<
Void
>
removeClaimingSavedData
(
Cache
cache
,
ClaimDataInfo
data
,
Device
device
)
{
if
(
data
.
isFromCache
())
{
cache
.
evict
(
data
.
getKey
());
}
SettableFuture
<
Void
>
result
=
SettableFuture
.
create
();
telemetryService
.
deleteAndNotify
(
device
.
getTenantId
(),
device
.
getId
(),
DataConstants
.
SERVER_SCOPE
,
Arrays
.
asList
(
CLAIM_ATTRIBUTE_NAME
,
CLAIM_DATA_ATTRIBUTE_NAME
),
new
FutureCallback
<>()
{
@Override
public
void
onSuccess
(
@Nullable
Void
tmp
)
{
result
.
set
(
tmp
);
}
@Override
public
void
onFailure
(
Throwable
t
)
{
result
.
setException
(
t
);
}
});
return
result
;
}
private
void
cacheEviction
(
DeviceId
deviceId
)
{
Cache
cache
=
cacheManager
.
getCache
(
CLAIM_DEVICES_CACHE
);
cache
.
evict
(
constructCacheKey
(
deviceId
));
}
}
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.device
;
import
com.fasterxml.jackson.databind.node.BooleanNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
com.fasterxml.jackson.databind.node.TextNode
;
import
lombok.RequiredArgsConstructor
;
import
lombok.SneakyThrows
;
import
org.apache.commons.collections.CollectionUtils
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.common.util.JacksonUtil
;
import
org.thingsboard.server.common.data.Device
;
import
org.thingsboard.server.common.data.DeviceProfile
;
import
org.thingsboard.server.common.data.DeviceProfileProvisionType
;
import
org.thingsboard.server.common.data.DeviceProfileType
;
import
org.thingsboard.server.common.data.DeviceTransportType
;
import
org.thingsboard.server.common.data.EntityType
;
import
org.thingsboard.server.common.data.StringUtils
;
import
org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials
;
import
org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential
;
import
org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode
;
import
org.thingsboard.server.common.data.device.data.PowerMode
;
import
org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration
;
import
org.thingsboard.server.common.data.device.profile.DeviceProfileData
;
import
org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration
;
import
org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration
;
import
org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration
;
import
org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration
;
import
org.thingsboard.server.common.data.id.DeviceId
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.security.DeviceCredentials
;
import
org.thingsboard.server.common.data.security.DeviceCredentialsType
;
import
org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType
;
import
org.thingsboard.server.dao.device.DeviceCredentialsService
;
import
org.thingsboard.server.dao.device.DeviceProfileService
;
import
org.thingsboard.server.dao.device.DeviceService
;
import
org.thingsboard.server.dao.exception.DeviceCredentialsValidationException
;
import
org.thingsboard.server.queue.util.TbCoreComponent
;
import
org.thingsboard.server.service.entitiy.device.TbDeviceService
;
import
org.thingsboard.server.service.security.model.SecurityUser
;
import
org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService
;
import
java.util.Collection
;
import
java.util.Collections
;
import
java.util.EnumSet
;
import
java.util.Map
;
import
java.util.Objects
;
import
java.util.Optional
;
import
java.util.Set
;
import
java.util.concurrent.locks.Lock
;
import
java.util.concurrent.locks.ReentrantLock
;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public
class
DeviceBulkImportService
extends
AbstractBulkImportService
<
Device
>
{
protected
final
DeviceService
deviceService
;
protected
final
TbDeviceService
tbDeviceService
;
protected
final
DeviceCredentialsService
deviceCredentialsService
;
protected
final
DeviceProfileService
deviceProfileService
;
private
final
Lock
findOrCreateDeviceProfileLock
=
new
ReentrantLock
();
@Override
protected
void
setEntityFields
(
Device
entity
,
Map
<
BulkImportColumnType
,
String
>
fields
)
{
ObjectNode
additionalInfo
=
getOrCreateAdditionalInfoObj
(
entity
);
fields
.
forEach
((
columnType
,
value
)
->
{
switch
(
columnType
)
{
case
NAME:
entity
.
setName
(
value
);
break
;
case
TYPE:
entity
.
setType
(
value
);
break
;
case
LABEL:
entity
.
setLabel
(
value
);
break
;
case
DESCRIPTION:
additionalInfo
.
set
(
"description"
,
new
TextNode
(
value
));
break
;
case
IS_GATEWAY:
additionalInfo
.
set
(
"gateway"
,
BooleanNode
.
valueOf
(
Boolean
.
parseBoolean
(
value
)));
break
;
}
entity
.
setAdditionalInfo
(
additionalInfo
);
});
}
@Override
@SneakyThrows
protected
Device
saveEntity
(
SecurityUser
user
,
Device
entity
,
Map
<
BulkImportColumnType
,
String
>
fields
)
{
DeviceCredentials
deviceCredentials
;
try
{
deviceCredentials
=
createDeviceCredentials
(
entity
.
getTenantId
(),
entity
.
getId
(),
fields
);
deviceCredentialsService
.
formatCredentials
(
deviceCredentials
);
}
catch
(
Exception
e
)
{
throw
new
DeviceCredentialsValidationException
(
"Invalid device credentials: "
+
e
.
getMessage
());
}
DeviceProfile
deviceProfile
;
if
(
deviceCredentials
.
getCredentialsType
()
==
DeviceCredentialsType
.
LWM2M_CREDENTIALS
)
{
deviceProfile
=
setUpLwM2mDeviceProfile
(
entity
.
getTenantId
(),
entity
);
}
else
if
(
StringUtils
.
isNotEmpty
(
entity
.
getType
()))
{
deviceProfile
=
deviceProfileService
.
findOrCreateDeviceProfile
(
entity
.
getTenantId
(),
entity
.
getType
());
}
else
{
deviceProfile
=
deviceProfileService
.
findDefaultDeviceProfile
(
entity
.
getTenantId
());
}
entity
.
setDeviceProfileId
(
deviceProfile
.
getId
());
return
tbDeviceService
.
saveDeviceWithCredentials
(
entity
,
deviceCredentials
,
user
);
}
@Override
protected
Device
findOrCreateEntity
(
TenantId
tenantId
,
String
name
)
{
return
Optional
.
ofNullable
(
deviceService
.
findDeviceByTenantIdAndName
(
tenantId
,
name
))
.
orElseGet
(
Device:
:
new
);
}
@Override
protected
void
setOwners
(
Device
entity
,
SecurityUser
user
)
{
entity
.
setTenantId
(
user
.
getTenantId
());
entity
.
setCustomerId
(
user
.
getCustomerId
());
}
@SneakyThrows
private
DeviceCredentials
createDeviceCredentials
(
TenantId
tenantId
,
DeviceId
deviceId
,
Map
<
BulkImportColumnType
,
String
>
fields
)
{
DeviceCredentials
credentials
=
new
DeviceCredentials
();
if
(
fields
.
containsKey
(
BulkImportColumnType
.
LWM2M_CLIENT_ENDPOINT
))
{
credentials
.
setCredentialsType
(
DeviceCredentialsType
.
LWM2M_CREDENTIALS
);
setUpLwm2mCredentials
(
fields
,
credentials
);
}
else
if
(
fields
.
containsKey
(
BulkImportColumnType
.
X509
))
{
credentials
.
setCredentialsType
(
DeviceCredentialsType
.
X509_CERTIFICATE
);
setUpX509CertificateCredentials
(
fields
,
credentials
);
}
else
if
(
CollectionUtils
.
containsAny
(
fields
.
keySet
(),
EnumSet
.
of
(
BulkImportColumnType
.
MQTT_CLIENT_ID
,
BulkImportColumnType
.
MQTT_USER_NAME
,
BulkImportColumnType
.
MQTT_PASSWORD
)))
{
credentials
.
setCredentialsType
(
DeviceCredentialsType
.
MQTT_BASIC
);
setUpBasicMqttCredentials
(
fields
,
credentials
);
}
else
if
(
deviceId
!=
null
&&
!
fields
.
containsKey
(
BulkImportColumnType
.
ACCESS_TOKEN
))
{
credentials
=
deviceCredentialsService
.
findDeviceCredentialsByDeviceId
(
tenantId
,
deviceId
);
}
else
{
credentials
.
setCredentialsType
(
DeviceCredentialsType
.
ACCESS_TOKEN
);
setUpAccessTokenCredentials
(
fields
,
credentials
);
}
return
credentials
;
}
private
void
setUpAccessTokenCredentials
(
Map
<
BulkImportColumnType
,
String
>
fields
,
DeviceCredentials
credentials
)
{
credentials
.
setCredentialsId
(
Optional
.
ofNullable
(
fields
.
get
(
BulkImportColumnType
.
ACCESS_TOKEN
))
.
orElseGet
(()
->
StringUtils
.
randomAlphanumeric
(
20
)));
}
private
void
setUpBasicMqttCredentials
(
Map
<
BulkImportColumnType
,
String
>
fields
,
DeviceCredentials
credentials
)
{
BasicMqttCredentials
basicMqttCredentials
=
new
BasicMqttCredentials
();
basicMqttCredentials
.
setClientId
(
fields
.
get
(
BulkImportColumnType
.
MQTT_CLIENT_ID
));
basicMqttCredentials
.
setUserName
(
fields
.
get
(
BulkImportColumnType
.
MQTT_USER_NAME
));
basicMqttCredentials
.
setPassword
(
fields
.
get
(
BulkImportColumnType
.
MQTT_PASSWORD
));
credentials
.
setCredentialsValue
(
JacksonUtil
.
toString
(
basicMqttCredentials
));
}
private
void
setUpX509CertificateCredentials
(
Map
<
BulkImportColumnType
,
String
>
fields
,
DeviceCredentials
credentials
)
{
credentials
.
setCredentialsValue
(
fields
.
get
(
BulkImportColumnType
.
X509
));
}
private
void
setUpLwm2mCredentials
(
Map
<
BulkImportColumnType
,
String
>
fields
,
DeviceCredentials
credentials
)
throws
com
.
fasterxml
.
jackson
.
core
.
JsonProcessingException
{
ObjectNode
lwm2mCredentials
=
JacksonUtil
.
newObjectNode
();
Set
.
of
(
BulkImportColumnType
.
LWM2M_CLIENT_SECURITY_CONFIG_MODE
,
BulkImportColumnType
.
LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE
,
BulkImportColumnType
.
LWM2M_SERVER_SECURITY_MODE
).
stream
()
.
map
(
fields:
:
get
)
.
filter
(
Objects:
:
nonNull
)
.
forEach
(
securityMode
->
{
try
{
LwM2MSecurityMode
.
valueOf
(
securityMode
.
toUpperCase
());
}
catch
(
IllegalArgumentException
e
)
{
throw
new
DeviceCredentialsValidationException
(
"Unknown LwM2M security mode: "
+
securityMode
+
", (the mode should be: NO_SEC, PSK, RPK, X509)!"
);
}
});
ObjectNode
client
=
JacksonUtil
.
newObjectNode
();
setValues
(
client
,
fields
,
Set
.
of
(
BulkImportColumnType
.
LWM2M_CLIENT_SECURITY_CONFIG_MODE
,
BulkImportColumnType
.
LWM2M_CLIENT_ENDPOINT
,
BulkImportColumnType
.
LWM2M_CLIENT_IDENTITY
,
BulkImportColumnType
.
LWM2M_CLIENT_KEY
,
BulkImportColumnType
.
LWM2M_CLIENT_CERT
));
LwM2MClientCredential
lwM2MClientCredential
=
JacksonUtil
.
treeToValue
(
client
,
LwM2MClientCredential
.
class
);
// so that only fields needed for specific type of lwM2MClientCredentials were saved in json
lwm2mCredentials
.
set
(
"client"
,
JacksonUtil
.
valueToTree
(
lwM2MClientCredential
));
ObjectNode
bootstrapServer
=
JacksonUtil
.
newObjectNode
();
setValues
(
bootstrapServer
,
fields
,
Set
.
of
(
BulkImportColumnType
.
LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE
,
BulkImportColumnType
.
LWM2M_BOOTSTRAP_SERVER_PUBLIC_KEY_OR_ID
,
BulkImportColumnType
.
LWM2M_BOOTSTRAP_SERVER_SECRET_KEY
));
ObjectNode
lwm2mServer
=
JacksonUtil
.
newObjectNode
();
setValues
(
lwm2mServer
,
fields
,
Set
.
of
(
BulkImportColumnType
.
LWM2M_SERVER_SECURITY_MODE
,
BulkImportColumnType
.
LWM2M_SERVER_CLIENT_PUBLIC_KEY_OR_ID
,
BulkImportColumnType
.
LWM2M_SERVER_CLIENT_SECRET_KEY
));
ObjectNode
bootstrap
=
JacksonUtil
.
newObjectNode
();
bootstrap
.
set
(
"bootstrapServer"
,
bootstrapServer
);
bootstrap
.
set
(
"lwm2mServer"
,
lwm2mServer
);
lwm2mCredentials
.
set
(
"bootstrap"
,
bootstrap
);
credentials
.
setCredentialsValue
(
lwm2mCredentials
.
toString
());
}
private
DeviceProfile
setUpLwM2mDeviceProfile
(
TenantId
tenantId
,
Device
device
)
{
DeviceProfile
deviceProfile
=
deviceProfileService
.
findDeviceProfileByName
(
tenantId
,
device
.
getType
());
if
(
deviceProfile
!=
null
)
{
if
(
deviceProfile
.
getTransportType
()
!=
DeviceTransportType
.
LWM2M
)
{
deviceProfile
.
setTransportType
(
DeviceTransportType
.
LWM2M
);
deviceProfile
.
getProfileData
().
setTransportConfiguration
(
new
Lwm2mDeviceProfileTransportConfiguration
());
deviceProfile
=
deviceProfileService
.
saveDeviceProfile
(
deviceProfile
);
}
}
else
{
findOrCreateDeviceProfileLock
.
lock
();
try
{
deviceProfile
=
deviceProfileService
.
findDeviceProfileByName
(
tenantId
,
device
.
getType
());
if
(
deviceProfile
==
null
)
{
deviceProfile
=
new
DeviceProfile
();
deviceProfile
.
setTenantId
(
tenantId
);
deviceProfile
.
setType
(
DeviceProfileType
.
DEFAULT
);
deviceProfile
.
setName
(
device
.
getType
());
deviceProfile
.
setTransportType
(
DeviceTransportType
.
LWM2M
);
deviceProfile
.
setProvisionType
(
DeviceProfileProvisionType
.
DISABLED
);
Lwm2mDeviceProfileTransportConfiguration
transportConfiguration
=
new
Lwm2mDeviceProfileTransportConfiguration
();
transportConfiguration
.
setBootstrap
(
Collections
.
emptyList
());
transportConfiguration
.
setClientLwM2mSettings
(
new
OtherConfiguration
(
1
,
1
,
1
,
PowerMode
.
DRX
,
null
,
null
,
null
,
null
,
null
));
transportConfiguration
.
setObserveAttr
(
new
TelemetryMappingConfiguration
(
Collections
.
emptyMap
(),
Collections
.
emptySet
(),
Collections
.
emptySet
(),
Collections
.
emptySet
(),
Collections
.
emptyMap
()));
DeviceProfileData
deviceProfileData
=
new
DeviceProfileData
();
DefaultDeviceProfileConfiguration
configuration
=
new
DefaultDeviceProfileConfiguration
();
DisabledDeviceProfileProvisionConfiguration
provisionConfiguration
=
new
DisabledDeviceProfileProvisionConfiguration
(
null
);
deviceProfileData
.
setConfiguration
(
configuration
);
deviceProfileData
.
setTransportConfiguration
(
transportConfiguration
);
deviceProfileData
.
setProvisionConfiguration
(
provisionConfiguration
);
deviceProfile
.
setProfileData
(
deviceProfileData
);
deviceProfile
=
deviceProfileService
.
saveDeviceProfile
(
deviceProfile
);
}
}
finally
{
findOrCreateDeviceProfileLock
.
unlock
();
}
}
return
deviceProfile
;
}
private
void
setValues
(
ObjectNode
objectNode
,
Map
<
BulkImportColumnType
,
String
>
data
,
Collection
<
BulkImportColumnType
>
columns
)
{
for
(
BulkImportColumnType
column
:
columns
)
{
String
value
=
StringUtils
.
defaultString
(
data
.
get
(
column
),
column
.
getDefaultValue
());
if
(
value
!=
null
&&
column
.
getKey
()
!=
null
)
{
objectNode
.
set
(
column
.
getKey
(),
new
TextNode
(
value
));
}
}
}
@Override
protected
EntityType
getEntityType
()
{
return
EntityType
.
DEVICE
;
}
}
application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.device
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.JsonNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
com.google.common.util.concurrent.ListenableFuture
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.common.util.JacksonUtil
;
import
org.thingsboard.server.cluster.TbClusterService
;
import
org.thingsboard.server.common.data.DataConstants
;
import
org.thingsboard.server.common.data.Device
;
import
org.thingsboard.server.common.data.DeviceProfile
;
import
org.thingsboard.server.common.data.StringUtils
;
import
org.thingsboard.server.common.data.audit.ActionType
;
import
org.thingsboard.server.common.data.id.CustomerId
;
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.kv.BaseAttributeKvEntry
;
import
org.thingsboard.server.common.data.kv.StringDataEntry
;
import
org.thingsboard.server.common.data.security.DeviceCredentials
;
import
org.thingsboard.server.common.msg.TbMsg
;
import
org.thingsboard.server.common.msg.TbMsgMetaData
;
import
org.thingsboard.server.common.msg.queue.ServiceType
;
import
org.thingsboard.server.common.msg.queue.TopicPartitionInfo
;
import
org.thingsboard.server.dao.attributes.AttributesService
;
import
org.thingsboard.server.dao.audit.AuditLogService
;
import
org.thingsboard.server.dao.device.DeviceCredentialsService
;
import
org.thingsboard.server.dao.device.DeviceDao
;
import
org.thingsboard.server.dao.device.DeviceProfileDao
;
import
org.thingsboard.server.dao.device.DeviceProvisionService
;
import
org.thingsboard.server.dao.device.DeviceService
;
import
org.thingsboard.server.dao.device.provision.ProvisionFailedException
;
import
org.thingsboard.server.dao.device.provision.ProvisionRequest
;
import
org.thingsboard.server.dao.device.provision.ProvisionResponse
;
import
org.thingsboard.server.dao.device.provision.ProvisionResponseStatus
;
import
org.thingsboard.server.gen.transport.TransportProtos
;
import
org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg
;
import
org.thingsboard.server.queue.TbQueueCallback
;
import
org.thingsboard.server.queue.TbQueueProducer
;
import
org.thingsboard.server.queue.common.TbProtoQueueMsg
;
import
org.thingsboard.server.queue.discovery.PartitionService
;
import
org.thingsboard.server.queue.provider.TbQueueProducerProvider
;
import
org.thingsboard.server.queue.util.TbCoreComponent
;
import
org.thingsboard.server.service.state.DeviceStateService
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Optional
;
import
java.util.concurrent.ExecutionException
;
@Service
@Slf4j
@TbCoreComponent
public
class
DeviceProvisionServiceImpl
implements
DeviceProvisionService
{
protected
TbQueueProducer
<
TbProtoQueueMsg
<
ToRuleEngineMsg
>>
ruleEngineMsgProducer
;
private
static
final
String
DEVICE_PROVISION_STATE
=
"provisionState"
;
private
static
final
String
PROVISIONED_STATE
=
"provisioned"
;
@Autowired
TbClusterService
clusterService
;
@Autowired
DeviceDao
deviceDao
;
@Autowired
DeviceProfileDao
deviceProfileDao
;
@Autowired
DeviceService
deviceService
;
@Autowired
DeviceCredentialsService
deviceCredentialsService
;
@Autowired
AttributesService
attributesService
;
@Autowired
DeviceStateService
deviceStateService
;
@Autowired
AuditLogService
auditLogService
;
@Autowired
PartitionService
partitionService
;
public
DeviceProvisionServiceImpl
(
TbQueueProducerProvider
producerProvider
)
{
ruleEngineMsgProducer
=
producerProvider
.
getRuleEngineMsgProducer
();
}
@Override
public
ProvisionResponse
provisionDevice
(
ProvisionRequest
provisionRequest
)
{
String
provisionRequestKey
=
provisionRequest
.
getCredentials
().
getProvisionDeviceKey
();
String
provisionRequestSecret
=
provisionRequest
.
getCredentials
().
getProvisionDeviceSecret
();
if
(!
StringUtils
.
isEmpty
(
provisionRequest
.
getDeviceName
()))
{
provisionRequest
.
setDeviceName
(
provisionRequest
.
getDeviceName
().
trim
());
if
(
StringUtils
.
isEmpty
(
provisionRequest
.
getDeviceName
()))
{
log
.
warn
(
"Provision request contains empty device name!"
);
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
FAILURE
.
name
());
}
}
if
(
StringUtils
.
isEmpty
(
provisionRequestKey
)
||
StringUtils
.
isEmpty
(
provisionRequestSecret
))
{
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
NOT_FOUND
.
name
());
}
DeviceProfile
targetProfile
=
deviceProfileDao
.
findByProvisionDeviceKey
(
provisionRequestKey
);
if
(
targetProfile
==
null
||
targetProfile
.
getProfileData
().
getProvisionConfiguration
()
==
null
||
targetProfile
.
getProfileData
().
getProvisionConfiguration
().
getProvisionDeviceSecret
()
==
null
)
{
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
NOT_FOUND
.
name
());
}
Device
targetDevice
=
deviceDao
.
findDeviceByTenantIdAndName
(
targetProfile
.
getTenantId
().
getId
(),
provisionRequest
.
getDeviceName
()).
orElse
(
null
);
switch
(
targetProfile
.
getProvisionType
())
{
case
ALLOW_CREATE_NEW_DEVICES:
if
(
targetProfile
.
getProfileData
().
getProvisionConfiguration
().
getProvisionDeviceSecret
().
equals
(
provisionRequestSecret
))
{
if
(
targetDevice
!=
null
)
{
log
.
warn
(
"[{}] The device is present and could not be provisioned once more!"
,
targetDevice
.
getName
());
notify
(
targetDevice
,
provisionRequest
,
DataConstants
.
PROVISION_FAILURE
,
false
);
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
FAILURE
.
name
());
}
else
{
return
createDevice
(
provisionRequest
,
targetProfile
);
}
}
break
;
case
CHECK_PRE_PROVISIONED_DEVICES:
if
(
targetProfile
.
getProfileData
().
getProvisionConfiguration
().
getProvisionDeviceSecret
().
equals
(
provisionRequestSecret
))
{
if
(
targetDevice
!=
null
&&
targetDevice
.
getDeviceProfileId
().
equals
(
targetProfile
.
getId
()))
{
return
processProvision
(
targetDevice
,
provisionRequest
);
}
else
{
log
.
warn
(
"[{}] Failed to find pre provisioned device!"
,
provisionRequest
.
getDeviceName
());
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
FAILURE
.
name
());
}
}
break
;
}
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
NOT_FOUND
.
name
());
}
private
ProvisionResponse
processProvision
(
Device
device
,
ProvisionRequest
provisionRequest
)
{
try
{
Optional
<
AttributeKvEntry
>
provisionState
=
attributesService
.
find
(
device
.
getTenantId
(),
device
.
getId
(),
DataConstants
.
SERVER_SCOPE
,
DEVICE_PROVISION_STATE
).
get
();
if
(
provisionState
!=
null
&&
provisionState
.
isPresent
()
&&
!
provisionState
.
get
().
getValueAsString
().
equals
(
PROVISIONED_STATE
))
{
notify
(
device
,
provisionRequest
,
DataConstants
.
PROVISION_FAILURE
,
false
);
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
FAILURE
.
name
());
}
else
{
saveProvisionStateAttribute
(
device
).
get
();
notify
(
device
,
provisionRequest
,
DataConstants
.
PROVISION_SUCCESS
,
true
);
}
}
catch
(
InterruptedException
|
ExecutionException
e
)
{
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
FAILURE
.
name
());
}
return
new
ProvisionResponse
(
deviceCredentialsService
.
findDeviceCredentialsByDeviceId
(
device
.
getTenantId
(),
device
.
getId
()),
ProvisionResponseStatus
.
SUCCESS
);
}
private
ProvisionResponse
createDevice
(
ProvisionRequest
provisionRequest
,
DeviceProfile
profile
)
{
return
processCreateDevice
(
provisionRequest
,
profile
);
}
private
void
notify
(
Device
device
,
ProvisionRequest
provisionRequest
,
String
type
,
boolean
success
)
{
pushProvisionEventToRuleEngine
(
provisionRequest
,
device
,
type
);
logAction
(
device
.
getTenantId
(),
device
.
getCustomerId
(),
device
,
success
,
provisionRequest
);
}
private
ProvisionResponse
processCreateDevice
(
ProvisionRequest
provisionRequest
,
DeviceProfile
profile
)
{
try
{
if
(
StringUtils
.
isEmpty
(
provisionRequest
.
getDeviceName
()))
{
String
newDeviceName
=
StringUtils
.
randomAlphanumeric
(
20
);
log
.
info
(
"Device name not found in provision request. Generated name is: {}"
,
newDeviceName
);
provisionRequest
.
setDeviceName
(
newDeviceName
);
}
Device
savedDevice
=
deviceService
.
saveDevice
(
provisionRequest
,
profile
);
clusterService
.
onDeviceUpdated
(
savedDevice
,
null
);
saveProvisionStateAttribute
(
savedDevice
).
get
();
pushDeviceCreatedEventToRuleEngine
(
savedDevice
);
notify
(
savedDevice
,
provisionRequest
,
DataConstants
.
PROVISION_SUCCESS
,
true
);
return
new
ProvisionResponse
(
getDeviceCredentials
(
savedDevice
),
ProvisionResponseStatus
.
SUCCESS
);
}
catch
(
Exception
e
)
{
log
.
warn
(
"[{}] Error during device creation from provision request: [{}]"
,
provisionRequest
.
getDeviceName
(),
provisionRequest
,
e
);
Device
device
=
deviceService
.
findDeviceByTenantIdAndName
(
profile
.
getTenantId
(),
provisionRequest
.
getDeviceName
());
if
(
device
!=
null
)
{
notify
(
device
,
provisionRequest
,
DataConstants
.
PROVISION_FAILURE
,
false
);
}
throw
new
ProvisionFailedException
(
ProvisionResponseStatus
.
FAILURE
.
name
());
}
}
private
ListenableFuture
<
List
<
String
>>
saveProvisionStateAttribute
(
Device
device
)
{
return
attributesService
.
save
(
device
.
getTenantId
(),
device
.
getId
(),
DataConstants
.
SERVER_SCOPE
,
Collections
.
singletonList
(
new
BaseAttributeKvEntry
(
new
StringDataEntry
(
DEVICE_PROVISION_STATE
,
PROVISIONED_STATE
),
System
.
currentTimeMillis
())));
}
private
DeviceCredentials
getDeviceCredentials
(
Device
device
)
{
return
deviceCredentialsService
.
findDeviceCredentialsByDeviceId
(
device
.
getTenantId
(),
device
.
getId
());
}
private
void
pushProvisionEventToRuleEngine
(
ProvisionRequest
request
,
Device
device
,
String
type
)
{
try
{
JsonNode
entityNode
=
JacksonUtil
.
valueToTree
(
request
);
TbMsg
msg
=
TbMsg
.
newMsg
(
type
,
device
.
getId
(),
device
.
getCustomerId
(),
createTbMsgMetaData
(
device
),
JacksonUtil
.
toString
(
entityNode
));
sendToRuleEngine
(
device
.
getTenantId
(),
msg
,
null
);
}
catch
(
IllegalArgumentException
e
)
{
log
.
warn
(
"[{}] Failed to push device action to rule engine: {}"
,
device
.
getId
(),
type
,
e
);
}
}
private
void
pushDeviceCreatedEventToRuleEngine
(
Device
device
)
{
try
{
ObjectNode
entityNode
=
JacksonUtil
.
OBJECT_MAPPER
.
valueToTree
(
device
);
TbMsg
msg
=
TbMsg
.
newMsg
(
DataConstants
.
ENTITY_CREATED
,
device
.
getId
(),
device
.
getCustomerId
(),
createTbMsgMetaData
(
device
),
JacksonUtil
.
OBJECT_MAPPER
.
writeValueAsString
(
entityNode
));
sendToRuleEngine
(
device
.
getTenantId
(),
msg
,
null
);
}
catch
(
JsonProcessingException
|
IllegalArgumentException
e
)
{
log
.
warn
(
"[{}] Failed to push device action to rule engine: {}"
,
device
.
getId
(),
DataConstants
.
ENTITY_CREATED
,
e
);
}
}
protected
void
sendToRuleEngine
(
TenantId
tenantId
,
TbMsg
tbMsg
,
TbQueueCallback
callback
)
{
TopicPartitionInfo
tpi
=
partitionService
.
resolve
(
ServiceType
.
TB_RULE_ENGINE
,
tenantId
,
tbMsg
.
getOriginator
());
TransportProtos
.
ToRuleEngineMsg
msg
=
TransportProtos
.
ToRuleEngineMsg
.
newBuilder
().
setTbMsg
(
TbMsg
.
toByteString
(
tbMsg
))
.
setTenantIdMSB
(
tenantId
.
getId
().
getMostSignificantBits
())
.
setTenantIdLSB
(
tenantId
.
getId
().
getLeastSignificantBits
()).
build
();
ruleEngineMsgProducer
.
send
(
tpi
,
new
TbProtoQueueMsg
<>(
tbMsg
.
getId
(),
msg
),
callback
);
}
private
TbMsgMetaData
createTbMsgMetaData
(
Device
device
)
{
TbMsgMetaData
metaData
=
new
TbMsgMetaData
();
metaData
.
putValue
(
"tenantId"
,
device
.
getTenantId
().
toString
());
return
metaData
;
}
private
void
logAction
(
TenantId
tenantId
,
CustomerId
customerId
,
Device
device
,
boolean
success
,
ProvisionRequest
provisionRequest
)
{
ActionType
actionType
=
success
?
ActionType
.
PROVISION_SUCCESS
:
ActionType
.
PROVISION_FAILURE
;
auditLogService
.
logEntityAction
(
tenantId
,
customerId
,
new
UserId
(
UserId
.
NULL_UUID
),
device
.
getName
(),
device
.
getId
(),
device
,
actionType
,
null
,
provisionRequest
);
}
}
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.edge
;
import
com.fasterxml.jackson.databind.JsonNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
com.google.common.util.concurrent.FutureCallback
;
import
com.google.common.util.concurrent.Futures
;
import
com.google.common.util.concurrent.ListenableFuture
;
import
lombok.extern.slf4j.Slf4j
;
import
org.checkerframework.checker.nullness.qual.Nullable
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.common.util.JacksonUtil
;
import
org.thingsboard.common.util.ThingsBoardThreadFactory
;
import
org.thingsboard.server.cluster.TbClusterService
;
import
org.thingsboard.server.common.data.EdgeUtils
;
import
org.thingsboard.server.common.data.edge.Edge
;
import
org.thingsboard.server.common.data.edge.EdgeEvent
;
import
org.thingsboard.server.common.data.edge.EdgeEventActionType
;
import
org.thingsboard.server.common.data.edge.EdgeEventType
;
import
org.thingsboard.server.common.data.id.EdgeId
;
import
org.thingsboard.server.common.data.id.EntityId
;
import
org.thingsboard.server.common.data.id.RuleChainId
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.msg.queue.TbCallback
;
import
org.thingsboard.server.dao.edge.EdgeEventService
;
import
org.thingsboard.server.dao.edge.EdgeService
;
import
org.thingsboard.server.gen.transport.TransportProtos
;
import
org.thingsboard.server.queue.util.TbCoreComponent
;
import
org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.AssetProfileEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.QueueEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.UserEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.WidgetBundleEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.WidgetTypeEdgeProcessor
;
import
javax.annotation.PostConstruct
;
import
javax.annotation.PreDestroy
;
import
java.util.UUID
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
@Service
@TbCoreComponent
@Slf4j
public
class
DefaultEdgeNotificationService
implements
EdgeNotificationService
{
public
static
final
String
EDGE_IS_ROOT_BODY_KEY
=
"isRoot"
;
@Autowired
private
EdgeService
edgeService
;
@Autowired
private
EdgeEventService
edgeEventService
;
@Autowired
private
TbClusterService
clusterService
;
@Autowired
private
EdgeProcessor
edgeProcessor
;
@Autowired
private
AssetEdgeProcessor
assetProcessor
;
@Autowired
private
DeviceEdgeProcessor
deviceProcessor
;
@Autowired
private
EntityViewEdgeProcessor
entityViewProcessor
;
@Autowired
private
DashboardEdgeProcessor
dashboardProcessor
;
@Autowired
private
RuleChainEdgeProcessor
ruleChainProcessor
;
@Autowired
private
UserEdgeProcessor
userProcessor
;
@Autowired
private
CustomerEdgeProcessor
customerProcessor
;
@Autowired
private
DeviceProfileEdgeProcessor
deviceProfileProcessor
;
@Autowired
private
AssetProfileEdgeProcessor
assetProfileProcessor
;
@Autowired
private
OtaPackageEdgeProcessor
otaPackageProcessor
;
@Autowired
private
WidgetBundleEdgeProcessor
widgetBundleProcessor
;
@Autowired
private
WidgetTypeEdgeProcessor
widgetTypeProcessor
;
@Autowired
private
QueueEdgeProcessor
queueProcessor
;
@Autowired
private
AlarmEdgeProcessor
alarmProcessor
;
@Autowired
private
RelationEdgeProcessor
relationProcessor
;
private
ExecutorService
dbCallBackExecutor
;
@PostConstruct
public
void
initExecutor
()
{
dbCallBackExecutor
=
Executors
.
newSingleThreadExecutor
(
ThingsBoardThreadFactory
.
forName
(
"edge-notifications"
));
}
@PreDestroy
public
void
shutdownExecutor
()
{
if
(
dbCallBackExecutor
!=
null
)
{
dbCallBackExecutor
.
shutdownNow
();
}
}
@Override
public
Edge
setEdgeRootRuleChain
(
TenantId
tenantId
,
Edge
edge
,
RuleChainId
ruleChainId
)
throws
Exception
{
edge
.
setRootRuleChainId
(
ruleChainId
);
Edge
savedEdge
=
edgeService
.
saveEdge
(
edge
);
ObjectNode
isRootBody
=
JacksonUtil
.
OBJECT_MAPPER
.
createObjectNode
();
isRootBody
.
put
(
EDGE_IS_ROOT_BODY_KEY
,
Boolean
.
TRUE
);
saveEdgeEvent
(
tenantId
,
edge
.
getId
(),
EdgeEventType
.
RULE_CHAIN
,
EdgeEventActionType
.
UPDATED
,
ruleChainId
,
isRootBody
).
get
();
return
savedEdge
;
}
private
ListenableFuture
<
Void
>
saveEdgeEvent
(
TenantId
tenantId
,
EdgeId
edgeId
,
EdgeEventType
type
,
EdgeEventActionType
action
,
EntityId
entityId
,
JsonNode
body
)
{
log
.
debug
(
"Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]"
,
tenantId
,
edgeId
,
type
,
action
,
entityId
,
body
);
EdgeEvent
edgeEvent
=
EdgeUtils
.
constructEdgeEvent
(
tenantId
,
edgeId
,
type
,
action
,
entityId
,
body
);
return
Futures
.
transform
(
edgeEventService
.
saveAsync
(
edgeEvent
),
unused
->
{
clusterService
.
onEdgeEventUpdate
(
tenantId
,
edgeId
);
return
null
;
},
dbCallBackExecutor
);
}
@Override
public
void
pushNotificationToEdge
(
TransportProtos
.
EdgeNotificationMsgProto
edgeNotificationMsg
,
TbCallback
callback
)
{
log
.
debug
(
"Pushing notification to edge {}"
,
edgeNotificationMsg
);
try
{
TenantId
tenantId
=
TenantId
.
fromUUID
(
new
UUID
(
edgeNotificationMsg
.
getTenantIdMSB
(),
edgeNotificationMsg
.
getTenantIdLSB
()));
EdgeEventType
type
=
EdgeEventType
.
valueOf
(
edgeNotificationMsg
.
getType
());
ListenableFuture
<
Void
>
future
;
switch
(
type
)
{
case
EDGE:
future
=
edgeProcessor
.
processEdgeNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
ASSET:
future
=
assetProcessor
.
processAssetNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
DEVICE:
future
=
deviceProcessor
.
processDeviceNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
ENTITY_VIEW:
future
=
entityViewProcessor
.
processEntityViewNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
DASHBOARD:
future
=
dashboardProcessor
.
processDashboardNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
RULE_CHAIN:
future
=
ruleChainProcessor
.
processRuleChainNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
USER:
future
=
userProcessor
.
processUserNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
CUSTOMER:
future
=
customerProcessor
.
processCustomerNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
DEVICE_PROFILE:
future
=
deviceProfileProcessor
.
processDeviceProfileNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
ASSET_PROFILE:
future
=
assetProfileProcessor
.
processAssetProfileNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
OTA_PACKAGE:
future
=
otaPackageProcessor
.
processOtaPackageNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
WIDGETS_BUNDLE:
future
=
widgetBundleProcessor
.
processWidgetsBundleNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
WIDGET_TYPE:
future
=
widgetTypeProcessor
.
processWidgetTypeNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
QUEUE:
future
=
queueProcessor
.
processQueueNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
ALARM:
future
=
alarmProcessor
.
processAlarmNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
case
RELATION:
future
=
relationProcessor
.
processRelationNotification
(
tenantId
,
edgeNotificationMsg
);
break
;
default
:
log
.
warn
(
"Edge event type [{}] is not designed to be pushed to edge"
,
type
);
future
=
Futures
.
immediateFuture
(
null
);
}
Futures
.
addCallback
(
future
,
new
FutureCallback
<>()
{
@Override
public
void
onSuccess
(
@Nullable
Void
unused
)
{
callback
.
onSuccess
();
}
@Override
public
void
onFailure
(
Throwable
throwable
)
{
callBackFailure
(
edgeNotificationMsg
,
callback
,
throwable
);
}
},
dbCallBackExecutor
);
}
catch
(
Exception
e
)
{
callBackFailure
(
edgeNotificationMsg
,
callback
,
e
);
}
}
private
void
callBackFailure
(
TransportProtos
.
EdgeNotificationMsgProto
edgeNotificationMsg
,
TbCallback
callback
,
Throwable
throwable
)
{
log
.
error
(
"Can't push to edge updates, edgeNotificationMsg [{}]"
,
edgeNotificationMsg
,
throwable
);
callback
.
onFailure
(
throwable
);
}
}
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.edge
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
com.fasterxml.jackson.databind.node.TextNode
;
import
lombok.RequiredArgsConstructor
;
import
lombok.SneakyThrows
;
import
org.springframework.stereotype.Service
;
import
org.thingsboard.server.common.data.EntityType
;
import
org.thingsboard.server.common.data.edge.Edge
;
import
org.thingsboard.server.common.data.id.TenantId
;
import
org.thingsboard.server.common.data.rule.RuleChain
;
import
org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType
;
import
org.thingsboard.server.dao.edge.EdgeService
;
import
org.thingsboard.server.dao.rule.RuleChainService
;
import
org.thingsboard.server.queue.util.TbCoreComponent
;
import
org.thingsboard.server.service.entitiy.edge.TbEdgeService
;
import
org.thingsboard.server.service.security.model.SecurityUser
;
import
org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService
;
import
java.util.Map
;
import
java.util.Optional
;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public
class
EdgeBulkImportService
extends
AbstractBulkImportService
<
Edge
>
{
private
final
EdgeService
edgeService
;
private
final
TbEdgeService
tbEdgeService
;
private
final
RuleChainService
ruleChainService
;
@Override
protected
void
setEntityFields
(
Edge
entity
,
Map
<
BulkImportColumnType
,
String
>
fields
)
{
ObjectNode
additionalInfo
=
getOrCreateAdditionalInfoObj
(
entity
);
fields
.
forEach
((
columnType
,
value
)
->
{
switch
(
columnType
)
{
case
NAME:
entity
.
setName
(
value
);
break
;
case
TYPE:
entity
.
setType
(
value
);
break
;
case
LABEL:
entity
.
setLabel
(
value
);
break
;
case
DESCRIPTION:
additionalInfo
.
set
(
"description"
,
new
TextNode
(
value
));
break
;
case
ROUTING_KEY:
entity
.
setRoutingKey
(
value
);
break
;
case
SECRET:
entity
.
setSecret
(
value
);
break
;
}
});
entity
.
setAdditionalInfo
(
additionalInfo
);
}
@SneakyThrows
@Override
protected
Edge
saveEntity
(
SecurityUser
user
,
Edge
entity
,
Map
<
BulkImportColumnType
,
String
>
fields
)
{
RuleChain
edgeTemplateRootRuleChain
=
ruleChainService
.
getEdgeTemplateRootRuleChain
(
user
.
getTenantId
());
return
tbEdgeService
.
save
(
entity
,
edgeTemplateRootRuleChain
,
user
);
}
@Override
protected
Edge
findOrCreateEntity
(
TenantId
tenantId
,
String
name
)
{
return
Optional
.
ofNullable
(
edgeService
.
findEdgeByTenantIdAndName
(
tenantId
,
name
))
.
orElseGet
(
Edge:
:
new
);
}
@Override
protected
void
setOwners
(
Edge
entity
,
SecurityUser
user
)
{
entity
.
setTenantId
(
user
.
getTenantId
());
entity
.
setCustomerId
(
user
.
getCustomerId
());
}
@Override
protected
EntityType
getEntityType
()
{
return
EntityType
.
EDGE
;
}
}
application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
0 → 100644
View file @
98ad3b98
/**
* Copyright © 2016-2022 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.edge
;
import
freemarker.template.Configuration
;
import
lombok.Data
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.context.annotation.Lazy
;
import
org.springframework.stereotype.Component
;
import
org.thingsboard.server.cluster.TbClusterService
;
import
org.thingsboard.server.dao.asset.AssetProfileService
;
import
org.thingsboard.server.dao.asset.AssetService
;
import
org.thingsboard.server.dao.attributes.AttributesService
;
import
org.thingsboard.server.dao.customer.CustomerService
;
import
org.thingsboard.server.dao.dashboard.DashboardService
;
import
org.thingsboard.server.dao.device.DeviceProfileService
;
import
org.thingsboard.server.dao.device.DeviceService
;
import
org.thingsboard.server.dao.edge.EdgeEventService
;
import
org.thingsboard.server.dao.edge.EdgeService
;
import
org.thingsboard.server.dao.entityview.EntityViewService
;
import
org.thingsboard.server.dao.ota.OtaPackageService
;
import
org.thingsboard.server.dao.queue.QueueService
;
import
org.thingsboard.server.dao.rule.RuleChainService
;
import
org.thingsboard.server.dao.settings.AdminSettingsService
;
import
org.thingsboard.server.dao.user.UserService
;
import
org.thingsboard.server.dao.widget.WidgetsBundleService
;
import
org.thingsboard.server.queue.util.TbCoreComponent
;
import
org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings
;
import
org.thingsboard.server.service.edge.rpc.constructor.EdgeMsgConstructor
;
import
org.thingsboard.server.service.edge.rpc.processor.AdminSettingsEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.AssetProfileEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.QueueEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.TelemetryEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.UserEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.WidgetBundleEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.processor.WidgetTypeEdgeProcessor
;
import
org.thingsboard.server.service.edge.rpc.sync.EdgeRequestsService
;
import
org.thingsboard.server.service.executors.DbCallbackExecutorService
;
import
org.thingsboard.server.service.executors.GrpcCallbackExecutorService
;
@Component
@TbCoreComponent
@Data
@Lazy
public
class
EdgeContextComponent
{
@Autowired
private
TbClusterService
clusterService
;
@Autowired
private
EdgeService
edgeService
;
@Autowired
private
EdgeEventService
edgeEventService
;
@Autowired
private
AdminSettingsService
adminSettingsService
;
@Autowired
private
Configuration
freemarkerConfig
;
@Autowired
private
DeviceService
deviceService
;
@Autowired
private
AssetService
assetService
;
@Autowired
private
EntityViewService
entityViewService
;
@Autowired
private
DeviceProfileService
deviceProfileService
;
@Autowired
private
AssetProfileService
assetProfileService
;
@Autowired
private
AttributesService
attributesService
;
@Autowired
private
DashboardService
dashboardService
;
@Autowired
private
RuleChainService
ruleChainService
;
@Autowired
private
UserService
userService
;
@Autowired
private
CustomerService
customerService
;
@Autowired
private
WidgetsBundleService
widgetsBundleService
;
@Autowired
private
EdgeRequestsService
edgeRequestsService
;
@Autowired
private
OtaPackageService
otaPackageService
;
@Autowired
private
QueueService
queueService
;
@Autowired
private
AlarmEdgeProcessor
alarmProcessor
;
@Autowired
private
DeviceProfileEdgeProcessor
deviceProfileProcessor
;
@Autowired
private
AssetProfileEdgeProcessor
assetProfileProcessor
;
@Autowired
private
EdgeProcessor
edgeProcessor
;
@Autowired
private
DeviceEdgeProcessor
deviceProcessor
;
@Autowired
private
AssetEdgeProcessor
assetProcessor
;
@Autowired
private
EntityViewEdgeProcessor
entityViewProcessor
;
@Autowired
private
UserEdgeProcessor
userProcessor
;
@Autowired
private
RelationEdgeProcessor
relationProcessor
;
@Autowired
private
TelemetryEdgeProcessor
telemetryProcessor
;
@Autowired
private
DashboardEdgeProcessor
dashboardProcessor
;
@Autowired
private
RuleChainEdgeProcessor
ruleChainProcessor
;
@Autowired
private
CustomerEdgeProcessor
customerProcessor
;
@Autowired
private
WidgetBundleEdgeProcessor
widgetBundleProcessor
;
@Autowired
private
WidgetTypeEdgeProcessor
widgetTypeProcessor
;
@Autowired
private
AdminSettingsEdgeProcessor
adminSettingsProcessor
;
@Autowired
private
OtaPackageEdgeProcessor
otaPackageEdgeProcessor
;
@Autowired
private
QueueEdgeProcessor
queueEdgeProcessor
;
@Autowired
private
EdgeMsgConstructor
edgeMsgConstructor
;
@Autowired
private
EdgeEventStorageSettings
edgeEventStorageSettings
;
@Autowired
private
DbCallbackExecutorService
dbCallbackExecutor
;
@Autowired
private
GrpcCallbackExecutorService
grpcCallbackExecutorService
;
}
Prev
1
…
6
7
8
9
10
11
12
13
14
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment