From c22bf33defb1982446789d9b4ce6b05a8849dfca Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 1 Dec 2016 11:38:15 +0200 Subject: [PATCH] Initial commit --- .gitignore | 32 + LICENSE | 201 +++++ README.md | 29 + application/.gitignore | 1 + application/build.gradle | 134 ++++ application/pom.xml | 417 ++++++++++ application/src/main/conf/logback.xml | 44 ++ application/src/main/conf/thingsboard.conf | 19 + .../server/ThingsboardServerApplication.java | 46 ++ .../server/actors/ActorSystemContext.java | 191 +++++ .../server/actors/app/AppActor.java | 226 ++++++ .../server/actors/device/DeviceActor.java | 89 +++ .../device/DeviceActorMessageProcessor.java | 366 +++++++++ .../server/actors/device/SessionInfo.java | 32 + .../device/ToDeviceRpcRequestMetadata.java | 28 + .../server/actors/plugin/PluginActor.java | 151 ++++ .../plugin/PluginActorMessageProcessor.java | 233 ++++++ .../actors/plugin/PluginCallbackMessage.java | 53 ++ .../plugin/PluginProcessingContext.java | 306 ++++++++ .../actors/plugin/PluginTerminationMsg.java | 30 + .../actors/plugin/RuleToPluginMsgWrapper.java | 66 ++ .../plugin/SharedPluginProcessingContext.java | 111 +++ .../actors/plugin/TimeoutScheduler.java | 27 + .../actors/rpc/BasicRpcSessionListener.java | 170 +++++ .../server/actors/rpc/RpcBroadcastMsg.java | 27 + .../server/actors/rpc/RpcManagerActor.java | 192 +++++ .../server/actors/rpc/RpcSessionActor.java | 118 +++ .../actors/rpc/RpcSessionClosedMsg.java | 29 + .../actors/rpc/RpcSessionConnectedMsg.java | 31 + .../rpc/RpcSessionCreateRequestMsg.java | 35 + .../actors/rpc/RpcSessionDisconnectedMsg.java | 29 + .../server/actors/rpc/RpcSessionTellMsg.java | 29 + .../server/actors/rpc/SessionActorInfo.java | 30 + .../actors/rule/ChainProcessingContext.java | 104 +++ .../actors/rule/ChainProcessingMetaData.java | 42 ++ .../actors/rule/ComplexRuleActorChain.java | 43 ++ .../actors/rule/CompoundRuleActorChain.java | 20 + .../server/actors/rule/RuleActor.java | 90 +++ .../server/actors/rule/RuleActorChain.java | 24 + .../rule/RuleActorMessageProcessor.java | 339 +++++++++ .../server/actors/rule/RuleActorMetaData.java | 107 +++ .../rule/RuleContextAwareMsgProcessor.java | 33 + .../actors/rule/RuleProcessingContext.java | 89 +++ .../server/actors/rule/RuleProcessingMsg.java | 31 + .../actors/rule/RuleTerminationMsg.java | 30 + .../actors/rule/RuleToPluginTimeoutMsg.java | 36 + .../server/actors/rule/RulesProcessedMsg.java | 34 + .../actors/rule/SimpleRuleActorChain.java | 40 + .../server/actors/service/ActorService.java | 31 + .../server/actors/service/ComponentActor.java | 169 +++++ .../actors/service/ContextAwareActor.java | 31 + .../actors/service/ContextBasedCreator.java | 32 + .../actors/service/DefaultActorService.java | 234 ++++++ .../actors/service/RestMsgProcessor.java | 24 + .../actors/service/WebSocketMsgProcessor.java | 24 + .../actors/session/ASyncMsgProcessor.java | 133 ++++ .../AbstractSessionActorMsgProcessor.java | 119 +++ .../server/actors/session/SessionActor.java | 139 ++++ .../actors/session/SessionManagerActor.java | 154 ++++ .../actors/session/SessionTerminationMsg.java | 26 + .../actors/session/SyncMsgProcessor.java | 93 +++ .../AbstractContextAwareMsgProcessor.java | 135 ++++ .../actors/shared/ActorTerminationMsg.java | 31 + .../actors/shared/ComponentMsgProcessor.java | 55 ++ .../actors/shared/SessionTimeoutMsg.java | 29 + .../actors/shared/plugin/PluginManager.java | 83 ++ .../shared/plugin/SystemPluginManager.java | 41 + .../shared/plugin/TenantPluginManager.java | 41 + .../actors/shared/rule/RuleManager.java | 126 ++++ .../actors/shared/rule/SystemRuleManager.java | 35 + .../actors/shared/rule/TenantRuleManager.java | 34 + .../server/actors/stats/StatsActor.java | 75 ++ .../server/actors/stats/StatsPersistMsg.java | 32 + .../server/actors/stats/StatsPersistTick.java | 18 + .../actors/tenant/RuleChainDeviceMsg.java | 40 + .../server/actors/tenant/TenantActor.java | 184 +++++ .../CurrentServerInstanceService.java | 55 ++ .../cluster/discovery/DiscoveryService.java | 37 + .../discovery/DiscoveryServiceListener.java | 28 + .../discovery/DummyDiscoveryService.java | 75 ++ .../cluster/discovery/ServerInstance.java | 52 ++ .../discovery/ServerInstanceService.java | 24 + .../cluster/discovery/ZkDiscoveryService.java | 207 +++++ .../routing/ClusterRoutingService.java | 33 + .../ConsistentClusterRoutingService.java | 142 ++++ .../cluster/rpc/ClusterGrpcService.java | 273 +++++++ .../cluster/rpc/ClusterRpcService.java | 53 ++ .../service/cluster/rpc/GrpcSession.java | 130 ++++ .../cluster/rpc/GrpcSessionListener.java | 45 ++ .../service/cluster/rpc/RpcMsgListener.java | 50 ++ .../cluster/rpc/RpcSessionCreationFuture.java | 63 ++ .../AnnotationComponentDiscoveryService.java | 190 +++++ .../component/ComponentDiscoveryService.java | 35 + .../thingsboard/server/utils/MiscUtils.java | 49 ++ application/src/main/proto/cluster.proto | 97 +++ application/src/main/proto/discovery.proto | 26 + .../src/main/resources/actor-system.conf | 163 ++++ application/src/main/resources/banner.txt | 3 + .../main/resources/i18n/messages.properties | 5 + application/src/main/resources/logback.xml | 35 + .../resources/templates/account.activated.vm | 122 +++ .../main/resources/templates/activation.vm | 122 +++ .../resources/templates/password.was.reset.vm | 122 +++ .../resources/templates/reset.password.vm | 122 +++ .../src/main/resources/templates/test.vm | 112 +++ .../src/main/resources/thingsboard.yml | 176 +++++ .../src/main/scripts/control/deb/postinst | 6 + .../src/main/scripts/control/deb/postrm | 3 + .../src/main/scripts/control/deb/preinst | 18 + .../src/main/scripts/control/deb/prerm | 5 + .../src/main/scripts/control/rpm/postinst | 9 + .../src/main/scripts/control/rpm/postrm | 6 + .../src/main/scripts/control/rpm/preinst | 6 + .../src/main/scripts/control/rpm/prerm | 6 + .../main/scripts/control/thingsboard.service | 11 + .../server/ThingsboardApplicationTests.java | 49 ++ .../server/actors/ActorsTestSuite.java | 27 + .../actors/DefaultActorServiceTest.java | 242 ++++++ .../server/actors/DummySessionID.java | 63 ++ .../controller/AbstractControllerTest.java | 375 +++++++++ .../controller/AdminControllerTest.java | 146 ++++ .../server/controller/AuthControllerTest.java | 79 ++ .../ComponentDescriptorControllerTest.java | 106 +++ .../controller/ControllerTestSuite.java | 39 + .../controller/CustomerControllerTest.java | 366 +++++++++ .../controller/DashboardControllerTest.java | 431 +++++++++++ .../controller/DeviceControllerTest.java | 549 ++++++++++++++ .../controller/PluginControllerTest.java | 232 ++++++ .../server/controller/RuleControllerTest.java | 246 ++++++ .../controller/TenantControllerTest.java | 220 ++++++ .../server/controller/UserControllerTest.java | 618 +++++++++++++++ .../controller/WidgetTypeControllerTest.java | 233 ++++++ .../WidgetsBundleControllerTest.java | 316 ++++++++ .../server/service/mail/TestMailService.java | 57 ++ .../server/system/HttpDeviceApiTest.java | 82 ++ .../server/system/SystemTestSuite.java | 39 + application/src/test/resources/logback.xml | 21 + common/data/pom.xml | 76 ++ .../server/common/data/AdminSettings.java | 105 +++ .../server/common/data/BaseData.java | 84 +++ .../server/common/data/CacheConstants.java | 20 + .../server/common/data/ContactBased.java | 185 +++++ .../server/common/data/Customer.java | 145 ++++ .../server/common/data/Dashboard.java | 144 ++++ .../server/common/data/DataConstants.java | 39 + .../server/common/data/Device.java | 148 ++++ .../server/common/data/EntityType.java | 23 + .../thingsboard/server/common/data/Event.java | 48 ++ .../server/common/data/SearchTextBased.java | 41 + .../server/common/data/Tenant.java | 144 ++++ .../thingsboard/server/common/data/User.java | 200 +++++ .../common/data/id/AdminSettingsId.java | 30 + .../common/data/id/ComponentDescriptorId.java | 31 + .../server/common/data/id/CustomerId.java | 39 + .../server/common/data/id/DashboardId.java | 29 + .../common/data/id/DeviceCredentialsId.java | 29 + .../server/common/data/id/DeviceId.java | 43 ++ .../server/common/data/id/EntityId.java | 39 + .../server/common/data/id/EventId.java | 35 + .../server/common/data/id/IdBased.java | 77 ++ .../server/common/data/id/NodeId.java | 25 + .../server/common/data/id/PluginId.java | 39 + .../server/common/data/id/RuleId.java | 37 + .../server/common/data/id/SessionId.java | 24 + .../server/common/data/id/TenantId.java | 39 + .../server/common/data/id/UUIDBased.java | 72 ++ .../common/data/id/UserCredentialsId.java | 25 + .../server/common/data/id/UserId.java | 29 + .../server/common/data/id/WidgetTypeId.java | 32 + .../common/data/id/WidgetsBundleId.java | 32 + .../server/common/data/kv/AttributeKey.java | 29 + .../common/data/kv/AttributeKvEntry.java | 25 + .../common/data/kv/BaseAttributeKvEntry.java | 104 +++ .../server/common/data/kv/BaseTsKvQuery.java | 73 ++ .../server/common/data/kv/BasicKvEntry.java | 73 ++ .../server/common/data/kv/BasicTsKvEntry.java | 97 +++ .../common/data/kv/BooleanDataEntry.java | 69 ++ .../server/common/data/kv/DataType.java | 22 + .../common/data/kv/DoubleDataEntry.java | 70 ++ .../server/common/data/kv/KvEntry.java | 43 ++ .../server/common/data/kv/LongDataEntry.java | 70 ++ .../common/data/kv/StringDataEntry.java | 72 ++ .../server/common/data/kv/TsKvEntry.java | 28 + .../server/common/data/kv/TsKvQuery.java | 30 + .../server/common/data/page/BasePageLink.java | 37 + .../common/data/page/PageDataIterable.java | 78 ++ .../server/common/data/page/TextPageData.java | 71 ++ .../server/common/data/page/TextPageLink.java | 80 ++ .../server/common/data/page/TimePageData.java | 70 ++ .../server/common/data/page/TimePageLink.java | 63 ++ .../data/plugin/ComponentDescriptor.java | 87 +++ .../data/plugin/ComponentLifecycleEvent.java | 25 + .../data/plugin/ComponentLifecycleState.java | 23 + .../common/data/plugin/ComponentScope.java | 23 + .../common/data/plugin/ComponentType.java | 25 + .../common/data/plugin/PluginMetaData.java | 175 +++++ .../server/common/data/rule/RuleMetaData.java | 69 ++ .../server/common/data/rule/RuleType.java | 28 + .../server/common/data/rule/Scope.java | 28 + .../common/data/security/Authority.java | 47 ++ .../data/security/DeviceCredentials.java | 128 ++++ .../security/DeviceCredentialsFilter.java | 27 + .../data/security/DeviceCredentialsType.java | 22 + .../data/security/DeviceTokenCredentials.java | 42 ++ .../common/data/security/UserCredentials.java | 156 ++++ .../server/common/data/widget/WidgetType.java | 129 ++++ .../common/data/widget/WidgetsBundle.java | 121 +++ common/message/pom.xml | 76 ++ .../server/common/msg/RuleMsg.java | 38 + .../common/msg/aware/CustomerAwareMsg.java | 24 + .../common/msg/aware/DeviceAwareMsg.java | 23 + .../server/common/msg/aware/NodeAwareMsg.java | 24 + .../common/msg/aware/PluginAwareMsg.java | 24 + .../server/common/msg/aware/RuleAwareMsg.java | 24 + .../common/msg/aware/SessionAwareMsg.java | 24 + .../common/msg/aware/TenantAwareMsg.java | 24 + .../common/msg/cluster/ClusterEventMsg.java | 29 + .../common/msg/cluster/ServerAddress.java | 46 ++ .../common/msg/cluster/ToAllNodesMsg.java | 24 + .../msg/core/AttributesSubscribeMsg.java | 29 + .../msg/core/AttributesUnsubscribeMsg.java | 29 + .../core/AttributesUpdateNotification.java | 47 ++ .../msg/core/BasicCommandAckResponse.java | 44 ++ .../msg/core/BasicGetAttributesRequest.java | 52 ++ .../msg/core/BasicGetAttributesResponse.java | 39 + .../server/common/msg/core/BasicRequest.java | 36 + .../common/msg/core/BasicResponseMsg.java | 79 ++ .../msg/core/BasicStatusCodeResponse.java | 41 + .../msg/core/BasicTelemetryUploadRequest.java | 65 ++ .../core/BasicToDeviceSessionActorMsg.java | 47 ++ .../core/BasicUpdateAttributesRequest.java | 63 ++ .../common/msg/core/GetAttributesRequest.java | 28 + .../msg/core/GetAttributesResponse.java | 22 + .../server/common/msg/core/ResponseMsg.java | 33 + .../common/msg/core/RpcSubscribeMsg.java | 29 + .../common/msg/core/RpcUnsubscribeMsg.java | 29 + .../common/msg/core/RuleEngineError.java | 43 ++ .../common/msg/core/RuleEngineErrorMsg.java | 61 ++ .../common/msg/core/SessionCloseMsg.java | 29 + .../common/msg/core/StatusCodeResponse.java | 20 + .../msg/core/TelemetryUploadRequest.java | 29 + .../msg/core/ToDeviceRpcRequestMsg.java | 41 + .../msg/core/ToDeviceRpcResponseMsg.java | 35 + .../msg/core/ToDeviceSessionActorMsg.java | 29 + .../msg/core/ToServerRpcRequestMsg.java | 36 + .../msg/core/ToServerRpcResponseMsg.java | 41 + .../msg/core/UpdateAttributesRequest.java | 28 + .../msg/device/BasicToDeviceActorMsg.java | 101 +++ .../common/msg/device/ToDeviceActorMsg.java | 40 + .../server/common/msg/kv/AttributesKVMsg.java | 29 + .../common/msg/kv/BasicAttributeKVMsg.java | 52 ++ .../msg/plugin/ComponentLifecycleMsg.java | 65 ++ .../msg/session/AdaptorToSessionActorMsg.java | 22 + .../BasicAdaptorToSessionActorMsg.java | 32 + .../BasicSessionActorToAdaptorMsg.java | 34 + .../common/msg/session/BasicSessionMsg.java | 44 ++ .../session/BasicToDeviceActorSessionMsg.java | 76 ++ .../common/msg/session/FeatureType.java | 20 + .../common/msg/session/FromDeviceMsg.java | 24 + .../msg/session/FromDeviceRequestMsg.java | 25 + .../server/common/msg/session/MsgType.java | 46 ++ .../msg/session/SessionActorToAdaptorMsg.java | 22 + .../common/msg/session/SessionContext.java | 36 + .../common/msg/session/SessionCtrlMsg.java | 22 + .../server/common/msg/session/SessionMsg.java | 24 + .../common/msg/session/SessionType.java | 22 + .../msg/session/ToDeviceActorSessionMsg.java | 27 + .../common/msg/session/ToDeviceMsg.java | 26 + .../msg/session/ctrl/SessionCloseMsg.java | 41 + .../ex/ProcessingTimeoutException.java | 22 + .../msg/session/ex/SessionAuthException.java | 26 + .../msg/session/ex/SessionException.java | 35 + common/pom.xml | 43 ++ common/transport/pom.xml | 79 ++ .../common/transport/SessionMsgProcessor.java | 25 + .../common/transport/TransportAdaptor.java | 32 + .../transport/adaptor/AdaptorException.java | 34 + .../transport/adaptor/JsonConverter.java | 199 +++++ .../transport/auth/DeviceAuthResult.java | 58 ++ .../transport/auth/DeviceAuthService.java | 30 + .../session/DeviceAwareSessionContext.java | 62 ++ extensions-api/pom.xml | 95 +++ .../extensions/api/component/Action.java | 40 + .../api/component/ConfigurableComponent.java | 25 + .../EmptyComponentConfiguration.java | 22 + .../extensions/api/component/Filter.java | 40 + .../extensions/api/component/Plugin.java | 42 ++ .../extensions/api/component/Processor.java | 40 + .../api/configuration/Configurable.java | 26 + .../api/configuration/Configuration.java | 24 + .../ConfigurationValidationException.java | 26 + .../api/device/DeviceAttributes.java | 68 ++ .../DeviceAttributesEventNotificationMsg.java | 51 ++ .../device/ToDeviceActorNotificationMsg.java | 28 + .../api/plugins/AbstractPlugin.java | 88 +++ .../server/extensions/api/plugins/Plugin.java | 54 ++ .../extensions/api/plugins/PluginAction.java | 37 + .../plugins/PluginApiCallSecurityContext.java | 70 ++ .../api/plugins/PluginCallback.java | 26 + .../api/plugins/PluginConstants.java | 23 + .../extensions/api/plugins/PluginContext.java | 106 +++ .../api/plugins/PluginException.java | 26 + .../PluginInitializationException.java | 29 + .../handlers/DefaultRestMsgHandler.java | 72 ++ .../handlers/DefaultRpcMsgHandler.java | 30 + .../handlers/DefaultRuleMsgHandler.java | 63 ++ .../handlers/DefaultWebsocketMsgHandler.java | 103 +++ .../api/plugins/handlers/RestMsgHandler.java | 28 + .../api/plugins/handlers/RpcMsgHandler.java | 28 + .../api/plugins/handlers/RuleMsgHandler.java | 31 + .../plugins/handlers/WebsocketMsgHandler.java | 28 + .../plugins/msg/AbstractPluginToRuleMsg.java | 63 ++ .../plugins/msg/AbstractRuleToPluginMsg.java | 72 ++ .../plugins/msg/FromDeviceRpcResponse.java | 44 ++ .../GetAttributesRequestRuleToPluginMsg.java | 33 + .../msg/GetRequestRuleToPluginMsg.java | 31 + .../api/plugins/msg/PluginToRuleMsg.java | 64 ++ .../plugins/msg/ResponsePluginToRuleMsg.java | 32 + .../extensions/api/plugins/msg/RpcError.java | 23 + .../msg/RpcRequestRuleToPluginMsg.java | 31 + .../msg/RpcResponsePluginToRuleMsg.java | 32 + .../api/plugins/msg/RuleToPluginMsg.java | 61 ++ ...TelemetryUploadRequestRuleToPluginMsg.java | 31 + .../api/plugins/msg/TimeoutIntMsg.java | 27 + .../api/plugins/msg/TimeoutMsg.java | 27 + .../api/plugins/msg/TimeoutUUIDMsg.java | 29 + .../api/plugins/msg/ToDeviceRpcRequest.java | 37 + .../plugins/msg/ToDeviceRpcRequestBody.java | 29 + .../msg/ToDeviceRpcRequestPluginMsg.java | 62 ++ .../api/plugins/msg/ToPluginActorMsg.java | 25 + .../msg/ToPluginRpcResponseDeviceMsg.java | 31 + ...pdateAttributesRequestRuleToPluginMsg.java | 31 + .../api/plugins/rest/BasicPluginRestMsg.java | 63 ++ .../api/plugins/rest/PluginRestMsg.java | 38 + .../api/plugins/rest/RestRequest.java | 87 +++ .../api/plugins/rpc/PluginRpcMsg.java | 46 ++ .../extensions/api/plugins/rpc/RpcMsg.java | 35 + .../ws/BasicPluginWebsocketSessionRef.java | 111 +++ .../plugins/ws/PluginWebsocketSessionRef.java | 45 ++ .../api/plugins/ws/SessionEvent.java | 53 ++ .../api/plugins/ws/WsSessionMetaData.java | 50 ++ .../ws/msg/AbstractPluginWebSocketMsg.java | 64 ++ .../ws/msg/BinaryPluginWebSocketMsg.java | 29 + .../ws/msg/EmptyPluginWebsocketMsg.java | 30 + .../ws/msg/PingPluginWebsocketMsg.java | 27 + .../plugins/ws/msg/PluginWebsocketMsg.java | 31 + .../ws/msg/PongPluginWebsocketMsg.java | 27 + .../msg/SessionEventPluginWebSocketMsg.java | 29 + .../ws/msg/TextPluginWebSocketMsg.java | 28 + .../extensions/api/rules/RuleContext.java | 36 + .../extensions/api/rules/RuleException.java | 30 + .../extensions/api/rules/RuleFilter.java | 28 + .../rules/RuleInitializationException.java | 29 + .../api/rules/RuleLifecycleComponent.java | 29 + .../api/rules/RuleProcessingMetaData.java | 43 ++ .../extensions/api/rules/RuleProcessor.java | 27 + .../rules/SimpleRuleLifecycleComponent.java | 41 + .../extensions/api/rules/ToRuleActorMsg.java | 25 + .../main/resources/EmptyJsonDescriptor.json | 12 + extensions-core/pom.xml | 131 ++++ .../core/action/mail/SendMailAction.java | 109 +++ .../mail/SendMailActionConfiguration.java | 34 + .../core/action/mail/SendMailActionMsg.java | 36 + .../mail/SendMailRuleToPluginActionMsg.java | 35 + .../core/action/rpc/RpcPluginAction.java | 67 ++ .../telemetry/TelemetryPluginAction.java | 73 ++ .../AbstractTemplatePluginAction.java | 84 +++ .../template/TemplateActionConfiguration.java | 25 + .../extensions/core/filter/BasicJsFilter.java | 92 +++ .../core/filter/DeviceAttributesFilter.java | 93 +++ .../DeviceAttributesFilterConfiguration.java | 27 + .../core/filter/DeviceTelemetryFilter.java | 60 ++ .../core/filter/JsFilterConfiguration.java | 27 + .../core/filter/MethodNameFilter.java | 54 ++ .../filter/MethodNameFilterConfiguration.java | 33 + .../extensions/core/filter/MsgTypeFilter.java | 67 ++ .../filter/MsgTypeFilterConfiguration.java | 28 + .../core/filter/NashornJsEvaluator.java | 63 ++ .../core/plugin/KeyValuePluginProperties.java | 27 + .../core/plugin/mail/MailPlugin.java | 121 +++ .../plugin/mail/MailPluginConfiguration.java | 33 + .../messaging/DeviceMessagingPlugin.java | 69 ++ .../DeviceMessagingPluginConfiguration.java | 30 + .../DeviceMessagingRuleMsgHandler.java | 228 ++++++ .../messaging/PendingRpcRequestMetadata.java | 37 + .../core/plugin/rpc/LocalRequestMetaData.java | 30 + .../core/plugin/rpc/RpcManager.java | 69 ++ .../extensions/core/plugin/rpc/RpcPlugin.java | 77 ++ .../plugin/rpc/RpcPluginConfiguration.java | 26 + .../core/plugin/rpc/cmd/RpcRequest.java | 28 + .../rpc/handlers/RpcRestMsgHandler.java | 127 ++++ .../core/plugin/telemetry/AttributeData.java | 48 ++ .../plugin/telemetry/SubscriptionManager.java | 284 +++++++ .../telemetry/TelemetryStoragePlugin.java | 106 +++ .../core/plugin/telemetry/TsData.java | 42 ++ .../cmd/AttributesSubscriptionCmd.java | 36 + .../plugin/telemetry/cmd/GetHistoryCmd.java | 70 ++ .../plugin/telemetry/cmd/SubscriptionCmd.java | 70 ++ .../telemetry/cmd/TelemetryPluginCmd.java | 29 + .../cmd/TelemetryPluginCmdsWrapper.java | 57 ++ .../cmd/TimeseriesSubscriptionCmd.java | 46 ++ .../handlers/TelemetryRestMsgHandler.java | 201 +++++ .../handlers/TelemetryRpcMsgHandler.java | 185 +++++ .../handlers/TelemetryRuleMsgHandler.java | 130 ++++ .../TelemetryWebsocketMsgHandler.java | 314 ++++++++ .../plugin/telemetry/sub/Subscription.java | 73 ++ .../telemetry/sub/SubscriptionErrorCode.java | 50 ++ .../telemetry/sub/SubscriptionState.java | 47 ++ .../telemetry/sub/SubscriptionType.java | 23 + .../telemetry/sub/SubscriptionUpdate.java | 97 +++ .../core/plugin/time/TimePlugin.java | 92 +++ .../plugin/time/TimePluginConfiguration.java | 26 + .../AlarmDeduplicationProcessor.java | 84 +++ ...rmDeduplicationProcessorConfiguration.java | 29 + .../extensions/core/utils/VelocityUtils.java | 96 +++ .../src/main/proto/telemetry.proto | 57 ++ ...AlarmDeduplicationProcessorDescriptor.json | 30 + .../DeviceMessagingPluginDescriptor.json | 37 + .../main/resources/JsFilterDescriptor.json | 21 + .../src/main/resources/MailPluginData.json | 16 + .../main/resources/MailPluginDescriptor.json | 61 ++ .../resources/MethodNameFilterDescriptor.json | 28 + .../resources/MsgTypeFilterDescriptor.json | 40 + .../src/main/resources/RpcPluginData.json | 3 + .../main/resources/RpcPluginDescriptor.json | 18 + .../main/resources/SendMailActionData.json | 7 + .../resources/SendMailActionDescriptor.json | 55 ++ .../main/resources/TimePluginDescriptor.json | 16 + .../filter/DeviceAttributesFilterTest.java | 126 ++++ license-header-template.txt | 13 + pom.xml | 710 ++++++++++++++++++ tools/pom.xml | 83 ++ tools/src/main/java/MqttStressTestClient.java | 82 ++ tools/src/main/java/MqttStressTestTool.java | 88 +++ tools/src/main/java/RestClient.java | 72 ++ tools/src/main/java/ResultAccumulator.java | 85 +++ tools/src/main/shell/keygen.properties | 8 + tools/src/main/shell/keygen.sh | 57 ++ tools/src/main/shell/securemqttclient.py | 55 ++ tools/src/main/shell/simplemqttclient.py | 50 ++ transport/coap/pom.xml | 84 +++ .../transport/coap/CoapTransportResource.java | 222 ++++++ .../transport/coap/CoapTransportService.java | 96 +++ .../coap/adaptors/CoapTransportAdaptor.java | 25 + .../coap/adaptors/JsonCoapAdaptor.java | 265 +++++++ .../transport/coap/client/DeviceEmulator.java | 179 +++++ .../session/CoapExchangeObserverProxy.java | 38 + .../coap/session/CoapSessionCtx.java | 143 ++++ .../transport/coap/session/CoapSessionId.java | 77 ++ .../server/transport/coap/CoapServerTest.java | 201 +++++ .../coap/CoapServerTestConfiguration.java | 35 + .../resources/coap-transport-test.properties | 4 + transport/http/pom.xml | 81 ++ .../transport/http/DeviceApiController.java | 195 +++++ .../http/session/HttpSessionCtx.java | 162 ++++ .../transport/http/session/HttpSessionId.java | 37 + transport/mqtt/pom.xml | 90 +++ .../mqtt/MqttSslHandlerProvider.java | 91 +++ .../transport/mqtt/MqttTransportHandler.java | 260 +++++++ .../mqtt/MqttTransportServerInitializer.java | 69 ++ .../transport/mqtt/MqttTransportService.java | 108 +++ .../mqtt/adaptors/JsonMqttAdaptor.java | 238 ++++++ .../mqtt/adaptors/MqttTransportAdaptor.java | 26 + .../mqtt/session/MqttSessionCtx.java | 116 +++ .../transport/mqtt/session/MqttSessionId.java | 56 ++ transport/pom.xml | 50 ++ 466 files changed, 33748 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 application/.gitignore create mode 100644 application/build.gradle create mode 100644 application/pom.xml create mode 100644 application/src/main/conf/logback.xml create mode 100644 application/src/main/conf/thingsboard.conf create mode 100644 application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/app/AppActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/plugin/TimeoutScheduler.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/service/ActorService.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/service/RestMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/ActorTerminationMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java create mode 100644 application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSessionListener.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java create mode 100644 application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcSessionCreationFuture.java create mode 100644 application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java create mode 100644 application/src/main/java/org/thingsboard/server/utils/MiscUtils.java create mode 100644 application/src/main/proto/cluster.proto create mode 100644 application/src/main/proto/discovery.proto create mode 100644 application/src/main/resources/actor-system.conf create mode 100644 application/src/main/resources/banner.txt create mode 100644 application/src/main/resources/i18n/messages.properties create mode 100644 application/src/main/resources/logback.xml create mode 100644 application/src/main/resources/templates/account.activated.vm create mode 100644 application/src/main/resources/templates/activation.vm create mode 100644 application/src/main/resources/templates/password.was.reset.vm create mode 100644 application/src/main/resources/templates/reset.password.vm create mode 100644 application/src/main/resources/templates/test.vm create mode 100644 application/src/main/resources/thingsboard.yml create mode 100644 application/src/main/scripts/control/deb/postinst create mode 100644 application/src/main/scripts/control/deb/postrm create mode 100644 application/src/main/scripts/control/deb/preinst create mode 100644 application/src/main/scripts/control/deb/prerm create mode 100644 application/src/main/scripts/control/rpm/postinst create mode 100644 application/src/main/scripts/control/rpm/postrm create mode 100644 application/src/main/scripts/control/rpm/preinst create mode 100644 application/src/main/scripts/control/rpm/prerm create mode 100644 application/src/main/scripts/control/thingsboard.service create mode 100644 application/src/test/java/org/thingsboard/server/ThingsboardApplicationTests.java create mode 100644 application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java create mode 100644 application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java create mode 100644 application/src/test/java/org/thingsboard/server/actors/DummySessionID.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/ComponentDescriptorControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/ControllerTestSuite.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/PluginControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/RuleControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java create mode 100644 application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java create mode 100644 application/src/test/java/org/thingsboard/server/system/SystemTestSuite.java create mode 100644 application/src/test/resources/logback.xml create mode 100644 common/data/pom.xml create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/Customer.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/Device.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/Event.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBased.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/User.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/ComponentDescriptorId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceCredentialsId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/EventId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/NodeId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/PluginId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/RuleId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/SessionId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/UserCredentialsId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKey.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKvEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/BooleanDataEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/DoubleDataEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/LongDataEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageLink.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleState.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentScope.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rule/Scope.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/Authority.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java create mode 100644 common/message/pom.xml create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/aware/CustomerAwareMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/aware/DeviceAwareMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/aware/NodeAwareMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/aware/PluginAwareMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleAwareMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/aware/SessionAwareMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/aware/TenantAwareMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerAddress.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ToAllNodesMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicToDeviceSessionActorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicUpdateAttributesRequest.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesResponse.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceSessionActorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/core/UpdateAttributesRequest.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicToDeviceActorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/device/ToDeviceActorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/kv/AttributesKVMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/AdaptorToSessionActorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicToDeviceActorSessionMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceRequestMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/MsgType.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionType.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceActorSessionMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/ProcessingTimeoutException.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionAuthException.java create mode 100644 common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionException.java create mode 100644 common/pom.xml create mode 100644 common/transport/pom.xml create mode 100644 common/transport/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java create mode 100644 common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java create mode 100644 common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java create mode 100644 common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java create mode 100644 common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java create mode 100644 common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java create mode 100644 common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java create mode 100644 extensions-api/pom.xml create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Action.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/ConfigurableComponent.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/EmptyComponentConfiguration.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Filter.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Plugin.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Processor.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configurable.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configuration.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/ConfigurationValidationException.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributes.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginApiCallSecurityContext.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginCallback.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginException.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginInitializationException.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRestMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRpcMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultWebsocketMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RestMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RpcMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RuleMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/WebsocketMsgHandler.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractPluginToRuleMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractRuleToPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/FromDeviceRpcResponse.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetAttributesRequestRuleToPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetRequestRuleToPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/PluginToRuleMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ResponsePluginToRuleMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcError.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcRequestRuleToPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcResponsePluginToRuleMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RuleToPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TelemetryUploadRequestRuleToPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutIntMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutUUIDMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginActorMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginRpcResponseDeviceMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/BasicPluginRestMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/PluginRestMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/RestRequest.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/PluginRpcMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/RpcMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/BasicPluginWebsocketSessionRef.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/PluginWebsocketSessionRef.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/SessionEvent.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/WsSessionMetaData.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/AbstractPluginWebSocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/BinaryPluginWebSocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/EmptyPluginWebsocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PingPluginWebsocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PluginWebsocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PongPluginWebsocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/SessionEventPluginWebSocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/TextPluginWebSocketMsg.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleContext.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleException.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleInitializationException.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleLifecycleComponent.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessingMetaData.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/SimpleRuleLifecycleComponent.java create mode 100644 extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/ToRuleActorMsg.java create mode 100644 extensions-api/src/main/resources/EmptyJsonDescriptor.json create mode 100644 extensions-core/pom.xml create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailActionConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailActionMsg.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailRuleToPluginActionMsg.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/TemplateActionConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilterConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/JsFilterConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilterConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilterConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/KeyValuePluginProperties.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/mail/MailPlugin.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/mail/MailPluginConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingPlugin.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingPluginConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/PendingRpcRequestMetadata.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/LocalRequestMetaData.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcManager.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPlugin.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPluginConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/cmd/RpcRequest.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/AttributeData.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/SubscriptionManager.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/TelemetryStoragePlugin.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/TsData.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/AttributesSubscriptionCmd.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/GetHistoryCmd.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/SubscriptionCmd.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/TelemetryPluginCmd.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/TelemetryPluginCmdsWrapper.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/TimeseriesSubscriptionCmd.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRpcMsgHandler.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryWebsocketMsgHandler.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/Subscription.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/SubscriptionErrorCode.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/SubscriptionState.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/SubscriptionType.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/sub/SubscriptionUpdate.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/time/TimePlugin.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/time/TimePluginConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessorConfiguration.java create mode 100644 extensions-core/src/main/java/org/thingsboard/server/extensions/core/utils/VelocityUtils.java create mode 100644 extensions-core/src/main/proto/telemetry.proto create mode 100644 extensions-core/src/main/resources/AlarmDeduplicationProcessorDescriptor.json create mode 100644 extensions-core/src/main/resources/DeviceMessagingPluginDescriptor.json create mode 100644 extensions-core/src/main/resources/JsFilterDescriptor.json create mode 100644 extensions-core/src/main/resources/MailPluginData.json create mode 100644 extensions-core/src/main/resources/MailPluginDescriptor.json create mode 100644 extensions-core/src/main/resources/MethodNameFilterDescriptor.json create mode 100644 extensions-core/src/main/resources/MsgTypeFilterDescriptor.json create mode 100644 extensions-core/src/main/resources/RpcPluginData.json create mode 100644 extensions-core/src/main/resources/RpcPluginDescriptor.json create mode 100644 extensions-core/src/main/resources/SendMailActionData.json create mode 100644 extensions-core/src/main/resources/SendMailActionDescriptor.json create mode 100644 extensions-core/src/main/resources/TimePluginDescriptor.json create mode 100644 extensions-core/src/test/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilterTest.java create mode 100644 license-header-template.txt create mode 100755 pom.xml create mode 100644 tools/pom.xml create mode 100644 tools/src/main/java/MqttStressTestClient.java create mode 100644 tools/src/main/java/MqttStressTestTool.java create mode 100644 tools/src/main/java/RestClient.java create mode 100644 tools/src/main/java/ResultAccumulator.java create mode 100644 tools/src/main/shell/keygen.properties create mode 100755 tools/src/main/shell/keygen.sh create mode 100644 tools/src/main/shell/securemqttclient.py create mode 100644 tools/src/main/shell/simplemqttclient.py create mode 100644 transport/coap/pom.xml create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java create mode 100644 transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java create mode 100644 transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java create mode 100644 transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java create mode 100644 transport/coap/src/test/resources/coap-transport-test.properties create mode 100644 transport/http/pom.xml create mode 100644 transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java create mode 100644 transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java create mode 100644 transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionId.java create mode 100644 transport/mqtt/pom.xml create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttSessionCtx.java create mode 100644 transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttSessionId.java create mode 100644 transport/pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..e14c866b6e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +output/** +*.class +*~ +*.iml +*/.idea/** +.idea/** +.idea +*.log +*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] +*/.classpath +.classpath +*/.project +.project +.cache/** +target/ +build/ +tmp_deb_control/ +tmp_rpm_control/ +tmp_sh/ +.gwt/ +.settings/ +/bin +bin/ +**/dependency-reduced-pom.xml +pom.xml.versionsBackup +.DS_Store +**/.gradle +**/local.properties +**/build +**/target +**/Californium.properties +**/.env diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..c8f142f0bf9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 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. diff --git a/README.md b/README.md new file mode 100644 index 00000000000..dfd87356e53 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# iotrules +IoT Rules Engine + +**Docker usage** + +**start platfrom using docker:** +- install docker +- cd to 'docker' folder +- create folder for cassandra data directory on your local env (host) + - `mkdir /home/user/data_dir` +- modify .env file to point to the directory created in previous step +- start ./deploy.sh script to run all the services + + +**start-up for local development** + +cassandra with thingsboard schema (9042 and 9061 ports are exposed). +zookeper services (2181 port is exposed). +9042, 9061 and 2181 ports must be free so 'Thingsboard' server that is running outside docker container is able to connect to services. +you can change these ports in docker-compose.static.yml file to some others, but 'Thingsbaord' application.yml file must be updated accordingly. +if you would like to change cassandra port, change it to "9999:9042" for example and update cassandra.node_list entry in application.yml file to localhost:9999. + +- install docker +- cd to 'docker' folder +- create folder for cassandra data directory on your local env (host) + - `mkdir /home/user/data_dir` +- modify .env file to point to the directory created in previous step +- start ./deploy_cassandra_zookeeper.sh script to run cassandra with thingsboard schema and zookeper services +- Start boot class: _org.thingsboard.server.ThingsboardServerApplication_ diff --git a/application/.gitignore b/application/.gitignore new file mode 100644 index 00000000000..08eb0a07a69 --- /dev/null +++ b/application/.gitignore @@ -0,0 +1 @@ +!bin/ \ No newline at end of file diff --git a/application/build.gradle b/application/build.gradle new file mode 100644 index 00000000000..833cf4cdc76 --- /dev/null +++ b/application/build.gradle @@ -0,0 +1,134 @@ +/** + * Copyright © 2016 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. + */ + +buildscript { + ext { + osPackageVersion = "3.8.0" + } + repositories { + jcenter() + } + dependencies { + classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}") + } +} + +apply plugin: "nebula.ospackage" + +buildDir = projectBuildDir +version = projectVersion +distsDirName = "./" + +// OS Package plugin configuration +ospackage { + packageName = pkgName + version = "${project.version}" + release = 1 + os = LINUX + type = BINARY + + into pkgInstallFolder + + user pkgName + permissionGroup pkgName + + // Copy the actual .jar file + from(mainJar) { + // Strip the version from the jar filename + rename { String fileName -> + fileName.replace("-${project.version}", "") + } + fileMode 0500 + into "bin" + } + + // Copy the config files + from("target/conf") { + fileType CONFIG | NOREPLACE + fileMode 0754 + into "conf" + } + + // Copy the data files + from("target/data") { + fileType CONFIG | NOREPLACE + fileMode 0754 + into "data" + } + + // Copy the extensions files + from("target/extensions") { + into "extensions" + } +} + +// Configure our RPM build task +buildRpm { + + arch = NOARCH + + version = projectVersion.replace('-', '') + archiveName = "${pkgName}.rpm" + + requires("java-1.8.0") + + preInstall file("${buildDir}/control/rpm/preinst") + postInstall file("${buildDir}/control/rpm/postinst") + preUninstall file("${buildDir}/control/rpm/prerm") + postUninstall file("${buildDir}/control/rpm/postrm") + + user pkgName + permissionGroup pkgName + + // Copy the system unit files + from("${buildDir}/control/${pkgName}.service") { + addParentDirs = false + fileMode 0644 + into "/usr/lib/systemd/system" + } + + directory(pkgLogFolder, 0755) + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} + +// Same as the buildRpm task +buildDeb { + + arch = "all" + + archiveName = "${pkgName}.deb" + + requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer") + + configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf") + configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml") + configurationFile("${pkgInstallFolder}/conf/logback.xml") + configurationFile("${pkgInstallFolder}/conf/actor-system.conf") + + preInstall file("${buildDir}/control/deb/preinst") + postInstall file("${buildDir}/control/deb/postinst") + preUninstall file("${buildDir}/control/deb/prerm") + postUninstall file("${buildDir}/control/deb/postrm") + + user pkgName + permissionGroup pkgName + + directory(pkgLogFolder, 0755) + link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar") + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} diff --git a/application/pom.xml b/application/pom.xml new file mode 100644 index 00000000000..d75ece112bc --- /dev/null +++ b/application/pom.xml @@ -0,0 +1,417 @@ + + + 4.0.0 + + org.thingsboard + 0.0.1-SNAPSHOT + server + + org.thingsboard.server + application + jar + + Thingsboard Server Application + http://thingsboard.org + + + UTF-8 + ${basedir}/.. + thingsboard + /var/log/${pkg.name} + /usr/share/${pkg.name} + + + + + io.netty + netty-transport-native-epoll + ${netty.version} + + linux-x86_64 + + + org.thingsboard.server + extensions-api + + + org.thingsboard.server + extensions-core + + + org.thingsboard.server.common + transport + + + org.thingsboard.server.transport + http + + + org.thingsboard.server.transport + coap + + + org.thingsboard.server.transport + mqtt + + + org.thingsboard.server + dao + + + org.thingsboard.server + dao + test-jar + test + + + io.takari.junit + takari-cpsuite + test + + + org.cassandraunit + cassandra-unit + + + org.slf4j + slf4j-log4j12 + + + test + + + org.thingsboard.server + ui + ${project.version} + runtime + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + + io.jsonwebtoken + jjwt + + + joda-time + joda-time + + + org.apache.velocity + velocity + + + org.apache.velocity + velocity-tools + + + org.springframework + spring-context-support + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + com.jayway.jsonpath + json-path + test + + + com.jayway.jsonpath + json-path-assert + test + + + com.typesafe.akka + akka-actor_${scala.version} + + + com.typesafe.akka + akka-slf4j_${scala.version} + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + junit + junit + test + + + org.mockito + mockito-all + test + + + javax.mail + mail + + + org.apache.curator + curator-recipes + + + com.google.protobuf + protobuf-java + + + io.grpc + grpc-netty + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + + + ${pkg.name}-${project.version} + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${surfire.version} + + + thingsboard + + + **/*TestSuite.java + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/resources + + logback.xml + + false + + + + + + copy-service-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/conf + true + + + + + + copy-control + process-resources + + copy-resources + + + ${project.build.directory}/control + + + src/main/scripts/control + true + + + + + + copy-data-cql + process-resources + + copy-resources + + + ${project.build.directory}/data + + + ../dao/src/main/resources + + **/*.cql + + false + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-extensions + package + + copy + + + ${project.build.directory}/extensions + + + org.thingsboard.server.extensions + extension-rabbitmq + extension + + + org.thingsboard.server.extensions + extension-rest-api-call + extension + + + org.thingsboard.server.extensions + extension-kafka + extension + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + Thingsboard + ${project.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + ZIP + true + true + + ${pkg.installFolder}/conf + ${pkg.logFolder} + ${pkg.name}.out + + + + + + repackage + + + + + + org.fortasoft + gradle-maven-plugin + + + build + buildDeb + buildRpm + + + -PprojectBuildDir=${project.build.directory} + -PprojectVersion=${project.version} + -PmainJar=${project.build.directory}/${project.build.finalName}.${project.packaging} + -PpkgName=${pkg.name} + -PpkgInstallFolder=${pkg.installFolder} + -PpkgLogFolder=${pkg.logFolder} + + + + + package + + invoke + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + org.codehaus.mojo + build-helper-maven-plugin + + + + diff --git a/application/src/main/conf/logback.xml b/application/src/main/conf/logback.xml new file mode 100644 index 00000000000..41873560b30 --- /dev/null +++ b/application/src/main/conf/logback.xml @@ -0,0 +1,44 @@ + + + + + + + ${pkg.logFolder}/${pkg.name}.log + + ${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + diff --git a/application/src/main/conf/thingsboard.conf b/application/src/main/conf/thingsboard.conf new file mode 100644 index 00000000000..dc161c09215 --- /dev/null +++ b/application/src/main/conf/thingsboard.conf @@ -0,0 +1,19 @@ +# +# Copyright © 2016 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. +# + +export JAVA_OPTS="$JAVA_OPTS" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java new file mode 100644 index 00000000000..f082824ab78 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016 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; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +import java.util.Arrays; + +@EnableAutoConfiguration +@SpringBootApplication +@ComponentScan({"org.thingsboard.server"}) +public class ThingsboardServerApplication { + + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "thingsboard"; + + public static void main(String[] args) { + SpringApplication.run(ThingsboardServerApplication.class, updateArguments(args)); + } + + private static String[] updateArguments(String[] args) { + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { + String[] modifiedArgs = new String[args.length + 1]; + System.arraycopy(args, 0, modifiedArgs, 0, args.length); + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; + return modifiedArgs; + } + return args; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java new file mode 100644 index 00000000000..6b5dd56b753 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -0,0 +1,191 @@ +/** + * Copyright © 2016 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.actors; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Scheduler; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.server.actors.service.ActorService; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.transport.auth.DeviceAuthService; +import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.event.EventService; +import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.rule.RuleService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; +import org.thingsboard.server.service.component.ComponentDiscoveryService; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Optional; + +@Component +public class ActorSystemContext { + private static final String AKKA_CONF_FILE_NAME = "actor-system.conf"; + + protected final ObjectMapper mapper = new ObjectMapper(); + + @Getter @Setter private ActorService actorService; + + @Autowired + @Getter private DiscoveryService discoveryService; + + @Autowired + @Getter @Setter private ComponentDiscoveryService componentService; + + @Autowired + @Getter private ClusterRoutingService routingService; + + @Autowired + @Getter private ClusterRpcService rpcService; + + @Autowired + @Getter private DeviceAuthService deviceAuthService; + + @Autowired + @Getter private DeviceService deviceService; + + @Autowired + @Getter private TenantService tenantService; + + @Autowired + @Getter private CustomerService customerService; + + @Autowired + @Getter private RuleService ruleService; + + @Autowired + @Getter private PluginService pluginService; + + @Autowired + @Getter private TimeseriesService tsService; + + @Autowired + @Getter private AttributesService attributesService; + + @Autowired + @Getter private EventService eventService; + + @Autowired + @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; + + @Value("${actors.session.sync.timeout}") + @Getter private long syncSessionTimeout; + + @Value("${actors.plugin.termination.delay}") + @Getter private long pluginActorTerminationDelay; + + @Value("${actors.plugin.processing.timeout}") + @Getter private long pluginProcessingTimeout; + + @Value("${actors.plugin.error_persist_frequency}") + @Getter private long pluginErrorPersistFrequency; + + @Value("${actors.rule.termination.delay}") + @Getter private long ruleActorTerminationDelay; + + @Value("${actors.rule.error_persist_frequency}") + @Getter private long ruleErrorPersistFrequency; + + @Value("${actors.statistics.enabled}") + @Getter private boolean statisticsEnabled; + + @Value("${actors.statistics.persist_frequency}") + @Getter private long statisticsPersistFrequency; + + @Getter @Setter private ActorSystem actorSystem; + + @Getter @Setter private ActorRef appActor; + + @Getter @Setter private ActorRef sessionManagerActor; + + @Getter @Setter private ActorRef statsActor; + + @Getter private final Config config; + + public ActorSystemContext() { + config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load()); + } + + public Scheduler getScheduler() { + return actorSystem.scheduler(); + } + + public void persistError(TenantId tenantId, EntityId entityId, String method, Exception e) { + Event event = new Event(); + event.setTenantId(tenantId); + event.setEntityId(entityId); + event.setType(DataConstants.ERROR); + event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), method, toString(e))); + persistEvent(event); + } + + public void persistLifecycleEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent lcEvent, Exception e) { + Event event = new Event(); + event.setTenantId(tenantId); + event.setEntityId(entityId); + event.setType(DataConstants.LC_EVENT); + event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), lcEvent, Optional.ofNullable(e))); + persistEvent(event); + } + + private void persistEvent(Event event) { + eventService.save(event); + } + + private String toString(Exception e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } + + private JsonNode toBodyJson(ServerAddress server, ComponentLifecycleEvent event, Optional e) { + ObjectNode node = mapper.createObjectNode().put("server", server.toString()).put("event", event.name()); + if (e.isPresent()) { + node = node.put("success", false); + node = node.put("error", toString(e.get())); + } else { + node = node.put("success", true); + } + return node; + } + + private JsonNode toBodyJson(ServerAddress server, String method, String body) { + return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java new file mode 100644 index 00000000000..c370616be6d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -0,0 +1,226 @@ +/** + * Copyright © 2016 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.actors.app; + +import akka.actor.*; +import akka.actor.SupervisorStrategy.Directive; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import akka.japi.Function; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.plugin.PluginTerminationMsg; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.actors.shared.plugin.PluginManager; +import org.thingsboard.server.actors.shared.plugin.SystemPluginManager; +import org.thingsboard.server.actors.shared.rule.RuleManager; +import org.thingsboard.server.actors.shared.rule.SystemRuleManager; +import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; +import org.thingsboard.server.actors.tenant.TenantActor; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; +import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg; +import scala.concurrent.duration.Duration; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class AppActor extends ContextAwareActor { + + private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); + + public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); + private final RuleManager ruleManager; + private final PluginManager pluginManager; + private final TenantService tenantService; + private final Map tenantActors; + + private AppActor(ActorSystemContext systemContext) { + super(systemContext); + this.ruleManager = new SystemRuleManager(systemContext); + this.pluginManager = new SystemPluginManager(systemContext); + this.tenantService = systemContext.getTenantService(); + this.tenantActors = new HashMap<>(); + } + + @Override + public SupervisorStrategy supervisorStrategy() { + return strategy; + } + + @Override + public void preStart() { + logger.info("Starting main system actor."); + try { + ruleManager.init(this.context()); + pluginManager.init(this.context()); + + PageDataIterable tenantIterator = new PageDataIterable<>(link -> tenantService.findTenants(link), ENTITY_PACK_LIMIT); + for (Tenant tenant : tenantIterator) { + logger.debug("[{}] Creating tenant actor", tenant.getId()); + getOrCreateTenantActor(tenant.getId()); + logger.debug("Tenant actor created."); + } + + logger.info("Main system actor started."); + } catch (Exception e) { + logger.error(e, "Unknown failure"); + } + } + + @Override + public void onReceive(Object msg) throws Exception { + logger.debug("Received message: {}", msg); + if (msg instanceof ToDeviceActorMsg) { + processDeviceMsg((ToDeviceActorMsg) msg); + } else if (msg instanceof ToPluginActorMsg) { + onToPluginMsg((ToPluginActorMsg) msg); + } else if (msg instanceof ToRuleActorMsg) { + onToRuleMsg((ToRuleActorMsg) msg); + } else if (msg instanceof ToDeviceActorNotificationMsg) { + onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); + } else if (msg instanceof Terminated) { + processTermination((Terminated) msg); + } else if (msg instanceof ClusterEventMsg) { + broadcast(msg); + } else if (msg instanceof ComponentLifecycleMsg) { + onComponentLifecycleMsg((ComponentLifecycleMsg) msg); + } else if (msg instanceof PluginTerminationMsg) { + onPluginTerminated((PluginTerminationMsg) msg); + } else { + logger.warning("Unknown message: {}!", msg); + } + } + + private void onPluginTerminated(PluginTerminationMsg msg) { + pluginManager.remove(msg.getId()); + } + + private void broadcast(Object msg) { + pluginManager.broadcast(msg); + tenantActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); + } + + private void onToRuleMsg(ToRuleActorMsg msg) { + ActorRef target; + if (SYSTEM_TENANT.equals(msg.getTenantId())) { + target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId()); + } else { + target = getOrCreateTenantActor(msg.getTenantId()); + } + target.tell(msg, ActorRef.noSender()); + } + + private void onToPluginMsg(ToPluginActorMsg msg) { + ActorRef target; + if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) { + target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId()); + } else { + target = getOrCreateTenantActor(msg.getPluginTenantId()); + } + target.tell(msg, ActorRef.noSender()); + } + + private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { + ActorRef target = null; + if (SYSTEM_TENANT.equals(msg.getTenantId())) { + if (msg.getPluginId().isPresent()) { + target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId().get()); + } else if (msg.getRuleId().isPresent()) { + Optional ref = ruleManager.update(this.context(), msg.getRuleId().get(), msg.getEvent()); + if (ref.isPresent()) { + target = ref.get(); + } else { + logger.debug("Failed to find actor for rule: [{}]", msg.getRuleId()); + return; + } + } + } else { + target = getOrCreateTenantActor(msg.getTenantId()); + } + if (target != null) { + target.tell(msg, ActorRef.noSender()); + } + } + + private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) { + getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender()); + } + + private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) { + TenantId tenantId = toDeviceActorMsg.getTenantId(); + ActorRef tenantActor = getOrCreateTenantActor(tenantId); + if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) { + tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain()), context().self()); + } else { + tenantActor.tell(toDeviceActorMsg, context().self()); + } + } + + private ActorRef getOrCreateTenantActor(TenantId tenantId) { + ActorRef tenantActor = tenantActors.get(tenantId); + if (tenantActor == null) { + tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId)) + .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString()); + tenantActors.put(tenantId, tenantActor); + } + return tenantActor; + } + + private void processTermination(Terminated message) { + ActorRef terminated = message.actor(); + if (terminated instanceof LocalActorRef) { + logger.debug("Removed actor: {}", terminated); + } else { + throw new IllegalStateException("Remote actors are not supported!"); + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + public ActorCreator(ActorSystemContext context) { + super(context); + } + + @Override + public AppActor create() throws Exception { + return new AppActor(context); + } + } + + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function() { + @Override + public Directive apply(Throwable t) { + logger.error(t, "Unknown failure"); + if (t instanceof RuntimeException) { + return SupervisorStrategy.restart(); + } else { + return SupervisorStrategy.stop(); + } + } + }); +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java new file mode 100644 index 00000000000..8b669e9060b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java @@ -0,0 +1,89 @@ +/** + * Copyright © 2016 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.actors.device; + +import akka.event.Logging; +import akka.event.LoggingAdapter; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.rule.RulesProcessedMsg; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.*; + +public class DeviceActor extends ContextAwareActor { + + private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); + + private final TenantId tenantId; + private final DeviceId deviceId; + private final DeviceActorMessageProcessor processor; + + private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { + super(systemContext); + this.tenantId = tenantId; + this.deviceId = deviceId; + this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId); + } + + @Override + public void onReceive(Object msg) throws Exception { + if (msg instanceof RuleChainDeviceMsg) { + processor.process(context(), (RuleChainDeviceMsg) msg); + } else if (msg instanceof RulesProcessedMsg) { + processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg); + } else if (msg instanceof ToDeviceActorMsg) { + processor.process(context(), (ToDeviceActorMsg) msg); + } else if (msg instanceof ToDeviceActorNotificationMsg) { + if (msg instanceof DeviceAttributesEventNotificationMsg) { + processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg); + } else if (msg instanceof ToDeviceRpcRequestPluginMsg) { + processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg); + } + } else if (msg instanceof TimeoutMsg) { + processor.processTimeout(context(), (TimeoutMsg) msg); + } else if (msg instanceof ClusterEventMsg) { + processor.processClusterEventMsg((ClusterEventMsg) msg); + } else { + logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName()); + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final TenantId tenantId; + private final DeviceId deviceId; + + public ActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) { + super(context); + this.tenantId = tenantId; + this.deviceId = deviceId; + } + + @Override + public DeviceActor create() throws Exception { + return new DeviceActor(context, tenantId, deviceId); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java new file mode 100644 index 00000000000..3949691938c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -0,0 +1,366 @@ +/** + * Copyright © 2016 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.actors.device; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.event.LoggingAdapter; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.rule.ChainProcessingContext; +import org.thingsboard.server.actors.rule.ChainProcessingMetaData; +import org.thingsboard.server.actors.rule.RuleProcessingMsg; +import org.thingsboard.server.actors.rule.RulesProcessedMsg; +import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; +import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.data.kv.AttributeKey; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.core.AttributesUpdateNotification; +import org.thingsboard.server.common.msg.core.BasicCommandAckResponse; +import org.thingsboard.server.common.msg.core.BasicToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.core.SessionCloseMsg; +import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg; +import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionType; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; +import org.thingsboard.server.extensions.api.device.DeviceAttributes; +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.RpcError; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author Andrew Shvayka + */ +public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { + + private final DeviceId deviceId; + private final Map attributeSubscriptions; + private final Map rpcSubscriptions; + + private final Map rpcPendingMap; + + private int rpcSeq = 0; + private DeviceAttributes deviceAttributes; + + public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) { + super(systemContext, logger); + this.deviceId = deviceId; + this.attributeSubscriptions = new HashMap<>(); + this.rpcSubscriptions = new HashMap<>(); + this.rpcPendingMap = new HashMap<>(); + refreshAttributes(); + } + + private void refreshAttributes() { + this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE), + fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE)); + } + + void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) { + ToDeviceRpcRequest request = msg.getMsg(); + ToDeviceRpcRequestBody body = request.getBody(); + ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( + rpcSeq++, + body.getMethod(), + body.getParams() + ); + + long timeout = request.getExpirationTime() - System.currentTimeMillis(); + if (timeout <= 0) { + logger.debug("[{}][{}] Ignoring message due to exp time reached", deviceId, request.getId(), request.getExpirationTime()); + return; + } + + boolean sent = rpcSubscriptions.size() > 0; + Set syncSessionSet = new HashSet<>(); + rpcSubscriptions.entrySet().forEach(sub -> { + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sub.getKey()); + sendMsgToSessionActor(response, sub.getValue().getServer()); + if (SessionType.SYNC == sub.getValue().getType()) { + syncSessionSet.add(sub.getKey()); + } + }); + syncSessionSet.forEach(rpcSubscriptions::remove); + + if (request.isOneway() && sent) { + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null); + context.parent().tell(responsePluginMsg, ActorRef.noSender()); + logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); + } else { + registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); + } + if (sent) { + logger.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); + } else { + logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); + } + + } + + private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) { + rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); + TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout); + scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout()); + } + + public void processTimeout(ActorContext context, TimeoutMsg msg) { + ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId()); + if (requestMd != null) { + logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION); + context.parent().tell(responsePluginMsg, ActorRef.noSender()); + } + } + + private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional server) { + if (!rpcPendingMap.isEmpty()) { + logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), sessionId); + if (type == SessionType.SYNC) { + logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); + rpcSubscriptions.remove(sessionId); + } + } else { + logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); + } + Set sentOneWayIds = new HashSet<>(); + if (type == SessionType.ASYNC) { + rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds)); + } else { + rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds)); + } + + sentOneWayIds.forEach(rpcPendingMap::remove); + } + + private Consumer> processPendingRpc(ActorContext context, SessionId sessionId, Optional server, Set sentOneWayIds) { + return entry -> { + ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg(); + ToDeviceRpcRequestBody body = request.getBody(); + if (request.isOneway()) { + sentOneWayIds.add(request.getId()); + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null); + context.parent().tell(responsePluginMsg, ActorRef.noSender()); + } + ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( + entry.getKey(), + body.getMethod(), + body.getParams() + ); + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(rpcRequest, sessionId); + sendMsgToSessionActor(response, server); + }; + } + + void process(ActorContext context, ToDeviceActorMsg msg) { + processSubscriptionCommands(context, msg); + processRpcResponses(context, msg); + processSessionStateMsgs(msg); + } + + void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) { + //TODO: improve this procedure to fetch only changed attributes. + refreshAttributes(); + //TODO: support attributes deletion + Set keys = msg.getKeys(); + if (attributeSubscriptions.size() > 0) { + ToDeviceMsg notification = null; + if (msg.isDeleted()) { + List sharedKeys = keys.stream() + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) + .collect(Collectors.toList()); + notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys)); + } else { + List attributes = keys.stream() + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) + .map(key -> deviceAttributes.getServerPublicAttribute(key.getAttributeKey())) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + if (attributes.size() > 0) { + notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes)); + } else { + logger.debug("[{}] No public server side attributes changed!", deviceId); + } + } + if (notification != null) { + ToDeviceMsg finalNotification = notification; + attributeSubscriptions.entrySet().forEach(sub -> { + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(finalNotification, sub.getKey()); + sendMsgToSessionActor(response, sub.getValue().getServer()); + }); + } + } else { + logger.debug("[{}] No registered attributes subscriptions to process!", deviceId); + } + } + + void process(ActorContext context, RuleChainDeviceMsg srcMsg) { + ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(), + srcMsg.getToDeviceActorMsg(), deviceAttributes, context.self()); + ChainProcessingContext ctx = new ChainProcessingContext(md); + if (ctx.getChainLength() > 0) { + RuleProcessingMsg msg = new RuleProcessingMsg(ctx); + ActorRef ruleActorRef = ctx.getCurrentActor(); + ruleActorRef.tell(msg, ActorRef.noSender()); + } else { + context.self().tell(new RulesProcessedMsg(ctx), context.self()); + } + } + + void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) { + SessionId sessionId = msg.getSessionId(); + FromDeviceMsg inMsg = msg.getPayload(); + if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) { + logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); + ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg; + ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId()); + boolean success = requestMd != null; + if (success) { + ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData()); + Optional pluginServerAddress = requestMd.getMsg().getServerAddress(); + if (pluginServerAddress.isPresent()) { + systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg); + logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); + } else { + context.parent().tell(responsePluginMsg, ActorRef.noSender()); + logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); + } + } else { + logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); + } + if (msg.getSessionType() == SessionType.SYNC) { + BasicCommandAckResponse response = success + ? BasicCommandAckResponse.onSuccess(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId()) + : BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException()); + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress()); + } + } + } + + public void processClusterEventMsg(ClusterEventMsg msg) { + if (!msg.isAdded()) { + logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress()); + Predicate> filter = e -> e.getValue().getServer() + .map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false); + attributeSubscriptions.entrySet().removeIf(filter); + rpcSubscriptions.entrySet().removeIf(filter); + } + } + + private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) { + return toPluginRpcResponseMsg(requestMsg, data, null); + } + + private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) { + return toPluginRpcResponseMsg(requestMsg, null, error); + } + + private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) { + return new ToPluginRpcResponseDeviceMsg( + requestMsg.getPluginId(), + requestMsg.getPluginTenantId(), + new FromDeviceRpcResponse(requestMsg.getMsg().getId(), + data, + error + ) + ); + } + + void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) { + ChainProcessingContext ctx = msg.getCtx(); + ToDeviceActorMsg inMsg = ctx.getInMsg(); + SessionId sid = inMsg.getSessionId(); + ToDeviceSessionActorMsg response; + if (ctx.getResponse() != null) { + response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid); + } else { + response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid); + } + sendMsgToSessionActor(response, inMsg.getServerAddress()); + } + + private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) { + SessionId sessionId = msg.getSessionId(); + SessionType sessionType = msg.getSessionType(); + FromDeviceMsg inMsg = msg.getPayload(); + if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) { + logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); + attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); + } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) { + logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); + attributeSubscriptions.remove(sessionId); + } else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) { + logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); + rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); + sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress()); + } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) { + logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); + rpcSubscriptions.remove(sessionId); + } + } + + private void processSessionStateMsgs(ToDeviceActorMsg msg) { + SessionId sessionId = msg.getSessionId(); + FromDeviceMsg inMsg = msg.getPayload(); + if (inMsg instanceof SessionCloseMsg) { + logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); + attributeSubscriptions.remove(sessionId); + rpcSubscriptions.remove(sessionId); + } + } + + private void sendMsgToSessionActor(ToDeviceSessionActorMsg response, Optional sessionAddress) { + if (sessionAddress.isPresent()) { + ServerAddress address = sessionAddress.get(); + logger.debug("{} Forwarding msg: {}", address, response); + systemContext.getRpcService().tell(sessionAddress.get(), response); + } else { + systemContext.getSessionManagerActor().tell(response, ActorRef.noSender()); + } + } + + private List fetchAttributes(String attributeType) { + return systemContext.getAttributesService().findAll(this.deviceId, attributeType); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java new file mode 100644 index 00000000000..178c6e7d5d8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 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.actors.device; + +import lombok.Data; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.session.SessionType; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@Data +public class SessionInfo { + private final SessionType type; + private final Optional server; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java new file mode 100644 index 00000000000..338c7eb3bc2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.actors.device; + +import lombok.Data; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToDeviceRpcRequestMetadata { + private final ToDeviceRpcRequestPluginMsg msg; + private final boolean sent; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java new file mode 100644 index 00000000000..1deb031f675 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java @@ -0,0 +1,151 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ComponentActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.stats.StatsPersistTick; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; + +public class PluginActor extends ComponentActor { + + private PluginActor(ActorSystemContext systemContext, TenantId tenantId, PluginId pluginId) { + super(systemContext, tenantId, pluginId); + setProcessor(new PluginActorMessageProcessor(tenantId, pluginId, systemContext, + logger, context().parent(), context().self())); + } + + @Override + public void onReceive(Object msg) throws Exception { + if (msg instanceof PluginWebsocketMsg) { + onWebsocketMsg((PluginWebsocketMsg) msg); + } else if (msg instanceof PluginRestMsg) { + onRestMsg((PluginRestMsg) msg); + } else if (msg instanceof PluginCallbackMessage) { + onPluginCallback((PluginCallbackMessage) msg); + } else if (msg instanceof RuleToPluginMsgWrapper) { + onRuleToPluginMsg((RuleToPluginMsgWrapper) msg); + } else if (msg instanceof PluginRpcMsg) { + onRpcMsg((PluginRpcMsg) msg); + } else if (msg instanceof ClusterEventMsg) { + onClusterEventMsg((ClusterEventMsg) msg); + } else if (msg instanceof ComponentLifecycleMsg) { + onComponentLifecycleMsg((ComponentLifecycleMsg) msg); + } else if (msg instanceof ToPluginRpcResponseDeviceMsg) { + onRpcResponse((ToPluginRpcResponseDeviceMsg) msg); + } else if (msg instanceof PluginTerminationMsg) { + logger.info("[{}][{}] Going to terminate plugin actor.", tenantId, id); + context().parent().tell(msg, ActorRef.noSender()); + context().stop(self()); + } else if (msg instanceof TimeoutMsg) { + onTimeoutMsg(context(), (TimeoutMsg) msg); + } else if (msg instanceof StatsPersistTick) { + onStatsPersistTick(id); + } else { + logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); + } + } + + private void onPluginCallback(PluginCallbackMessage msg) { + try { + processor.onPluginCallbackMsg(msg); + } catch (Exception e) { + logAndPersist("onPluginCallbackMsg", e); + } + } + + private void onTimeoutMsg(ActorContext context, TimeoutMsg msg) { + processor.onTimeoutMsg(context, msg); + } + + private void onRpcResponse(ToPluginRpcResponseDeviceMsg msg) { + processor.onDeviceRpcMsg(msg.getResponse()); + } + + private void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException { + logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg()); + try { + processor.onRuleToPluginMsg(msg); + increaseMessagesProcessedCount(); + } catch (Exception e) { + logAndPersist("onRuleMsg", e); + } + } + + private void onWebsocketMsg(PluginWebsocketMsg msg) { + logger.debug("[{}] Going to process web socket msg: {}", id, msg); + try { + processor.onWebsocketMsg(msg); + increaseMessagesProcessedCount(); + } catch (Exception e) { + logAndPersist("onWebsocketMsg", e); + } + } + + private void onRestMsg(PluginRestMsg msg) { + logger.debug("[{}] Going to process rest msg: {}", id, msg); + try { + processor.onRestMsg(msg); + increaseMessagesProcessedCount(); + } catch (Exception e) { + logAndPersist("onRestMsg", e); + } + } + + private void onRpcMsg(PluginRpcMsg msg) { + try { + logger.debug("[{}] Going to process rpc msg: {}", id, msg); + processor.onRpcMsg(msg); + } catch (Exception e) { + logAndPersist("onRpcMsg", e); + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final TenantId tenantId; + private final PluginId pluginId; + + public ActorCreator(ActorSystemContext context, TenantId tenantId, PluginId pluginId) { + super(context); + this.tenantId = tenantId; + this.pluginId = pluginId; + } + + @Override + public PluginActor create() throws Exception { + return new PluginActor(context, tenantId, pluginId); + } + } + + @Override + protected long getErrorPersistFrequency() { + return systemContext.getPluginErrorPersistFrequency(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java new file mode 100644 index 00000000000..72ae4bb5b52 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java @@ -0,0 +1,233 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.event.LoggingAdapter; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.extensions.api.plugins.Plugin; +import org.thingsboard.server.extensions.api.plugins.PluginInitializationException; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; + +/** + * @author Andrew Shvayka + */ +public class PluginActorMessageProcessor extends ComponentMsgProcessor { + + private final SharedPluginProcessingContext pluginCtx; + private final PluginProcessingContext trustedCtx; + private PluginMetaData pluginMd; + private Plugin pluginImpl; + private ComponentLifecycleState state; + + + protected PluginActorMessageProcessor(TenantId tenantId, PluginId pluginId, ActorSystemContext systemContext + , LoggingAdapter logger, ActorRef parent, ActorRef self) { + super(systemContext, logger, tenantId, pluginId); + this.pluginCtx = new SharedPluginProcessingContext(systemContext, tenantId, pluginId, parent, self); + this.trustedCtx = new PluginProcessingContext(pluginCtx, null); + } + + @Override + public void start() throws Exception { + logger.info("[{}] Going to start plugin actor.", entityId); + pluginMd = systemContext.getPluginService().findPluginById(entityId); + if (pluginMd == null) { + throw new PluginInitializationException("Plugin not found!"); + } + if (pluginMd.getConfiguration() == null) { + throw new PluginInitializationException("Plugin metadata is empty!"); + } + state = pluginMd.getState(); + if (state == ComponentLifecycleState.ACTIVE) { + logger.info("[{}] Plugin is active. Going to initialize plugin.", entityId); + initComponent(); + } else { + logger.info("[{}] Plugin is suspended. Skipping plugin initialization.", entityId); + } + } + + @Override + public void stop() throws Exception { + onStop(); + } + + private void initComponent() { + try { + pluginImpl = initComponent(pluginMd.getClazz(), ComponentType.PLUGIN, mapper.writeValueAsString(pluginMd.getConfiguration())); + } catch (InstantiationException e) { + throw new PluginInitializationException("No default constructor for plugin implementation!", e); + } catch (IllegalAccessException e) { + throw new PluginInitializationException("Illegal Access Exception during plugin initialization!", e); + } catch (ClassNotFoundException e) { + throw new PluginInitializationException("Plugin Class not found!", e); + } catch (JsonProcessingException e) { + throw new PluginInitializationException("Plugin Configuration is invalid!", e); + } catch (Exception e) { + throw new PluginInitializationException(e.getMessage(), e); + } + } + + public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException { + if (state == ComponentLifecycleState.ACTIVE) { + pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); + } else { + //TODO: reply with plugin suspended message + } + } + + public void onWebsocketMsg(PluginWebsocketMsg msg) { + if (state == ComponentLifecycleState.ACTIVE) { + pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg); + } else { + //TODO: reply with plugin suspended message + } + } + + public void onRestMsg(PluginRestMsg msg) { + if (state == ComponentLifecycleState.ACTIVE) { + pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg); + } + } + + public void onRpcMsg(PluginRpcMsg msg) { + if (state == ComponentLifecycleState.ACTIVE) { + pluginImpl.process(trustedCtx, msg.getRpcMsg()); + } else { + //TODO: reply with plugin suspended message + } + } + + public void onPluginCallbackMsg(PluginCallbackMessage msg) { + if (state == ComponentLifecycleState.ACTIVE) { + if (msg.isSuccess()) { + msg.getCallback().onSuccess(trustedCtx, msg.getV()); + } else { + msg.getCallback().onFailure(trustedCtx, msg.getE()); + } + } else { + //TODO: reply with plugin suspended message + } + } + + + public void onTimeoutMsg(ActorContext context, TimeoutMsg msg) { + if (state == ComponentLifecycleState.ACTIVE) { + pluginImpl.process(trustedCtx, msg); + } + } + + + public void onDeviceRpcMsg(FromDeviceRpcResponse response) { + if (state == ComponentLifecycleState.ACTIVE) { + pluginImpl.process(trustedCtx, response); + } + } + + @Override + public void onClusterEventMsg(ClusterEventMsg msg) { + if (state == ComponentLifecycleState.ACTIVE) { + ServerAddress address = msg.getServerAddress(); + if (msg.isAdded()) { + logger.debug("[{}] Going to process server add msg: {}", entityId, address); + pluginImpl.onServerAdded(trustedCtx, address); + } else { + logger.debug("[{}] Going to process server remove msg: {}", entityId, address); + pluginImpl.onServerRemoved(trustedCtx, address); + } + } + } + + @Override + public void onCreated(ActorContext context) { + logger.info("[{}] Going to process onCreated plugin.", entityId); + } + + @Override + public void onUpdate(ActorContext context) throws Exception { + PluginMetaData oldPluginMd = systemContext.getPluginService().findPluginById(entityId); + pluginMd = systemContext.getPluginService().findPluginById(entityId); + boolean requiresRestart = false; + logger.info("[{}] Plugin configuration was updated from {} to {}.", entityId, oldPluginMd, pluginMd); + if (!oldPluginMd.getClazz().equals(pluginMd.getClazz())) { + logger.info("[{}] Plugin requires restart due to clazz change from {} to {}.", + entityId, oldPluginMd.getClazz(), pluginMd.getClazz()); + requiresRestart = true; + } else if (oldPluginMd.getConfiguration().equals(pluginMd.getConfiguration())) { + logger.info("[{}] Plugin requires restart due to configuration change from {} to {}.", + entityId, oldPluginMd.getConfiguration(), pluginMd.getConfiguration()); + requiresRestart = true; + } + if (requiresRestart) { + this.state = ComponentLifecycleState.SUSPENDED; + if (pluginImpl != null) { + pluginImpl.stop(trustedCtx); + } + start(); + } + } + + @Override + public void onStop(ActorContext context) { + onStop(); + scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay()); + } + + private void onStop() { + logger.info("[{}] Going to process onStop plugin.", entityId); + this.state = ComponentLifecycleState.SUSPENDED; + if (pluginImpl != null) { + pluginImpl.stop(trustedCtx); + } + } + + @Override + public void onActivate(ActorContext context) throws Exception { + logger.info("[{}] Going to process onActivate plugin.", entityId); + this.state = ComponentLifecycleState.ACTIVE; + if (pluginImpl != null) { + pluginImpl.resume(trustedCtx); + logger.info("[{}] Plugin resumed.", entityId); + } else { + start(); + } + } + + @Override + public void onSuspend(ActorContext context) { + logger.info("[{}] Going to process onSuspend plugin.", entityId); + this.state = ComponentLifecycleState.SUSPENDED; + if (pluginImpl != null) { + pluginImpl.suspend(trustedCtx); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java new file mode 100644 index 00000000000..193ff31e411 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginCallbackMessage.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import lombok.Data; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.extensions.api.plugins.PluginCallback; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@ToString +public final class PluginCallbackMessage { + @Getter + private final PluginCallback callback; + @Getter + private final boolean success; + @Getter + private final V v; + @Getter + private final Exception e; + + public static PluginCallbackMessage onSuccess(PluginCallback callback, V data) { + return new PluginCallbackMessage(true, callback, data, null); + } + + public static PluginCallbackMessage onError(PluginCallback callback, Exception e) { + return new PluginCallbackMessage(false, callback, null, e); + } + + private PluginCallbackMessage(boolean success, PluginCallback callback, V v, Exception e) { + this.success = success; + this.callback = callback; + this.v = v; + this.e = e; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java new file mode 100644 index 00000000000..5541a2468ac --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java @@ -0,0 +1,306 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.ResultSetFuture; +import com.datastax.driver.core.Row; +import com.google.common.base.Function; +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.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKey; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.kv.TsKvQuery; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.PluginCallback; +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; + +import akka.actor.ActorRef; + +import javax.annotation.Nullable; + +@Slf4j +public final class PluginProcessingContext implements PluginContext { + + private static final Executor executor = Executors.newSingleThreadExecutor(); + + private final SharedPluginProcessingContext pluginCtx; + private final Optional securityCtx; + + public PluginProcessingContext(SharedPluginProcessingContext pluginCtx, PluginApiCallSecurityContext securityCtx) { + super(); + this.pluginCtx = pluginCtx; + this.securityCtx = Optional.ofNullable(securityCtx); + } + + @Override + public void sendPluginRpcMsg(RpcMsg msg) { + this.pluginCtx.rpcService.tell(new PluginRpcMsg(pluginCtx.tenantId, pluginCtx.pluginId, msg)); + } + + @Override + public void send(PluginWebsocketMsg wsMsg) throws IOException { + pluginCtx.msgEndpoint.send(wsMsg); + } + + @Override + public void close(PluginWebsocketSessionRef sessionRef) throws IOException { + pluginCtx.msgEndpoint.close(sessionRef); + } + + @Override + public void saveAttributes(DeviceId deviceId, String scope, List attributes, PluginCallback callback) { + validate(deviceId); + Set keys = new HashSet<>(); + for (AttributeKvEntry attribute : attributes) { + keys.add(new AttributeKey(scope, attribute.getKey())); + } + + ListenableFuture> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes); + Futures.addCallback(rsListFuture, getListCallback(callback, v -> { + onDeviceAttributesChanged(deviceId, keys); + return null; + }), executor); + } + + @Override + public Optional loadAttribute(DeviceId deviceId, String attributeType, String attributeKey) { + validate(deviceId); + AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey); + return Optional.ofNullable(attribute); + } + + @Override + public List loadAttributes(DeviceId deviceId, String attributeType, List attributeKeys) { + validate(deviceId); + List result = new ArrayList<>(attributeKeys.size()); + for (String attributeKey : attributeKeys) { + AttributeKvEntry attribute = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey); + if (attribute != null) { + result.add(attribute); + } + } + return result; + } + + @Override + public List loadAttributes(DeviceId deviceId, String attributeType) { + validate(deviceId); + return pluginCtx.attributesService.findAll(deviceId, attributeType); + } + + @Override + public void removeAttributes(DeviceId deviceId, String scope, List keys) { + validate(deviceId); + pluginCtx.attributesService.removeAll(deviceId, scope, keys); + onDeviceAttributesDeleted(deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet())); + } + + @Override + public void saveTsData(DeviceId deviceId, TsKvEntry entry, PluginCallback callback) { + validate(deviceId); + ListenableFuture> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry); + Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); + } + + @Override + public void saveTsData(DeviceId deviceId, List entries, PluginCallback callback) { + validate(deviceId); + ListenableFuture> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries); + Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); + } + + @Override + public List loadTimeseries(DeviceId deviceId, TsKvQuery query) { + validate(deviceId); + return pluginCtx.tsService.find(DataConstants.DEVICE, deviceId, query); + } + + @Override + public void loadLatestTimeseries(DeviceId deviceId, PluginCallback> callback) { + validate(deviceId); + ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId); + Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor); + } + + @Override + public void loadLatestTimeseries(DeviceId deviceId, Collection keys, PluginCallback> callback) { + validate(deviceId); + ListenableFuture> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys); + Futures.addCallback(rsListFuture, getListCallback(callback, rsList -> + { + List result = new ArrayList<>(); + for (ResultSet rs : rsList) { + Row row = rs.one(); + if (row != null) { + result.add(pluginCtx.tsService.convertResultToTsKvEntry(row)); + } + } + return result; + }), executor); + } + + @Override + public void reply(PluginToRuleMsg msg) { + pluginCtx.parentActor.tell(msg, ActorRef.noSender()); + } + + @Override + public boolean checkAccess(DeviceId deviceId) { + try { + return validate(deviceId); + } catch (IllegalStateException | IllegalArgumentException e) { + return false; + } + } + + @Override + public PluginId getPluginId() { + return pluginCtx.pluginId; + } + + @Override + public Optional getSecurityCtx() { + return securityCtx; + } + + private void onDeviceAttributesChanged(DeviceId deviceId, AttributeKey key) { + onDeviceAttributesChanged(deviceId, Collections.singleton(key)); + } + + private void onDeviceAttributesDeleted(DeviceId deviceId, Set keys) { + Device device = pluginCtx.deviceService.findDeviceById(deviceId); + pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), deviceId, keys)); + } + + private void onDeviceAttributesChanged(DeviceId deviceId, Set keys) { + Device device = pluginCtx.deviceService.findDeviceById(deviceId); + pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(device.getTenantId(), deviceId, keys)); + } + + private FutureCallback> getListCallback(final PluginCallback callback, Function, T> transformer) { + return new FutureCallback>() { + @Override + public void onSuccess(@Nullable List result) { + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender()); + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof Exception) { + pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender()); + } else { + log.error("Critical error: {}", t.getMessage(), t); + } + } + }; + } + + private FutureCallback getCallback(final PluginCallback callback, Function transformer) { + return new FutureCallback() { + @Override + public void onSuccess(@Nullable ResultSet result) { + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender()); + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof Exception) { + pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender()); + } else { + log.error("Critical error: {}", t.getMessage(), t); + } + } + }; + } + + // TODO: replace with our own exceptions + private boolean validate(DeviceId deviceId) { + if (securityCtx.isPresent()) { + PluginApiCallSecurityContext ctx = securityCtx.get(); + if (ctx.isTenantAdmin() || ctx.isCustomerUser()) { + Device device = pluginCtx.deviceService.findDeviceById(deviceId); + if (device == null) { + throw new IllegalStateException("Device not found!"); + } else { + if (!device.getTenantId().equals(ctx.getTenantId())) { + throw new IllegalArgumentException("Device belongs to different tenant!"); + } else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) { + throw new IllegalArgumentException("Device belongs to different customer!"); + } + } + } else { + return false; + } + } + return true; + } + + @Override + public Optional resolve(DeviceId deviceId) { + return pluginCtx.routingService.resolve(deviceId); + } + + @Override + public void getDevice(DeviceId deviceId, PluginCallback callback) { + //TODO: add caching here with async api. + Device device = pluginCtx.deviceService.findDeviceById(deviceId); + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, device), ActorRef.noSender()); + } + + @Override + public void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback> callback) { + //TODO: add caching here with async api. + List devices = pluginCtx.deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, new TextPageLink(limit)).getData(); + pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, devices), ActorRef.noSender()); + } + + @Override + public void sendRpcRequest(ToDeviceRpcRequest msg) { + pluginCtx.sendRpcRequest(msg); + } + + @Override + public void scheduleTimeoutMsg(TimeoutMsg msg) { + pluginCtx.scheduleTimeoutMsg(msg); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java new file mode 100644 index 00000000000..436310910d1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginTerminationMsg.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import org.thingsboard.server.actors.shared.ActorTerminationMsg; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.SessionId; + +/** + * @author Andrew Shvayka + */ +public class PluginTerminationMsg extends ActorTerminationMsg { + + public PluginTerminationMsg(PluginId id) { + super(id); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java b/application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java new file mode 100644 index 00000000000..599ad68ab6d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/RuleToPluginMsgWrapper.java @@ -0,0 +1,66 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.aware.RuleAwareMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; + +public class RuleToPluginMsgWrapper implements ToPluginActorMsg, RuleAwareMsg { + + private final TenantId pluginTenantId; + private final PluginId pluginId; + private final TenantId ruleTenantId; + private final RuleId ruleId; + private final RuleToPluginMsg msg; + + public RuleToPluginMsgWrapper(TenantId pluginTenantId, PluginId pluginId, TenantId ruleTenantId, RuleId ruleId, RuleToPluginMsg msg) { + super(); + this.pluginTenantId = pluginTenantId; + this.pluginId = pluginId; + this.ruleTenantId = ruleTenantId; + this.ruleId = ruleId; + this.msg = msg; + } + + @Override + public TenantId getPluginTenantId() { + return pluginTenantId; + } + + @Override + public PluginId getPluginId() { + return pluginId; + } + + public TenantId getRuleTenantId() { + return ruleTenantId; + } + + @Override + public RuleId getRuleId() { + return ruleId; + } + + + public RuleToPluginMsg getMsg() { + return msg; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java new file mode 100644 index 00000000000..71a6ac9b540 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java @@ -0,0 +1,111 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import akka.actor.ActorRef; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; +import scala.concurrent.duration.Duration; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +@Slf4j +public final class SharedPluginProcessingContext { + final ActorRef parentActor; + final ActorRef currentActor; + final ActorSystemContext systemContext; + final PluginWebSocketMsgEndpoint msgEndpoint; + final DeviceService deviceService; + final TimeseriesService tsService; + final AttributesService attributesService; + final ClusterRpcService rpcService; + final ClusterRoutingService routingService; + final PluginId pluginId; + final TenantId tenantId; + + public SharedPluginProcessingContext(ActorSystemContext sysContext, TenantId tenantId, PluginId pluginId, + ActorRef parentActor, ActorRef self) { + super(); + this.tenantId = tenantId; + this.pluginId = pluginId; + this.parentActor = parentActor; + this.currentActor = self; + this.systemContext = sysContext; + this.msgEndpoint = sysContext.getWsMsgEndpoint(); + this.tsService = sysContext.getTsService(); + this.attributesService = sysContext.getAttributesService(); + this.deviceService = sysContext.getDeviceService(); + this.rpcService = sysContext.getRpcService(); + this.routingService = sysContext.getRoutingService(); + } + + public PluginId getPluginId() { + return pluginId; + } + + public void toDeviceActor(DeviceAttributesEventNotificationMsg msg) { + forward(msg.getDeviceId(), msg, rpcService::tell); + } + + public void sendRpcRequest(ToDeviceRpcRequest msg) { + log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg); + ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg); + forward(msg.getDeviceId(), rpcMsg, rpcService::tell); + } + + private void forward(DeviceId deviceId, T msg, BiConsumer rpcFunction) { + Optional instance = routingService.resolve(deviceId); + if (instance.isPresent()) { + log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg); + rpcFunction.accept(instance.get(), msg); + } else { + log.trace("[{}] Forwarding msg {} to local device actor!", pluginId, msg); + parentActor.tell(msg, ActorRef.noSender()); + } + } + + public void scheduleTimeoutMsg(TimeoutMsg msg) { + log.debug("Scheduling msg {} with delay {} ms", msg, msg.getTimeout()); + systemContext.getScheduler().scheduleOnce( + Duration.create(msg.getTimeout(), TimeUnit.MILLISECONDS), + currentActor, + msg, + systemContext.getActorSystem().dispatcher(), + ActorRef.noSender()); + + } + + public ActorRef self() { + return currentActor; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/TimeoutScheduler.java b/application/src/main/java/org/thingsboard/server/actors/plugin/TimeoutScheduler.java new file mode 100644 index 00000000000..44a467646c0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/TimeoutScheduler.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.actors.plugin; + +import akka.actor.ActorRef; + +/** + * @author Andrew Shvayka + */ +public interface TimeoutScheduler { + + void scheduleMsgWithDelay(Object msg, long delayInMs); + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java new file mode 100644 index 00000000000..64a9a73e5a4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java @@ -0,0 +1,170 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import akka.actor.ActorRef; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.SerializationUtils; +import org.springframework.util.StringUtils; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ActorService; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.*; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; +import org.thingsboard.server.service.cluster.rpc.GrpcSession; +import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; + +import java.io.Serializable; +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public class BasicRpcSessionListener implements GrpcSessionListener { + + private final ActorSystemContext context; + private final ActorService service; + private final ActorRef manager; + private final ActorRef self; + + public BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) { + this.context = context; + this.service = context.getActorService(); + this.manager = manager; + this.self = self; + } + + @Override + public void onConnected(GrpcSession session) { + log.info("{} session started -> {}", getType(session), session.getRemoteServer()); + if (!session.isClient()) { + manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self); + } + } + + @Override + public void onDisconnected(GrpcSession session) { + log.info("{} session closed -> {}", getType(session), session.getRemoteServer()); + manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self); + } + + @Override + public void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg) { + if (log.isTraceEnabled()) { + log.trace("{} session [{}] received plugin msg {}", getType(session), session.getRemoteServer(), msg); + } + service.onMsg(convert(session.getRemoteServer(), msg)); + } + + @Override + public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) { + log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg); + service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray())); + } + + @Override + public void onToDeviceActorNotificationRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg) { + log.trace("{} session [{}] received device actor notification msg {}", getType(session), session.getRemoteServer(), msg); + service.onMsg((ToDeviceActorNotificationMsg) deserialize(msg.getData().toByteArray())); + } + + @Override + public void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg) { + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); + service.onMsg((ToDeviceSessionActorMsg) deserialize(msg.getData().toByteArray())); + } + + @Override + public void onToDeviceRpcRequestRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) { + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); + service.onMsg(deserialize(session.getRemoteServer(), msg)); + } + + @Override + public void onFromDeviceRpcResponseRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) { + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); + service.onMsg(deserialize(session.getRemoteServer(), msg)); + } + + @Override + public void onToAllNodesRpcMessage(GrpcSession session, ClusterAPIProtos.ToAllNodesRpcMessage msg) { + log.trace("{} session [{}] received session actor msg {}", getType(session), session.getRemoteServer(), msg); + service.onMsg((ToAllNodesMsg) deserialize(msg.getData().toByteArray())); + } + + @Override + public void onError(GrpcSession session, Throwable t) { + log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t); + manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self); + session.close(); + } + + private static String getType(GrpcSession session) { + return session.isClient() ? "Client" : "Server"; + } + + private static PluginRpcMsg convert(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcMessage msg) { + ClusterAPIProtos.PluginAddress address = msg.getAddress(); + TenantId tenantId = new TenantId(toUUID(address.getTenantId())); + PluginId pluginId = new PluginId(toUUID(address.getPluginId())); + RpcMsg rpcMsg = new RpcMsg(serverAddress, msg.getClazz(), msg.getData().toByteArray()); + return new PluginRpcMsg(tenantId, pluginId, rpcMsg); + } + + private static UUID toUUID(ClusterAPIProtos.Uid uid) { + return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb()); + } + + private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) { + ClusterAPIProtos.PluginAddress address = msg.getAddress(); + TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId())); + PluginId pluginId = new PluginId(toUUID(address.getPluginId())); + + TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId())); + DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId())); + + ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams()); + ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); + + return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request); + } + + private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) { + ClusterAPIProtos.PluginAddress address = msg.getAddress(); + TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId())); + PluginId pluginId = new PluginId(toUUID(address.getPluginId())); + + RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null; + FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error); + return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response); + } + + @SuppressWarnings("unchecked") + private static T deserialize(byte[] data) { + return (T) SerializationUtils.deserialize(data); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java new file mode 100644 index 00000000000..a528dd666b5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import lombok.Data; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +/** + * @author Andrew Shvayka + */ +@Data +public final class RpcBroadcastMsg { + private final ClusterAPIProtos.ToRpcServerMessage msg; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java new file mode 100644 index 00000000000..27ffdbabca9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java @@ -0,0 +1,192 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; +import org.thingsboard.server.service.cluster.discovery.ServerInstance; + +import java.util.*; + +/** + * @author Andrew Shvayka + */ +public class RpcManagerActor extends ContextAwareActor { + + private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + + private final Map sessionActors; + + private final Map> pendingMsgs; + + private final ServerAddress instance; + + public RpcManagerActor(ActorSystemContext systemContext) { + super(systemContext); + this.sessionActors = new HashMap<>(); + this.pendingMsgs = new HashMap<>(); + this.instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress(); + + systemContext.getDiscoveryService().getOtherServers().stream() + .filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0) + .forEach(otherServer -> onCreateSessionRequest( + new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null))); + + } + + @Override + public void onReceive(Object msg) throws Exception { + if (msg instanceof RpcSessionTellMsg) { + onMsg((RpcSessionTellMsg) msg); + } else if (msg instanceof RpcBroadcastMsg) { + onMsg((RpcBroadcastMsg) msg); + } else if (msg instanceof RpcSessionCreateRequestMsg) { + onCreateSessionRequest((RpcSessionCreateRequestMsg) msg); + } else if (msg instanceof RpcSessionConnectedMsg) { + onSessionConnected((RpcSessionConnectedMsg) msg); + } else if (msg instanceof RpcSessionDisconnectedMsg) { + onSessionDisconnected((RpcSessionDisconnectedMsg) msg); + } else if (msg instanceof RpcSessionClosedMsg) { + onSessionClosed((RpcSessionClosedMsg) msg); + } else if (msg instanceof ClusterEventMsg) { + onClusterEvent((ClusterEventMsg) msg); + } + } + + private void onMsg(RpcBroadcastMsg msg) { + log.debug("Forwarding msg to session actors {}", msg); + sessionActors.keySet().forEach(address -> onMsg(new RpcSessionTellMsg(address, msg.getMsg()))); + pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg())); + } + + private void onMsg(RpcSessionTellMsg msg) { + ServerAddress address = msg.getServerAddress(); + SessionActorInfo session = sessionActors.get(address); + if (session != null) { + log.debug("{} Forwarding msg to session actor", address); + session.actor.tell(msg, ActorRef.noSender()); + } else { + log.debug("{} Storing msg to pending queue", address); + Queue queue = pendingMsgs.get(address); + if (queue == null) { + queue = new LinkedList<>(); + pendingMsgs.put(address, queue); + } + queue.add(msg.getMsg()); + } + } + + @Override + public void postStop() { + sessionActors.clear(); + pendingMsgs.clear(); + } + + private void onClusterEvent(ClusterEventMsg msg) { + ServerAddress server = msg.getServerAddress(); + if (server.compareTo(instance) > 0) { + if (msg.isAdded()) { + onCreateSessionRequest(new RpcSessionCreateRequestMsg(UUID.randomUUID(), server, null)); + } else { + onSessionClose(false, server); + } + } + } + + private void onSessionConnected(RpcSessionConnectedMsg msg) { + register(msg.getRemoteAddress(), msg.getId(), context().sender()); + } + + private void onSessionDisconnected(RpcSessionDisconnectedMsg msg) { + boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress()); + onSessionClose(reconnect, msg.getRemoteAddress()); + } + + private void onSessionClosed(RpcSessionClosedMsg msg) { + boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress()); + onSessionClose(reconnect, msg.getRemoteAddress()); + } + + private boolean isRegistered(ServerAddress address) { + for (ServerInstance server : systemContext.getDiscoveryService().getOtherServers()) { + if (server.getServerAddress().equals(address)) { + return true; + } + } + return false; + } + + private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) { + log.debug("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect); + SessionActorInfo sessionRef = sessionActors.get(remoteAddress); + if (context().sender().equals(sessionRef.actor)) { + sessionActors.remove(remoteAddress); + pendingMsgs.remove(remoteAddress); + if (reconnect) { + onCreateSessionRequest(new RpcSessionCreateRequestMsg(sessionRef.sessionId, remoteAddress, null)); + } + } + } + + private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) { + ActorRef actorRef = createSessionActor(msg); + if (msg.getRemoteAddress() != null) { + register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef); + } + } + + private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) { + sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender)); + log.debug("[{}][{}] Registering session actor.", remoteAddress, uuid); + Queue data = pendingMsgs.remove(remoteAddress); + if (data != null) { + log.debug("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size()); + data.forEach(msg -> sender.tell(new RpcSessionTellMsg(remoteAddress, msg), ActorRef.noSender())); + } else { + log.debug("[{}][{}] No pending messages to forward.", remoteAddress, uuid); + } + } + + private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) { + log.debug("[{}] Creating session actor.", msg.getMsgUid()); + ActorRef actor = context().actorOf( + Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME)); + actor.tell(msg, context().self()); + return actor; + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + public ActorCreator(ActorSystemContext context) { + super(context); + } + + @Override + public RpcManagerActor create() throws Exception { + return new RpcManagerActor(context); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java new file mode 100644 index 00000000000..a66fbc55db6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import akka.event.Logging; +import akka.event.LoggingAdapter; +import io.grpc.Channel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.StreamObserver; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; +import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; +import org.thingsboard.server.service.cluster.rpc.GrpcSession; +import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +public class RpcSessionActor extends ContextAwareActor { + + private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + + private final UUID sessionId; + private GrpcSession session; + private GrpcSessionListener listener; + + public RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) { + super(systemContext); + this.sessionId = sessionId; + } + + @Override + public void onReceive(Object msg) throws Exception { + if (msg instanceof RpcSessionTellMsg) { + tell((RpcSessionTellMsg) msg); + } else if (msg instanceof RpcSessionCreateRequestMsg) { + initSession((RpcSessionCreateRequestMsg) msg); + } + } + + private void tell(RpcSessionTellMsg msg) { + session.sendMsg(msg.getMsg()); + } + + @Override + public void postStop() { + log.info("Closing session -> {}", session.getRemoteServer()); + session.close(); + } + + private void initSession(RpcSessionCreateRequestMsg msg) { + log.info("[{}] Initializing session", context().self()); + ServerAddress remoteServer = msg.getRemoteAddress(); + listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self()); + if (msg.getRemoteAddress() == null) { + // Server session + session = new GrpcSession(listener); + session.setOutputStream(msg.getResponseObserver()); + session.initInputStream(); + session.initOutputStream(); + systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream()); + } else { + // Client session + Channel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext(true).build(); + session = new GrpcSession(remoteServer, listener); + session.initInputStream(); + + ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel); + StreamObserver outputStream = stub.handlePluginMsgs(session.getInputStream()); + + session.setOutputStream(outputStream); + session.initOutputStream(); + outputStream.onNext(toConnectMsg()); + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final UUID sessionId; + + public ActorCreator(ActorSystemContext context, UUID sessionId) { + super(context); + this.sessionId = sessionId; + } + + @Override + public RpcSessionActor create() throws Exception { + return new RpcSessionActor(context, sessionId); + } + } + + private ClusterAPIProtos.ToRpcServerMessage toConnectMsg() { + ServerAddress instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress(); + return ClusterAPIProtos.ToRpcServerMessage.newBuilder().setConnectMsg( + ClusterAPIProtos.ConnectRpcMessage.newBuilder().setServerAddress( + ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost()).setPort(instance.getPort()).build()).build()).build(); + + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java new file mode 100644 index 00000000000..33bde077b29 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import lombok.Data; +import org.thingsboard.server.common.msg.cluster.ServerAddress; + +/** + * @author Andrew Shvayka + */ +@Data +public final class RpcSessionClosedMsg { + + private final boolean client; + private final ServerAddress remoteAddress; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java new file mode 100644 index 00000000000..0cacf6ccf6d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import lombok.Data; +import org.thingsboard.server.common.msg.cluster.ServerAddress; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Data +public final class RpcSessionConnectedMsg { + + private final ServerAddress remoteAddress; + private final UUID id; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java new file mode 100644 index 00000000000..0fe2817effa --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import io.grpc.stub.StreamObserver; +import lombok.Data; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Data +public final class RpcSessionCreateRequestMsg { + + private final UUID msgUid; + private final ServerAddress remoteAddress; + private final StreamObserver responseObserver; + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java new file mode 100644 index 00000000000..fe6087c73ed --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import lombok.Data; +import org.thingsboard.server.common.msg.cluster.ServerAddress; + +/** + * @author Andrew Shvayka + */ +@Data +public final class RpcSessionDisconnectedMsg { + + private final boolean client; + private final ServerAddress remoteAddress; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java new file mode 100644 index 00000000000..7a1853af84b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import lombok.Data; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +/** + * @author Andrew Shvayka + */ +@Data +public final class RpcSessionTellMsg { + private final ServerAddress serverAddress; + private final ClusterAPIProtos.ToRpcServerMessage msg; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java b/application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java new file mode 100644 index 00000000000..70bad398813 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 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.actors.rpc; + +import akka.actor.ActorRef; +import lombok.Data; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Data +public final class SessionActorInfo { + protected final UUID sessionId; + protected final ActorRef actor; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java new file mode 100644 index 00000000000..2c7adef8e99 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java @@ -0,0 +1,104 @@ +/** + * Copyright © 2016 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.actors.rule; + +import akka.actor.ActorRef; +import org.thingsboard.server.common.msg.core.RuleEngineError; +import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; +import org.thingsboard.server.extensions.api.device.DeviceAttributes; + +public class ChainProcessingContext { + + private final ChainProcessingMetaData md; + private final int index; + private final RuleEngineError error; + private ToDeviceMsg response; + + + public ChainProcessingContext(ChainProcessingMetaData md) { + super(); + this.md = md; + this.index = 0; + this.error = RuleEngineError.NO_RULES; + } + + private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) { + super(); + this.md = other.md; + this.index = other.index + indexOffset; + this.error = error; + this.response = other.response; + + if (this.index < 0 || this.index >= this.md.chain.size()) { + throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!"); + } + } + + public ActorRef getDeviceActor() { + return md.originator; + } + + public ActorRef getCurrentActor() { + return md.chain.getRuleActorMd(index).getActorRef(); + } + + public boolean hasNext() { + return (getChainLength() - 1) > index; + } + + public boolean isFailure() { + return (error != null && error.isCritical()) || (response != null && !response.isSuccess()); + } + + public ChainProcessingContext getNext() { + return new ChainProcessingContext(this, 1, this.error); + } + + public ChainProcessingContext withError(RuleEngineError error) { + if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) { + return new ChainProcessingContext(this, 0, error); + } else { + return this; + } + } + + public int getChainLength() { + return md.chain.size(); + } + + public ToDeviceActorMsg getInMsg() { + return md.inMsg; + } + + public DeviceAttributes getAttributes() { + return md.deviceAttributes; + } + + public ToDeviceMsg getResponse() { + return response; + } + + public void mergeResponse(ToDeviceMsg response) { + // TODO add merge logic + this.response = response; + } + + public RuleEngineErrorMsg getError() { + return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java b/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java new file mode 100644 index 00000000000..652cd24e9ee --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016 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.actors.rule; + +import org.thingsboard.server.extensions.api.device.DeviceAttributes; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; + +import akka.actor.ActorRef; + +/** + * Immutable part of chain processing data; + * + * @author ashvayka + */ +public final class ChainProcessingMetaData { + + final RuleActorChain chain; + final ToDeviceActorMsg inMsg; + final ActorRef originator; + final DeviceAttributes deviceAttributes; + + public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceAttributes deviceAttributes, ActorRef originator) { + super(); + this.chain = chain; + this.inMsg = inMsg; + this.originator = originator; + this.deviceAttributes = deviceAttributes; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java b/application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java new file mode 100644 index 00000000000..d7c6ce42793 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016 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.actors.rule; + +public class ComplexRuleActorChain implements RuleActorChain { + + private final RuleActorChain systemChain; + private final RuleActorChain tenantChain; + + public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) { + super(); + this.systemChain = systemChain; + this.tenantChain = tenantChain; + } + + @Override + public int size() { + return systemChain.size() + tenantChain.size(); + } + + @Override + public RuleActorMetaData getRuleActorMd(int index) { + if (index < systemChain.size()) { + return systemChain.getRuleActorMd(index); + } else { + return tenantChain.getRuleActorMd(index - systemChain.size()); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java b/application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java new file mode 100644 index 00000000000..edbd35f2b0c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/CompoundRuleActorChain.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016 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.actors.rule; + +public class CompoundRuleActorChain { + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java new file mode 100644 index 00000000000..f16a444f0e5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java @@ -0,0 +1,90 @@ +/** + * Copyright © 2016 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.actors.rule; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ComponentActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.stats.StatsPersistTick; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; + +public class RuleActor extends ComponentActor { + + private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) { + super(systemContext, tenantId, ruleId); + setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger)); + } + + @Override + public void onReceive(Object msg) throws Exception { + logger.debug("[{}] Received message: {}", id, msg); + if (msg instanceof RuleProcessingMsg) { + try { + processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg); + increaseMessagesProcessedCount(); + } catch (Exception e) { + logAndPersist("onDeviceMsg", e); + } + } else if (msg instanceof PluginToRuleMsg) { + try { + processor.onPluginMsg(context(), (PluginToRuleMsg) msg); + } catch (Exception e) { + logAndPersist("onPluginMsg", e); + } + } else if (msg instanceof ComponentLifecycleMsg) { + onComponentLifecycleMsg((ComponentLifecycleMsg) msg); + } else if (msg instanceof ClusterEventMsg) { + onClusterEventMsg((ClusterEventMsg) msg); + } else if (msg instanceof RuleToPluginTimeoutMsg) { + try { + processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg); + } catch (Exception e) { + logAndPersist("onTimeoutMsg", e); + } + } else if (msg instanceof StatsPersistTick) { + onStatsPersistTick(id); + } else { + logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final TenantId tenantId; + private final RuleId ruleId; + + public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) { + super(context); + this.tenantId = tenantId; + this.ruleId = ruleId; + } + + @Override + public RuleActor create() throws Exception { + return new RuleActor(context, tenantId, ruleId); + } + } + + @Override + protected long getErrorPersistFrequency() { + return systemContext.getRuleErrorPersistFrequency(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java new file mode 100644 index 00000000000..ee73d8190a4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorChain.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 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.actors.rule; + +public interface RuleActorChain { + + int size(); + + RuleActorMetaData getRuleActorMd(int index); + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java new file mode 100644 index 00000000000..82011c0d3c9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java @@ -0,0 +1,339 @@ +/** + * Copyright © 2016 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.actors.rule; + +import java.util.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper; +import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.data.rule.RuleMetaData; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.core.BasicRequest; +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; +import org.thingsboard.server.common.msg.core.RuleEngineError; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; +import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException; +import org.thingsboard.server.extensions.api.rules.*; +import org.thingsboard.server.extensions.api.plugins.PluginAction; +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; + +import com.fasterxml.jackson.databind.JsonNode; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.event.LoggingAdapter; + +class RuleActorMessageProcessor extends ComponentMsgProcessor { + + private final RuleProcessingContext ruleCtx; + private final Map pendingMsgMap; + + private RuleMetaData ruleMd; + private ComponentLifecycleState state; + private List filters; + private RuleProcessor processor; + private PluginAction action; + + private TenantId pluginTenantId; + private PluginId pluginId; + + protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) { + super(systemContext, logger, tenantId, ruleId); + this.pendingMsgMap = new HashMap<>(); + this.ruleCtx = new RuleProcessingContext(systemContext, ruleId); + } + + @Override + public void start() throws Exception { + logger.info("[{}][{}] Starting rule actor.", entityId, tenantId); + ruleMd = systemContext.getRuleService().findRuleById(entityId); + if (ruleMd == null) { + throw new RuleInitializationException("Rule not found!"); + } + state = ruleMd.getState(); + if (state == ComponentLifecycleState.ACTIVE) { + logger.info("[{}] Rule is active. Going to initialize rule components.", entityId); + initComponent(); + } else { + logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId); + } + + logger.info("[{}][{}] Started rule actor.", entityId, tenantId); + } + + @Override + public void stop() throws Exception { + onStop(); + } + + + private void initComponent() throws RuleException { + try { + if (!ruleMd.getFilters().isArray()) { + throw new RuntimeException("Filters are not array!"); + } + fetchPluginInfo(); + initFilters(); + initProcessor(); + initAction(); + } catch (RuntimeException e) { + throw new RuleInitializationException("Unknown runtime exception!", e); + } catch (InstantiationException e) { + throw new RuleInitializationException("No default constructor for rule implementation!", e); + } catch (IllegalAccessException e) { + throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); + } catch (ClassNotFoundException e) { + throw new RuleInitializationException("Rule Class not found!", e); + } catch (Exception e) { + throw new RuleException(e.getMessage(), e); + } + } + + private void initAction() throws Exception { + JsonNode actionMd = ruleMd.getAction(); + action = initComponent(actionMd); + } + + private void initProcessor() throws Exception { + if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) { + processor = initComponent(ruleMd.getProcessor()); + } + } + + private void initFilters() throws Exception { + filters = new ArrayList<>(ruleMd.getFilters().size()); + for (int i = 0; i < ruleMd.getFilters().size(); i++) { + filters.add(initComponent(ruleMd.getFilters().get(i))); + } + } + + private void fetchPluginInfo() { + PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken()); + pluginTenantId = pluginMd.getTenantId(); + pluginId = pluginMd.getId(); + } + + protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException { + if (state != ComponentLifecycleState.ACTIVE) { + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES); + return; + } + ChainProcessingContext chainCtx = msg.getCtx(); + ToDeviceActorMsg inMsg = chainCtx.getInMsg(); + + ruleCtx.update(inMsg, chainCtx.getAttributes()); + + logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg); + for (RuleFilter filter : filters) { + if (!filter.filter(ruleCtx, inMsg)) { + logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg); + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED); + return; + } + } + RuleProcessingMetaData inMsgMd; + if (processor != null) { + logger.debug("[{}] Going to process in msg: {}", entityId, inMsg); + inMsgMd = processor.process(ruleCtx, inMsg); + } else { + inMsgMd = new RuleProcessingMetaData(); + } + logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg); + Optional> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); + if (ruleToPluginMsgOptional.isPresent()) { + RuleToPluginMsg ruleToPluginMsg = ruleToPluginMsgOptional.get(); + logger.debug("[{}] Device msg is converter to: {}", entityId, ruleToPluginMsg); + context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); + if (action.isOneWayAction()) { + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); + } else { + pendingMsgMap.put(ruleToPluginMsg.getUid(), msg); + scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout()); + } + } else { + logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId); + pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_REQUEST_FROM_ACTIONS); + return; + } + } + + public void onPluginMsg(ActorContext context, PluginToRuleMsg msg) { + RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid()); + if (pendingMsg != null) { + ChainProcessingContext ctx = pendingMsg.getCtx(); + Optional ruleResponseOptional = action.convert(msg); + if (ruleResponseOptional.isPresent()) { + ctx.mergeResponse(ruleResponseOptional.get()); + pushToNextRule(context, ctx, null); + } else { + pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS); + } + } else { + logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid()); + } + } + + public void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) { + RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId()); + if (pendingMsg != null) { + logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg); + ChainProcessingContext ctx = pendingMsg.getCtx(); + pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT); + } + } + + private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) { + if (error != null) { + ctx = ctx.withError(error); + } + if (ctx.isFailure()) { + logger.debug("[{}] Forwarding processing chain to device actor due to failure.", ctx.getInMsg().getDeviceId()); + ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); + } else if (!ctx.hasNext()) { + logger.debug("[{}] Forwarding processing chain to device actor due to end of chain.", ctx.getInMsg().getDeviceId()); + ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); + } else { + logger.debug("[{}] Forwarding processing chain to next rule actor.", ctx.getInMsg().getDeviceId()); + ChainProcessingContext nextTask = ctx.getNext(); + nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self()); + } + } + + @Override + public void onCreated(ActorContext context) { + logger.info("[{}] Going to process onCreated rule.", entityId); + } + + @Override + public void onUpdate(ActorContext context) throws RuleException { + RuleMetaData oldRuleMd = ruleMd; + ruleMd = systemContext.getRuleService().findRuleById(entityId); + logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd); + try { + fetchPluginInfo(); + if (!Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) { + logger.info("[{}] Rule filters require restart due to json change from {} to {}.", + entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters())); + stopFilters(); + initFilters(); + } + if (!Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) { + logger.info("[{}] Rule processor require restart due to configuration change.", entityId); + stopProcessor(); + initProcessor(); + } + if (!Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) { + logger.info("[{}] Rule action require restart due to configuration change.", entityId); + stopAction(); + initAction(); + } + } catch (RuntimeException e) { + throw new RuleInitializationException("Unknown runtime exception!", e); + } catch (InstantiationException e) { + throw new RuleInitializationException("No default constructor for rule implementation!", e); + } catch (IllegalAccessException e) { + throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); + } catch (ClassNotFoundException e) { + throw new RuleInitializationException("Rule Class not found!", e); + } catch (JsonProcessingException e) { + throw new RuleInitializationException("Rule configuration is invalid!", e); + } catch (Exception e) { + throw new RuleInitializationException(e.getMessage(), e); + } + } + + @Override + public void onActivate(ActorContext context) throws Exception { + logger.info("[{}] Going to process onActivate rule.", entityId); + this.state = ComponentLifecycleState.ACTIVE; + if (action != null) { + if (filters != null) { + filters.forEach(f -> f.resume()); + } + if (processor != null) { + processor.resume(); + } + if (action != null) { + action.resume(); + } + logger.info("[{}] Rule resumed.", entityId); + } else { + start(); + } + } + + @Override + public void onSuspend(ActorContext context) { + logger.info("[{}] Going to process onSuspend rule.", entityId); + this.state = ComponentLifecycleState.SUSPENDED; + if (filters != null) { + filters.forEach(f -> f.suspend()); + } + if (processor != null) { + processor.suspend(); + } + if (action != null) { + action.suspend(); + } + } + + @Override + public void onStop(ActorContext context) { + logger.info("[{}] Going to process onStop rule.", entityId); + onStop(); + scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay()); + } + + private void onStop() { + this.state = ComponentLifecycleState.SUSPENDED; + stopFilters(); + stopProcessor(); + stopAction(); + } + + @Override + public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { + + } + + private void stopAction() { + if (action != null) { + action.stop(); + } + } + + private void stopProcessor() { + if (processor != null) { + processor.stop(); + } + } + + private void stopFilters() { + if (filters != null) { + filters.forEach(f -> f.stop()); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java new file mode 100644 index 00000000000..e25c8aae871 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java @@ -0,0 +1,107 @@ +/** + * Copyright © 2016 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.actors.rule; + +import java.util.Comparator; + +import org.thingsboard.server.common.data.id.RuleId; + +import akka.actor.ActorRef; + +public class RuleActorMetaData { + + private final RuleId ruleId; + private final boolean systemRule; + private final int weight; + private final ActorRef actorRef; + + public static final Comparator RULE_ACTOR_MD_COMPARATOR = new Comparator() { + + @Override + public int compare(RuleActorMetaData r1, RuleActorMetaData r2) { + if (r1.isSystemRule() && !r2.isSystemRule()) { + return 1; + } else if (!r1.isSystemRule() && r2.isSystemRule()) { + return -1; + } else { + return Integer.compare(r2.getWeight(), r1.getWeight()); + } + } + }; + + public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) { + return new RuleActorMetaData(ruleId, true, weight, actorRef); + } + + public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) { + return new RuleActorMetaData(ruleId, false, weight, actorRef); + } + + private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) { + super(); + this.ruleId = ruleId; + this.systemRule = systemRule; + this.weight = weight; + this.actorRef = actorRef; + } + + public RuleId getRuleId() { + return ruleId; + } + + public boolean isSystemRule() { + return systemRule; + } + + public int getWeight() { + return weight; + } + + public ActorRef getActorRef() { + return actorRef; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RuleActorMetaData other = (RuleActorMetaData) obj; + if (ruleId == null) { + if (other.ruleId != null) + return false; + } else if (!ruleId.equals(other.ruleId)) + return false; + return true; + } + + @Override + public String toString() { + return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]"; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java new file mode 100644 index 00000000000..507b9557c26 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleContextAwareMsgProcessor.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016 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.actors.rule; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; +import org.thingsboard.server.common.data.id.RuleId; + +import akka.event.LoggingAdapter; + +public class RuleContextAwareMsgProcessor extends AbstractContextAwareMsgProcessor { + + private final RuleId ruleId; + + protected RuleContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, RuleId ruleId) { + super(systemContext, logger); + this.ruleId = ruleId; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java new file mode 100644 index 00000000000..bd0728507f3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java @@ -0,0 +1,89 @@ +/** + * Copyright © 2016 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.actors.rule; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.id.*; +import org.thingsboard.server.dao.event.EventService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.extensions.api.device.DeviceAttributes; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.rules.RuleContext; + +import java.util.Optional; + +public class RuleProcessingContext implements RuleContext { + + private final TimeseriesService tsService; + private final EventService eventService; + private final RuleId ruleId; + private TenantId tenantId; + private CustomerId customerId; + private DeviceId deviceId; + private DeviceAttributes deviceAttributes; + + RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) { + this.tsService = systemContext.getTsService(); + this.eventService = systemContext.getEventService(); + this.ruleId = ruleId; + } + + void update(ToDeviceActorMsg toDeviceActorMsg, DeviceAttributes attributes) { + this.tenantId = toDeviceActorMsg.getTenantId(); + this.customerId = toDeviceActorMsg.getCustomerId(); + this.deviceId = toDeviceActorMsg.getDeviceId(); + this.deviceAttributes = attributes; + } + + @Override + public RuleId getRuleId() { + return ruleId; + } + + @Override + public DeviceAttributes getDeviceAttributes() { + return deviceAttributes; + } + + @Override + public Event save(Event event) { + checkEvent(event); + return eventService.save(event); + } + + @Override + public Optional saveIfNotExists(Event event) { + checkEvent(event); + return eventService.saveIfNotExists(event); + } + + @Override + public Optional findEvent(String eventType, String eventUid) { + return eventService.findEvent(tenantId, deviceId, eventType, eventUid); + } + + private void checkEvent(Event event) { + if (event.getTenantId() == null) { + event.setTenantId(tenantId); + } else if (!tenantId.equals(event.getTenantId())) { + throw new IllegalArgumentException("Invalid Tenant id!"); + } + if (event.getEntityId() == null) { + event.setEntityId(deviceId); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java new file mode 100644 index 00000000000..548c180ea73 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.actors.rule; + +public class RuleProcessingMsg { + + private final ChainProcessingContext ctx; + + public RuleProcessingMsg(ChainProcessingContext ctx) { + super(); + this.ctx = ctx; + } + + public ChainProcessingContext getCtx() { + return ctx; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java new file mode 100644 index 00000000000..2e920056f82 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleTerminationMsg.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 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.actors.rule; + +import org.thingsboard.server.actors.shared.ActorTerminationMsg; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleId; + +/** + * @author Andrew Shvayka + */ +public class RuleTerminationMsg extends ActorTerminationMsg { + + public RuleTerminationMsg(RuleId id) { + super(id); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java b/application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java new file mode 100644 index 00000000000..38595d662ac --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RuleToPluginTimeoutMsg.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016 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.actors.rule; + +import java.io.Serializable; +import java.util.UUID; + +public class RuleToPluginTimeoutMsg implements Serializable { + + private static final long serialVersionUID = 1L; + + private final UUID msgId; + + public RuleToPluginTimeoutMsg(UUID msgId) { + super(); + this.msgId = msgId; + } + + public UUID getMsgId() { + return msgId; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java b/application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java new file mode 100644 index 00000000000..dfebeaccd9d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/RulesProcessedMsg.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016 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.actors.rule; + +public class RulesProcessedMsg { + private final ChainProcessingContext ctx; + + public RulesProcessedMsg(ChainProcessingContext ctx) { + super(); + this.ctx = ctx; + } + + public ChainProcessingContext getCtx() { + return ctx; + } + + @Override + public String toString() { + return "RulesProcessedMsg [ctx=" + ctx + "]"; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java b/application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java new file mode 100644 index 00000000000..8112ac4c0da --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/rule/SimpleRuleActorChain.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016 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.actors.rule; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class SimpleRuleActorChain implements RuleActorChain { + + private final List rules; + + public SimpleRuleActorChain(Set ruleSet) { + rules = new ArrayList<>(ruleSet); + Collections.sort(rules, RuleActorMetaData.RULE_ACTOR_MD_COMPARATOR); + } + + public int size() { + return rules.size(); + } + + public RuleActorMetaData getRuleActorMd(int index) { + return rules.get(index); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java new file mode 100644 index 00000000000..1c64f546c1b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.actors.service; + +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.transport.SessionMsgProcessor; +import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; +import org.thingsboard.server.service.cluster.rpc.RpcMsgListener; + +public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener { + + void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state); + + void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state); +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java new file mode 100644 index 00000000000..f99155236dd --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java @@ -0,0 +1,169 @@ +/** + * Copyright © 2016 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.actors.service; + +import akka.actor.ActorRef; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.actors.stats.StatsPersistMsg; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; + +/** + * @author Andrew Shvayka + */ +public abstract class ComponentActor> extends ContextAwareActor { + + protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); + + private long lastPersistedErrorTs = 0L; + protected final TenantId tenantId; + protected final T id; + protected P processor; + private long messagesProcessed; + private long errorsOccurred; + + public ComponentActor(ActorSystemContext systemContext, TenantId tenantId, T id) { + super(systemContext); + this.tenantId = tenantId; + this.id = id; + } + + protected void setProcessor(P processor) { + this.processor = processor; + } + + @Override + public void preStart() { + try { + processor.start(); + logLifecycleEvent(ComponentLifecycleEvent.STARTED); + if (systemContext.isStatisticsEnabled()) { + scheduleStatsPersistTick(); + } + } catch (Exception e) { + logger.warning("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e); + logAndPersist("OnStart", e, true); + logLifecycleEvent(ComponentLifecycleEvent.STARTED, e); + } + } + + private void scheduleStatsPersistTick() { + try { + processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency()); + } catch (Exception e) { + logger.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage()); + logAndPersist("onScheduleStatsPersistMsg", e); + } + } + + @Override + public void postStop() { + try { + processor.stop(); + logLifecycleEvent(ComponentLifecycleEvent.STOPPED); + } catch (Exception e) { + logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage()); + logAndPersist("OnStop", e, true); + logLifecycleEvent(ComponentLifecycleEvent.STOPPED, e); + } + } + + protected void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { + try { + switch (msg.getEvent()) { + case CREATED: + processor.onCreated(context()); + break; + case UPDATED: + processor.onUpdate(context()); + break; + case ACTIVATED: + processor.onActivate(context()); + break; + case SUSPENDED: + processor.onSuspend(context()); + break; + case DELETED: + processor.onStop(context()); + } + logLifecycleEvent(msg.getEvent()); + } catch (Exception e) { + logAndPersist("onLifecycleMsg", e, true); + logLifecycleEvent(msg.getEvent(), e); + } + } + + protected void onClusterEventMsg(ClusterEventMsg msg) { + try { + processor.onClusterEventMsg(msg); + } catch (Exception e) { + logAndPersist("onClusterEventMsg", e); + } + } + + protected void onStatsPersistTick(EntityId entityId) { + try { + systemContext.getStatsActor().tell(new StatsPersistMsg(messagesProcessed, errorsOccurred, tenantId, entityId), ActorRef.noSender()); + resetStatsCounters(); + } catch (Exception e) { + logAndPersist("onStatsPersistTick", e); + } + } + + private void resetStatsCounters() { + messagesProcessed = 0; + errorsOccurred = 0; + } + + protected void increaseMessagesProcessedCount() { + messagesProcessed++; + } + + + protected void logAndPersist(String method, Exception e) { + logAndPersist(method, e, false); + } + + private void logAndPersist(String method, Exception e, boolean critical) { + errorsOccurred++; + if (critical) { + logger.warning("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e); + } else { + logger.debug("[{}][{}] Failed to process {} msg: {}", id, tenantId, method, e); + } + long ts = System.currentTimeMillis(); + if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) { + systemContext.persistError(tenantId, id, method, e); + lastPersistedErrorTs = ts; + } + } + + protected void logLifecycleEvent(ComponentLifecycleEvent event) { + logLifecycleEvent(event, null); + } + + protected void logLifecycleEvent(ComponentLifecycleEvent event, Exception e) { + systemContext.persistLifecycleEvent(tenantId, id, event, e); + } + + protected abstract long getErrorPersistFrequency(); +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java new file mode 100644 index 00000000000..b2a0de7c590 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.actors.service; + +import akka.actor.UntypedActor; +import org.thingsboard.server.actors.ActorSystemContext; + +public abstract class ContextAwareActor extends UntypedActor { + + public static final int ENTITY_PACK_LIMIT = 1024; + + protected final ActorSystemContext systemContext; + + public ContextAwareActor(ActorSystemContext systemContext) { + super(); + this.systemContext = systemContext; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java b/application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java new file mode 100644 index 00000000000..7222110c563 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 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.actors.service; + +import org.thingsboard.server.actors.ActorSystemContext; + +import akka.japi.Creator; + +public abstract class ContextBasedCreator implements Creator { + + private static final long serialVersionUID = 1L; + + protected final ActorSystemContext context; + + public ContextBasedCreator(ActorSystemContext context) { + super(); + this.context = context; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java new file mode 100644 index 00000000000..db6526d24ea --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -0,0 +1,234 @@ +/** + * Copyright © 2016 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.actors.service; + +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.actor.Terminated; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.app.AppActor; +import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; +import org.thingsboard.server.actors.rpc.RpcManagerActor; +import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; +import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; +import org.thingsboard.server.actors.session.SessionManagerActor; +import org.thingsboard.server.actors.stats.StatsActor; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.cluster.discovery.ServerInstance; +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; +import scala.concurrent.Await; +import scala.concurrent.Future; +import scala.concurrent.duration.Duration; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@Service +@Slf4j +public class DefaultActorService implements ActorService { + + private static final String ACTOR_SYSTEM_NAME = "Akka"; + + public static final String APP_DISPATCHER_NAME = "app-dispatcher"; + public static final String CORE_DISPATCHER_NAME = "core-dispatcher"; + public static final String RULE_DISPATCHER_NAME = "rule-dispatcher"; + public static final String PLUGIN_DISPATCHER_NAME = "plugin-dispatcher"; + public static final String SESSION_DISPATCHER_NAME = "session-dispatcher"; + public static final String RPC_DISPATCHER_NAME = "rpc-dispatcher"; + + @Autowired + private ActorSystemContext actorContext; + + @Autowired + private ClusterRpcService rpcService; + + @Autowired + private DiscoveryService discoveryService; + + private ActorSystem system; + + private ActorRef appActor; + + private ActorRef sessionManagerActor; + + private ActorRef rpcManagerActor; + + @PostConstruct + public void initActorSystem() { + log.info("Initializing Actor system. {}", actorContext.getRuleService()); + actorContext.setActorService(this); + system = ActorSystem.create(ACTOR_SYSTEM_NAME, actorContext.getConfig()); + actorContext.setActorSystem(system); + + appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor"); + actorContext.setAppActor(appActor); + + sessionManagerActor = system.actorOf(Props.create(new SessionManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), + "sessionManagerActor"); + actorContext.setSessionManagerActor(sessionManagerActor); + + rpcManagerActor = system.actorOf(Props.create(new RpcManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), + "rpcManagerActor"); + + ActorRef statsActor = system.actorOf(Props.create(new StatsActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), "statsActor"); + actorContext.setStatsActor(statsActor); + + rpcService.init(this); + + discoveryService.addListener(this); + log.info("Actor system initialized."); + } + + @PreDestroy + public void stopActorSystem() { + Future status = system.terminate(); + try { + Terminated terminated = Await.result(status, Duration.Inf()); + log.info("Actor system terminated: {}", terminated); + } catch (Exception e) { + log.error("Failed to terminate actor system.", e); + } + } + + @Override + public void process(SessionAwareMsg msg) { + if (msg instanceof SessionAwareMsg) { + log.debug("Processing session aware msg: {}", msg); + sessionManagerActor.tell(msg, ActorRef.noSender()); + } + } + + @Override + public void process(PluginWebsocketMsg msg) { + log.debug("Processing websocket msg: {}", msg); + appActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void process(PluginRestMsg msg) { + log.debug("Processing rest msg: {}", msg); + appActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(ToPluginActorMsg msg) { + log.trace("Processing plugin rpc msg: {}", msg); + appActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(ToDeviceActorMsg msg) { + log.trace("Processing device rpc msg: {}", msg); + appActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(ToDeviceActorNotificationMsg msg) { + log.trace("Processing notification rpc msg: {}", msg); + appActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(ToDeviceSessionActorMsg msg) { + log.trace("Processing session rpc msg: {}", msg); + sessionManagerActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(ToAllNodesMsg msg) { + log.trace("Processing broadcast rpc msg: {}", msg); + appActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(RpcSessionCreateRequestMsg msg) { + log.trace("Processing session create msg: {}", msg); + rpcManagerActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(RpcSessionTellMsg msg) { + log.trace("Processing session rpc msg: {}", msg); + rpcManagerActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onMsg(RpcBroadcastMsg msg) { + log.trace("Processing broadcast rpc msg: {}", msg); + rpcManagerActor.tell(msg, ActorRef.noSender()); + } + + @Override + public void onServerAdded(ServerInstance server) { + log.trace("Processing onServerAdded msg: {}", server); + broadcast(new ClusterEventMsg(server.getServerAddress(), true)); + } + + @Override + public void onServerUpdated(ServerInstance server) { + + } + + @Override + public void onServerRemoved(ServerInstance server) { + log.trace("Processing onServerRemoved msg: {}", server); + broadcast(new ClusterEventMsg(server.getServerAddress(), false)); + } + + @Override + public void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state) { + log.trace("[{}] Processing onPluginStateChange event: {}", pluginId, state); + broadcast(ComponentLifecycleMsg.forPlugin(tenantId, pluginId, state)); + } + + @Override + public void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state) { + log.trace("[{}] Processing onRuleStateChange event: {}", ruleId, state); + broadcast(ComponentLifecycleMsg.forRule(tenantId, ruleId, state)); + } + + public void broadcast(ToAllNodesMsg msg) { + rpcService.broadcast(msg); + appActor.tell(msg, ActorRef.noSender()); + } + + private void broadcast(ClusterEventMsg msg) { + this.appActor.tell(msg, ActorRef.noSender()); + this.sessionManagerActor.tell(msg, ActorRef.noSender()); + this.rpcManagerActor.tell(msg, ActorRef.noSender()); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/RestMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/service/RestMsgProcessor.java new file mode 100644 index 00000000000..44c60e55345 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/service/RestMsgProcessor.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 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.actors.service; + +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; + +public interface RestMsgProcessor { + + void process(PluginRestMsg msg); + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java new file mode 100644 index 00000000000..f8ebda3ff27 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/service/WebSocketMsgProcessor.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 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.actors.service; + +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; + +public interface WebSocketMsgProcessor { + + void process(PluginWebsocketMsg msg); + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java new file mode 100644 index 00000000000..eb812dffdd1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java @@ -0,0 +1,133 @@ +/** + * Copyright © 2016 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.actors.session; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.shared.SessionTimeoutMsg; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.core.AttributesSubscribeMsg; +import org.thingsboard.server.common.msg.core.ResponseMsg; +import org.thingsboard.server.common.msg.core.RpcSubscribeMsg; +import org.thingsboard.server.common.msg.core.SessionCloseMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.*; + +import akka.actor.ActorContext; +import akka.event.LoggingAdapter; +import org.thingsboard.server.common.msg.session.ex.SessionException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor { + + Map pendingMap = new HashMap<>(); + private Optional currentTargetServer; + private boolean subscribedToAttributeUpdates; + private boolean subscribedToRpcCommands; + + public ASyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { + super(ctx, logger, sessionId); + } + + @Override + protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) { + updateSessionCtx(msg, SessionType.ASYNC); + ToDeviceActorMsg pendingMsg = toDeviceMsg(msg); + FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload(); + switch (fromDeviceMsg.getMsgType()) { + case POST_TELEMETRY_REQUEST: + case POST_ATTRIBUTES_REQUEST: + FromDeviceRequestMsg requestMsg = (FromDeviceRequestMsg) fromDeviceMsg; + if (requestMsg.getRequestId() >= 0) { + logger.debug("[{}] Pending request {} registered", requestMsg.getRequestId(), requestMsg.getMsgType()); + //TODO: handle duplicates. + pendingMap.put(requestMsg.getRequestId(), pendingMsg); + } + break; + case SUBSCRIBE_ATTRIBUTES_REQUEST: + subscribedToAttributeUpdates = true; + break; + case UNSUBSCRIBE_ATTRIBUTES_REQUEST: + subscribedToAttributeUpdates = false; + break; + case SUBSCRIBE_RPC_COMMANDS_REQUEST: + subscribedToRpcCommands = true; + break; + case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: + subscribedToRpcCommands = false; + break; + } + currentTargetServer = forwardToAppActor(ctx, pendingMsg); + } + + @Override + public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { + try { + switch (msg.getMsgType()) { + case STATUS_CODE_RESPONSE: + case GET_ATTRIBUTES_RESPONSE: + ResponseMsg responseMsg = (ResponseMsg) msg; + if (responseMsg.getRequestId() >= 0) { + logger.debug("[{}] Pending request processed: {}", responseMsg.getRequestId(), responseMsg); + pendingMap.remove(responseMsg.getRequestId()); + } + break; + } + sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); + } catch (SessionException e) { + logger.warning("Failed to push session response msg", e); + } + } + + @Override + public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { + // TODO Auto-generated method stub + } + + protected void cleanupSession(ActorContext ctx) { + toDeviceMsg(new SessionCloseMsg()).ifPresent(msg -> forwardToAppActor(ctx, msg)); + } + + @Override + public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { + if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) { + Optional newTargetServer = systemContext.getRoutingService().resolve(getDeviceId()); + if (!newTargetServer.equals(currentTargetServer)) { + currentTargetServer = newTargetServer; + pendingMap.values().stream().forEach(v -> { + forwardToAppActor(context, v, currentTargetServer); + if (currentTargetServer.isPresent()) { + logger.debug("[{}] Forwarded msg to new server: {}", sessionId, currentTargetServer.get()); + } else { + logger.debug("[{}] Forwarded msg to local server.", sessionId); + } + }); + if (subscribedToAttributeUpdates) { + toDeviceMsg(new AttributesSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); + logger.debug("[{}] Forwarded attributes subscription.", sessionId); + } + if (subscribedToRpcCommands) { + toDeviceMsg(new RpcSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); + logger.debug("[{}] Forwarded rpc commands subscription.", sessionId); + } + } + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java new file mode 100644 index 00000000000..57adf8c8bc1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java @@ -0,0 +1,119 @@ +/** + * Copyright © 2016 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.actors.session; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; +import org.thingsboard.server.actors.shared.SessionTimeoutMsg; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.device.BasicToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.*; +import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.event.LoggingAdapter; + +import java.util.Optional; + +abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgProcessor { + + protected final SessionId sessionId; + protected SessionContext sessionCtx; + protected ToDeviceActorMsg toDeviceActorMsgPrototype; + + protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { + super(ctx, logger); + this.sessionId = sessionId; + } + + protected abstract void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg); + + protected abstract void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg); + + protected abstract void processToDeviceMsg(ActorContext context, ToDeviceMsg msg); + + public abstract void processClusterEvent(ActorContext context, ClusterEventMsg msg); + + protected void processSessionCtrlMsg(ActorContext ctx, SessionCtrlMsg msg) { + if (msg instanceof SessionCloseMsg) { + cleanupSession(ctx); + terminateSession(ctx, sessionId); + } + } + + protected void cleanupSession(ActorContext ctx) { + } + + protected void updateSessionCtx(ToDeviceActorSessionMsg msg, SessionType type) { + sessionCtx = msg.getSessionMsg().getSessionContext(); + toDeviceActorMsgPrototype = new BasicToDeviceActorMsg(msg, type); + } + + protected ToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) { + AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg(); + return new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, adaptorMsg.getMsg()); + } + + protected Optional toDeviceMsg(FromDeviceMsg msg) { + if (toDeviceActorMsgPrototype != null) { + return Optional.of(new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, msg)); + } else { + return Optional.empty(); + } + } + + protected Optional forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) { + Optional address = systemContext.getRoutingService().resolve(toForward.getDeviceId()); + forwardToAppActor(ctx, toForward, address); + return address; + } + + protected Optional forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional oldAddress) { + Optional newAddress = systemContext.getRoutingService().resolve(toForward.getDeviceId()); + if (!newAddress.equals(oldAddress)) { + if (newAddress.isPresent()) { + systemContext.getRpcService().tell(newAddress.get(), + toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer())); + } else { + getAppActor().tell(toForward, ctx.self()); + } + } + return newAddress; + } + + protected void forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward, Optional address) { + if (address.isPresent()) { + systemContext.getRpcService().tell(address.get(), + toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer())); + } else { + getAppActor().tell(toForward, ctx.self()); + } + } + + public static void terminateSession(ActorContext ctx, SessionId sessionId) { + ctx.parent().tell(new SessionTerminationMsg(sessionId), ActorRef.noSender()); + ctx.stop(ctx.self()); + } + + public DeviceId getDeviceId() { + return toDeviceActorMsgPrototype.getDeviceId(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java new file mode 100644 index 00000000000..30f5d9919bc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java @@ -0,0 +1,139 @@ +/** + * Copyright © 2016 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.actors.session; + +import akka.actor.OneForOneStrategy; +import akka.actor.SupervisorStrategy; +import akka.japi.Function; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.shared.SessionTimeoutMsg; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; +import org.thingsboard.server.common.msg.session.SessionCtrlMsg; +import org.thingsboard.server.common.msg.session.SessionMsg; +import org.thingsboard.server.common.msg.session.SessionType; +import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; + +import akka.event.Logging; +import akka.event.LoggingAdapter; +import scala.concurrent.duration.Duration; + +public class SessionActor extends ContextAwareActor { + + private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); + + private final SessionId sessionId; + private AbstractSessionActorMsgProcessor processor; + + private SessionActor(ActorSystemContext systemContext, SessionId sessionId) { + super(systemContext); + this.sessionId = sessionId; + } + + @Override + public SupervisorStrategy supervisorStrategy() { + return new OneForOneStrategy(-1, Duration.Inf(), + throwable -> { + logger.error(throwable, "Unknown session error"); + if (throwable instanceof Error) { + return OneForOneStrategy.escalate(); + } else { + return OneForOneStrategy.resume(); + } + }); + } + + @Override + public void onReceive(Object msg) throws Exception { + logger.debug("[{}] Processing: {}.", sessionId, msg); + if (msg instanceof ToDeviceActorSessionMsg) { + processDeviceMsg((ToDeviceActorSessionMsg) msg); + } else if (msg instanceof ToDeviceSessionActorMsg) { + processToDeviceMsg((ToDeviceSessionActorMsg) msg); + } else if (msg instanceof SessionTimeoutMsg) { + processTimeoutMsg((SessionTimeoutMsg) msg); + } else if (msg instanceof SessionCtrlMsg) { + processSessionCtrlMsg((SessionCtrlMsg) msg); + } else if (msg instanceof ClusterEventMsg) { + processClusterEvent((ClusterEventMsg) msg); + } else { + logger.warning("[{}] Unknown msg: {}", sessionId, msg); + } + } + + private void processClusterEvent(ClusterEventMsg msg) { + processor.processClusterEvent(context(), msg); + } + + private void processDeviceMsg(ToDeviceActorSessionMsg msg) { + initProcessor(msg); + processor.processToDeviceActorMsg(context(), msg); + } + + private void processToDeviceMsg(ToDeviceSessionActorMsg msg) { + processor.processToDeviceMsg(context(), msg.getMsg()); + } + + private void processTimeoutMsg(SessionTimeoutMsg msg) { + if (processor != null) { + processor.processTimeoutMsg(context(), msg); + } else { + logger.warning("[{}] Can't process timeout msg: {} without processor", sessionId, msg); + } + } + + private void processSessionCtrlMsg(SessionCtrlMsg msg) { + if (processor != null) { + processor.processSessionCtrlMsg(context(), msg); + } else if (msg instanceof SessionCloseMsg) { + AbstractSessionActorMsgProcessor.terminateSession(context(), sessionId); + } else { + logger.warning("[{}] Can't process session ctrl msg: {} without processor", sessionId, msg); + } + } + + private void initProcessor(ToDeviceActorSessionMsg msg) { + if (processor == null) { + SessionMsg sessionMsg = (SessionMsg) msg.getSessionMsg(); + if (sessionMsg.getSessionContext().getSessionType() == SessionType.SYNC) { + processor = new SyncMsgProcessor(systemContext, logger, sessionId); + } else { + processor = new ASyncMsgProcessor(systemContext, logger, sessionId); + } + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final SessionId sessionId; + + public ActorCreator(ActorSystemContext context, SessionId sessionId) { + super(context); + this.sessionId = sessionId; + } + + @Override + public SessionActor create() throws Exception { + return new SessionActor(context, sessionId); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java new file mode 100644 index 00000000000..44eff1680e1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java @@ -0,0 +1,154 @@ +/** + * Copyright © 2016 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.actors.session; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import akka.actor.*; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.actors.shared.SessionTimeoutMsg; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; + +import akka.event.Logging; +import akka.event.LoggingAdapter; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.core.SessionCloseMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.session.SessionCtrlMsg; + +public class SessionManagerActor extends ContextAwareActor { + + private static final int INITIAL_SESSION_MAP_SIZE = 1024; + + private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); + + private final Map sessionActors; + + public SessionManagerActor(ActorSystemContext systemContext) { + super(systemContext); + this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE); + } + + @Override + public void onReceive(Object msg) throws Exception { + if (msg instanceof SessionAwareMsg) { + forwardToSessionActor((SessionAwareMsg) msg); + } else if (msg instanceof SessionTerminationMsg) { + onSessionTermination((SessionTerminationMsg) msg); + } else if (msg instanceof Terminated) { + onTermination((Terminated) msg); + } else if (msg instanceof SessionTimeoutMsg) { + onSessionTimeout((SessionTimeoutMsg) msg); + } else if (msg instanceof SessionCtrlMsg) { + onSessionCtrlMsg((SessionCtrlMsg) msg); + } else if (msg instanceof ClusterEventMsg) { + broadcast(msg); + } + } + + private void broadcast(Object msg) { + sessionActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); + } + + private void onSessionTimeout(SessionTimeoutMsg msg) { + String sessionIdStr = msg.getSessionId().toUidStr(); + ActorRef sessionActor = sessionActors.get(sessionIdStr); + if (sessionActor != null) { + sessionActor.tell(msg, ActorRef.noSender()); + } + } + + private void onSessionCtrlMsg(SessionCtrlMsg msg) { + String sessionIdStr = msg.getSessionId().toUidStr(); + ActorRef sessionActor = sessionActors.get(sessionIdStr); + if (sessionActor != null) { + sessionActor.tell(msg, ActorRef.noSender()); + } + } + + private void onSessionTermination(SessionTerminationMsg msg) { + String sessionIdStr = msg.getId().toUidStr(); + ActorRef sessionActor = sessionActors.remove(sessionIdStr); + if (sessionActor != null) { + log.debug("[{}] Removed session actor.", sessionIdStr); + //TODO: onSubscriptionUpdate device actor about session close; + } else { + log.debug("[{}] Session actor was already removed.", sessionIdStr); + } + } + + private void forwardToSessionActor(SessionAwareMsg msg) { + if (msg instanceof ToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) { + String sessionIdStr = msg.getSessionId().toUidStr(); + ActorRef sessionActor = sessionActors.get(sessionIdStr); + if (sessionActor != null) { + sessionActor.tell(msg, ActorRef.noSender()); + } else { + log.debug("[{}] Session actor was already removed.", sessionIdStr); + } + } else { + try { + getOrCreateSessionActor(msg.getSessionId()).tell(msg, self()); + } catch (InvalidActorNameException e) { + log.info("Invalid msg : {}", msg); + } + } + } + + private ActorRef getOrCreateSessionActor(SessionId sessionId) { + String sessionIdStr = sessionId.toUidStr(); + ActorRef sessionActor = sessionActors.get(sessionIdStr); + if (sessionActor == null) { + log.debug("[{}] Creating session actor.", sessionIdStr); + sessionActor = context().actorOf( + Props.create(new SessionActor.ActorCreator(systemContext, sessionId)).withDispatcher(DefaultActorService.SESSION_DISPATCHER_NAME), + sessionIdStr); + sessionActors.put(sessionIdStr, sessionActor); + log.debug("[{}] Created session actor.", sessionIdStr); + } + return sessionActor; + } + + private void onTermination(Terminated message) { + ActorRef terminated = message.actor(); + if (terminated instanceof LocalActorRef) { + log.info("Removed actor: {}.", terminated); + //TODO: cleanup session actors map + } else { + throw new IllegalStateException("Remote actors are not supported!"); + } + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + public ActorCreator(ActorSystemContext context) { + super(context); + } + + @Override + public SessionManagerActor create() throws Exception { + return new SessionManagerActor(context); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java new file mode 100644 index 00000000000..365726ba01e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 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.actors.session; + +import org.thingsboard.server.actors.shared.ActorTerminationMsg; +import org.thingsboard.server.common.data.id.SessionId; + +public class SessionTerminationMsg extends ActorTerminationMsg { + + public SessionTerminationMsg(SessionId id) { + super(id); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java new file mode 100644 index 00000000000..afb35ac76d1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016 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.actors.session; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.shared.SessionTimeoutMsg; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.*; +import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; +import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; +import org.thingsboard.server.common.msg.session.ex.SessionException; + +import akka.actor.ActorContext; +import akka.event.LoggingAdapter; + +import java.util.Optional; + +class SyncMsgProcessor extends AbstractSessionActorMsgProcessor { + private ToDeviceActorMsg pendingMsg; + private Optional currentTargetServer; + private boolean pendingResponse; + + public SyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { + super(ctx, logger, sessionId); + } + + @Override + protected void processToDeviceActorMsg(ActorContext ctx, ToDeviceActorSessionMsg msg) { + updateSessionCtx(msg, SessionType.SYNC); + pendingMsg = toDeviceMsg(msg); + pendingResponse = true; + currentTargetServer = forwardToAppActor(ctx, pendingMsg); + scheduleMsgWithDelay(ctx, new SessionTimeoutMsg(sessionId), getTimeout(systemContext, msg.getSessionMsg().getSessionContext()), ctx.parent()); + } + + public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { + if (pendingResponse) { + try { + sessionCtx.onMsg(new SessionCloseMsg(sessionId, true)); + } catch (SessionException e) { + logger.warning("Failed to push session close msg", e); + } + terminateSession(context, this.sessionId); + } + } + + public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { + try { + sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); + pendingResponse = false; + } catch (SessionException e) { + logger.warning("Failed to push session response msg", e); + } + terminateSession(context, this.sessionId); + } + + @Override + public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { + if (pendingResponse) { + Optional newTargetServer = forwardToAppActorIfAdressChanged(context, pendingMsg, currentTargetServer); + if (logger.isDebugEnabled()) { + if (!newTargetServer.equals(currentTargetServer)) { + if (newTargetServer.isPresent()) { + logger.debug("[{}] Forwarded msg to new server: {}", sessionId, newTargetServer.get()); + } else { + logger.debug("[{}] Forwarded msg to local server.", sessionId); + } + } + } + currentTargetServer = newTargetServer; + } + } + + private long getTimeout(ActorSystemContext ctx, SessionContext sessionCtx) { + return sessionCtx.getTimeout() > 0 ? sessionCtx.getTimeout() : ctx.getSyncSessionTimeout(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java new file mode 100644 index 00000000000..1c7f687cdc0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java @@ -0,0 +1,135 @@ +/** + * Copyright © 2016 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.actors.shared; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.actor.Scheduler; +import akka.event.LoggingAdapter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.plugin.ComponentDescriptor; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.extensions.api.component.*; +import scala.concurrent.ExecutionContextExecutor; +import scala.concurrent.duration.Duration; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public abstract class AbstractContextAwareMsgProcessor { + + protected final ActorSystemContext systemContext; + protected final LoggingAdapter logger; + protected final ObjectMapper mapper = new ObjectMapper(); + + protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger) { + super(); + this.systemContext = systemContext; + this.logger = logger; + } + + protected ActorRef getAppActor() { + return systemContext.getAppActor(); + } + + protected Scheduler getScheduler() { + return systemContext.getScheduler(); + } + + protected ExecutionContextExecutor getSystemDispatcher() { + return systemContext.getActorSystem().dispatcher(); + } + + protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs) { + schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs, ctx.self()); + } + + protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs, ActorRef target) { + logger.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs); + getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); + } + + + protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs) { + scheduleMsgWithDelay(ctx, msg, delayInMs, ctx.self()); + } + + protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, ActorRef target) { + logger.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); + getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); + } + + protected T initComponent(JsonNode componentNode) throws Exception { + ComponentConfiguration configuration = new ComponentConfiguration( + componentNode.get("clazz").asText(), + componentNode.get("name").asText(), + mapper.writeValueAsString(componentNode.get("configuration")) + ); + logger.info("Initializing [{}][{}] component", configuration.getName(), configuration.getClazz()); + ComponentDescriptor componentDescriptor = systemContext.getComponentService().getComponent(configuration.getClazz()) + .orElseThrow(() -> new InstantiationException("Component Not found!")); + return initComponent(componentDescriptor, configuration); + } + + protected T initComponent(ComponentDescriptor componentDefinition, ComponentConfiguration configuration) + throws Exception { + return initComponent(componentDefinition.getClazz(), componentDefinition.getType(), configuration.getConfiguration()); + } + + protected T initComponent(String clazz, ComponentType type, String configuration) + throws Exception { + Class componentClazz = Class.forName(clazz); + T component = (T) (componentClazz.newInstance()); + Class configurationClazz; + switch (type) { + case FILTER: + configurationClazz = ((Filter) componentClazz.getAnnotation(Filter.class)).configuration(); + break; + case PROCESSOR: + configurationClazz = ((Processor) componentClazz.getAnnotation(Processor.class)).configuration(); + break; + case ACTION: + configurationClazz = ((Action) componentClazz.getAnnotation(Action.class)).configuration(); + break; + case PLUGIN: + configurationClazz = ((Plugin) componentClazz.getAnnotation(Plugin.class)).configuration(); + break; + default: + throw new IllegalStateException("Component with type: " + type + " is not supported!"); + } + component.init(decode(configuration, configurationClazz)); + return component; + } + + public C decode(String configuration, Class configurationClazz) throws IOException, RuntimeException { + logger.info("Initializing using configuration: {}", configuration); + return mapper.readValue(configuration, configurationClazz); + } + + @Data + @AllArgsConstructor + private static class ComponentConfiguration { + private final String clazz; + private final String name; + private final String configuration; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ActorTerminationMsg.java b/application/src/main/java/org/thingsboard/server/actors/shared/ActorTerminationMsg.java new file mode 100644 index 00000000000..ed94e2caf60 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/ActorTerminationMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.actors.shared; + +public abstract class ActorTerminationMsg { + + private final T id; + + public ActorTerminationMsg(T id) { + super(); + this.id = id; + } + + public T getId() { + return id; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java new file mode 100644 index 00000000000..2afd619b9fd --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016 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.actors.shared; + +import akka.actor.ActorContext; +import akka.event.LoggingAdapter; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.stats.StatsPersistTick; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; + +public abstract class ComponentMsgProcessor extends AbstractContextAwareMsgProcessor { + + protected final TenantId tenantId; + protected final T entityId; + + protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { + super(systemContext, logger); + this.tenantId = tenantId; + this.entityId = id; + } + + public abstract void start() throws Exception; + + public abstract void stop() throws Exception; + + public abstract void onCreated(ActorContext context) throws Exception; + + public abstract void onUpdate(ActorContext context) throws Exception; + + public abstract void onActivate(ActorContext context) throws Exception; + + public abstract void onSuspend(ActorContext context) throws Exception; + + public abstract void onStop(ActorContext context) throws Exception; + + public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception; + + public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) { + schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java b/application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java new file mode 100644 index 00000000000..2305a35d143 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.actors.shared; + +import lombok.Data; +import org.thingsboard.server.common.data.id.SessionId; + +import java.io.Serializable; + +@Data +public class SessionTimeoutMsg implements Serializable { + + private static final long serialVersionUID = 1L; + + private final SessionId sessionId; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java new file mode 100644 index 00000000000..c581c411233 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java @@ -0,0 +1,83 @@ +/** + * Copyright © 2016 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.actors.shared.plugin; + +import java.util.HashMap; +import java.util.Map; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.plugin.PluginActor; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.dao.plugin.PluginService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.actor.Props; + +@Slf4j +public abstract class PluginManager { + + protected final ActorSystemContext systemContext; + protected final PluginService pluginService; + protected final Map pluginActors; + + public PluginManager(ActorSystemContext systemContext) { + this.systemContext = systemContext; + this.pluginService = systemContext.getPluginService(); + this.pluginActors = new HashMap<>(); + } + + public void init(ActorContext context) { + PageDataIterable pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(), + ContextAwareActor.ENTITY_PACK_LIMIT); + for (PluginMetaData plugin : pluginIterator) { + log.debug("[{}] Creating plugin actor", plugin.getId()); + getOrCreatePluginActor(context, plugin.getId()); + log.debug("Plugin actor created."); + } + } + + abstract FetchFunction getFetchPluginsFunction(); + + abstract TenantId getTenantId(); + + public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) { + ActorRef pluginActor = pluginActors.get(pluginId); + if (pluginActor == null) { + pluginActor = context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pluginId)) + .withDispatcher(DefaultActorService.PLUGIN_DISPATCHER_NAME), pluginId.toString()); + pluginActors.put(pluginId, pluginActor); + } + return pluginActor; + } + + public void broadcast(Object msg) { + pluginActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); + } + + public void remove(PluginId id) { + pluginActors.remove(id); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java new file mode 100644 index 00000000000..d8b58a0f98c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 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.actors.shared.plugin; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.dao.plugin.BasePluginService; +import org.thingsboard.server.dao.plugin.PluginService; + +public class SystemPluginManager extends PluginManager { + + public SystemPluginManager(ActorSystemContext systemContext) { + super(systemContext); + } + + @Override + FetchFunction getFetchPluginsFunction() { + return link -> pluginService.findSystemPlugins(link); + } + + @Override + TenantId getTenantId() { + return BasePluginService.SYSTEM_TENANT; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java new file mode 100644 index 00000000000..dbff415adeb --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 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.actors.shared.plugin; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.plugin.PluginMetaData; + +public class TenantPluginManager extends PluginManager { + + private final TenantId tenantId; + + public TenantPluginManager(ActorSystemContext systemContext, TenantId tenantId) { + super(systemContext); + this.tenantId = tenantId; + } + + @Override + FetchFunction getFetchPluginsFunction() { + return link -> pluginService.findTenantPlugins(tenantId, link); + } + + @Override + TenantId getTenantId() { + return tenantId; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java new file mode 100644 index 00000000000..67d44e9cf61 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java @@ -0,0 +1,126 @@ +/** + * Copyright © 2016 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.actors.shared.rule; + +import akka.actor.ActorContext; +import akka.actor.ActorRef; +import akka.actor.Props; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.rule.RuleActor; +import org.thingsboard.server.actors.rule.RuleActorChain; +import org.thingsboard.server.actors.rule.RuleActorMetaData; +import org.thingsboard.server.actors.rule.SimpleRuleActorChain; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.data.rule.RuleMetaData; +import org.thingsboard.server.dao.rule.RuleService; + +import java.util.*; + +public abstract class RuleManager { + + protected static final Logger logger = LoggerFactory.getLogger(RuleManager.class); + + protected final ActorSystemContext systemContext; + protected final RuleService ruleService; + protected final Map ruleActors; + protected final TenantId tenantId; + + Map ruleMap = new HashMap<>(); + private RuleActorChain ruleChain; + + public RuleManager(ActorSystemContext systemContext, TenantId tenantId) { + this.systemContext = systemContext; + this.ruleService = systemContext.getRuleService(); + this.ruleActors = new HashMap<>(); + this.tenantId = tenantId; + } + + public void init(ActorContext context) { + PageDataIterable ruleIterator = new PageDataIterable<>(getFetchRulesFunction(), + ContextAwareActor.ENTITY_PACK_LIMIT); + ruleMap = new HashMap<>(); + + for (RuleMetaData rule : ruleIterator) { + logger.debug("[{}] Creating rule actor {}", rule.getId(), rule); + ActorRef ref = getOrCreateRuleActor(context, rule.getId()); + RuleActorMetaData actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref); + ruleMap.put(rule, actorMd); + logger.debug("[{}] Rule actor created.", rule.getId()); + } + + refreshRuleChain(); + } + + public Optional update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) { + RuleMetaData rule = null; + if (event != ComponentLifecycleEvent.DELETED) { + rule = systemContext.getRuleService().findRuleById(ruleId); + } + if (rule == null) { + rule = ruleMap.keySet().stream().filter(r -> r.getId().equals(ruleId)).findFirst().orElse(null); + rule.setState(ComponentLifecycleState.SUSPENDED); + } + if (rule != null) { + RuleActorMetaData actorMd = ruleMap.get(rule); + if (actorMd == null) { + ActorRef ref = getOrCreateRuleActor(context, rule.getId()); + actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref); + ruleMap.put(rule, actorMd); + } + refreshRuleChain(); + return Optional.of(actorMd.getActorRef()); + } else { + logger.warn("[{}] Can't process unknown rule!", rule.getId()); + return Optional.empty(); + } + } + + abstract FetchFunction getFetchRulesFunction(); + + public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) { + ActorRef ruleActor = ruleActors.get(ruleId); + if (ruleActor == null) { + ruleActor = context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, ruleId)) + .withDispatcher(DefaultActorService.RULE_DISPATCHER_NAME), ruleId.toString()); + ruleActors.put(ruleId, ruleActor); + } + return ruleActor; + } + + public RuleActorChain getRuleChain() { + return ruleChain; + } + + + private void refreshRuleChain() { + Set activeRuleSet = new HashSet<>(); + for (Map.Entry rule : ruleMap.entrySet()) { + if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) { + activeRuleSet.add(rule.getValue()); + } + } + ruleChain = new SimpleRuleActorChain(activeRuleSet); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java new file mode 100644 index 00000000000..6d56832b16e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016 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.actors.shared.rule; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.rule.RuleMetaData; +import org.thingsboard.server.dao.model.ModelConstants; + +public class SystemRuleManager extends RuleManager { + + public SystemRuleManager(ActorSystemContext systemContext) { + super(systemContext, new TenantId(ModelConstants.NULL_UUID)); + } + + @Override + FetchFunction getFetchRulesFunction() { + return link -> ruleService.findSystemRules(link); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java new file mode 100644 index 00000000000..700614d8bb8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016 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.actors.shared.rule; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; +import org.thingsboard.server.common.data.rule.RuleMetaData; + +public class TenantRuleManager extends RuleManager { + + public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) { + super(systemContext, tenantId); + } + + @Override + FetchFunction getFetchRulesFunction() { + return link -> ruleService.findTenantRules(tenantId, link); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java new file mode 100644 index 00000000000..8b59f70eeb2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016 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.actors.stats; + +import akka.event.Logging; +import akka.event.LoggingAdapter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.msg.cluster.ServerAddress; + +public class StatsActor extends ContextAwareActor { + + private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); + private final ObjectMapper mapper = new ObjectMapper(); + + public StatsActor(ActorSystemContext context) { + super(context); + } + + @Override + public void onReceive(Object msg) throws Exception { + logger.debug("Received message: {}", msg); + if (msg instanceof StatsPersistMsg) { + try { + onStatsPersistMsg((StatsPersistMsg) msg); + } catch (Exception e) { + logger.warning("Failed to persist statistics: {}", msg, e); + } + } + } + + public void onStatsPersistMsg(StatsPersistMsg msg) throws Exception { + Event event = new Event(); + event.setEntityId(msg.getEntityId()); + event.setTenantId(msg.getTenantId()); + event.setType(DataConstants.STATS); + event.setBody(toBodyJson(systemContext.getDiscoveryService().getCurrentServer().getServerAddress(), msg.getMessagesProcessed(), msg.getErrorsOccurred())); + systemContext.getEventService().save(event); + } + + private JsonNode toBodyJson(ServerAddress server, long messagesProcessed, long errorsOccurred) { + return mapper.createObjectNode().put("server", server.toString()).put("messagesProcessed", messagesProcessed).put("errorsOccurred", errorsOccurred); + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + public ActorCreator(ActorSystemContext context) { + super(context); + } + + @Override + public StatsActor create() throws Exception { + return new StatsActor(context); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java new file mode 100644 index 00000000000..437ef96fdcc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 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.actors.stats; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; + +@AllArgsConstructor +@Getter +@ToString +public final class StatsPersistMsg { + private long messagesProcessed; + private long errorsOccurred; + private TenantId tenantId; + private EntityId entityId; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java new file mode 100644 index 00000000000..61c9fc6d760 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java @@ -0,0 +1,18 @@ +/** + * Copyright © 2016 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.actors.stats; + +public final class StatsPersistTick {} diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java b/application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java new file mode 100644 index 00000000000..7d41c7bcdf8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016 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.actors.tenant; + +import org.thingsboard.server.actors.rule.RuleActorChain; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; + +public class RuleChainDeviceMsg { + + private final ToDeviceActorMsg toDeviceActorMsg; + private final RuleActorChain ruleChain; + + public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) { + super(); + this.toDeviceActorMsg = toDeviceActorMsg; + this.ruleChain = ruleChain; + } + + public ToDeviceActorMsg getToDeviceActorMsg() { + return toDeviceActorMsg; + } + + public RuleActorChain getRuleChain() { + return ruleChain; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java new file mode 100644 index 00000000000..965c652676c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -0,0 +1,184 @@ +/** + * Copyright © 2016 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.actors.tenant; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.device.DeviceActor; +import org.thingsboard.server.actors.plugin.PluginTerminationMsg; +import org.thingsboard.server.actors.rule.ComplexRuleActorChain; +import org.thingsboard.server.actors.rule.RuleActorChain; +import org.thingsboard.server.actors.service.ContextAwareActor; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.actors.shared.plugin.PluginManager; +import org.thingsboard.server.actors.shared.plugin.TenantPluginManager; +import org.thingsboard.server.actors.shared.rule.RuleManager; +import org.thingsboard.server.actors.shared.rule.TenantRuleManager; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; + +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; +import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg; + +public class TenantActor extends ContextAwareActor { + + private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); + + private final TenantId tenantId; + private final RuleManager ruleManager; + private final PluginManager pluginManager; + private final Map deviceActors; + + private TenantActor(ActorSystemContext systemContext, TenantId tenantId) { + super(systemContext); + this.tenantId = tenantId; + this.ruleManager = new TenantRuleManager(systemContext, tenantId); + this.pluginManager = new TenantPluginManager(systemContext, tenantId); + this.deviceActors = new HashMap<>(); + } + + @Override + public void preStart() { + logger.info("[{}] Starting tenant actor.", tenantId); + try { + ruleManager.init(this.context()); + pluginManager.init(this.context()); + logger.info("[{}] Tenant actor started.", tenantId); + } catch (Exception e) { + logger.error(e, "[{}] Unknown failure", tenantId); + } + } + + @Override + public void onReceive(Object msg) throws Exception { + logger.debug("[{}] Received message: {}", tenantId, msg); + if (msg instanceof RuleChainDeviceMsg) { + process((RuleChainDeviceMsg) msg); + } else if (msg instanceof ToDeviceActorMsg) { + onToDeviceActorMsg((ToDeviceActorMsg) msg); + } else if (msg instanceof ToPluginActorMsg) { + onToPluginMsg((ToPluginActorMsg) msg); + } else if (msg instanceof ToRuleActorMsg) { + onToRuleMsg((ToRuleActorMsg) msg); + } else if (msg instanceof ToDeviceActorNotificationMsg) { + onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); + } else if (msg instanceof ClusterEventMsg) { + broadcast(msg); + } else if (msg instanceof ComponentLifecycleMsg) { + onComponentLifecycleMsg((ComponentLifecycleMsg) msg); + } else if (msg instanceof PluginTerminationMsg) { + onPluginTerminated((PluginTerminationMsg) msg); + } else { + logger.warning("[{}] Unknown message: {}!", tenantId, msg); + } + } + + private void broadcast(Object msg) { + pluginManager.broadcast(msg); + deviceActors.values().stream().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); + } + + private void onToDeviceActorMsg(ToDeviceActorMsg msg) { + getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); + } + + private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) { + getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); + } + + private void onToRuleMsg(ToRuleActorMsg msg) { + ActorRef target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId()); + target.tell(msg, ActorRef.noSender()); + } + + private void onToPluginMsg(ToPluginActorMsg msg) { + if (msg.getPluginTenantId().equals(tenantId)) { + ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId()); + pluginActor.tell(msg, ActorRef.noSender()); + } else { + context().parent().tell(msg, ActorRef.noSender()); + } + } + + private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { + if (msg.getPluginId().isPresent()) { + ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId().get()); + pluginActor.tell(msg, ActorRef.noSender()); + } else if (msg.getRuleId().isPresent()) { + ActorRef target; + Optional ref = ruleManager.update(this.context(), msg.getRuleId().get(), msg.getEvent()); + if (ref.isPresent()) { + target = ref.get(); + } else { + logger.debug("Failed to find actor for rule: [{}]", msg.getRuleId()); + return; + } + target.tell(msg, ActorRef.noSender()); + } else { + logger.debug("[{}] Invalid component lifecycle msg.", tenantId); + } + } + + private void onPluginTerminated(PluginTerminationMsg msg) { + pluginManager.remove(msg.getId()); + } + + private void process(RuleChainDeviceMsg msg) { + ToDeviceActorMsg toDeviceActorMsg = msg.getToDeviceActorMsg(); + ActorRef deviceActor = getOrCreateDeviceActor(toDeviceActorMsg.getDeviceId()); + RuleActorChain chain = new ComplexRuleActorChain(msg.getRuleChain(), ruleManager.getRuleChain()); + deviceActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, chain), context().self()); + } + + private ActorRef getOrCreateDeviceActor(DeviceId deviceId) { + ActorRef deviceActor = deviceActors.get(deviceId); + if (deviceActor == null) { + deviceActor = context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId)) + .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString()); + deviceActors.put(deviceId, deviceActor); + } + return deviceActor; + } + + public static class ActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final TenantId tenantId; + + public ActorCreator(ActorSystemContext context, TenantId tenantId) { + super(context); + this.tenantId = tenantId; + } + + @Override + public TenantActor create() throws Exception { + return new TenantActor(context, tenantId); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java new file mode 100644 index 00000000000..5100bf8596f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016 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.cluster.discovery; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.thingsboard.server.gen.discovery.ServerInstanceProtos; + +import javax.annotation.PostConstruct; + +import static org.thingsboard.server.utils.MiscUtils.missingProperty; + +/** + * @author Andrew Shvayka + */ +@Service +@Slf4j +public class CurrentServerInstanceService implements ServerInstanceService { + + @Value("${rpc.bind_host}") + private String rpcHost; + @Value("${rpc.bind_port}") + private Integer rpcPort; + + private ServerInstance self; + + @PostConstruct + public void init() { + Assert.hasLength(rpcHost, missingProperty("rpc.bind_host")); + Assert.notNull(rpcPort, missingProperty("rpc.bind_port")); + + self = new ServerInstance(ServerInstanceProtos.ServerInfo.newBuilder().setHost(rpcHost).setPort(rpcPort).setTs(System.currentTimeMillis()).build()); + log.info("Current server instance: [{};{}]", self.getHost(), self.getPort()); + } + + @Override + public ServerInstance getSelf() { + return self; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java new file mode 100644 index 00000000000..bd6fe7b6cd2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016 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.cluster.discovery; + +import java.util.List; + +/** + * @author Andrew Shvayka + */ +public interface DiscoveryService { + + void publishCurrentServer(); + + void unpublishCurrentServer(); + + ServerInstance getCurrentServer(); + + List getOtherServers(); + + boolean addListener(DiscoveryServiceListener listener); + + boolean removeListener(DiscoveryServiceListener listener); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java new file mode 100644 index 00000000000..a0b8ebab424 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.cluster.discovery; + +/** + * @author Andrew Shvayka + */ +public interface DiscoveryServiceListener { + + void onServerAdded(ServerInstance server); + + void onServerUpdated(ServerInstance server); + + void onServerRemoved(ServerInstance server); +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java new file mode 100644 index 00000000000..fdf9fddb19f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016 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.cluster.discovery; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Service; +import org.thingsboard.server.service.environment.EnvironmentLogService; + +import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.List; + +/** + * @author Andrew Shvayka + */ +@Service +@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "false", matchIfMissing = true) +@Slf4j +@DependsOn("environmentLogService") +public class DummyDiscoveryService implements DiscoveryService { + + @Autowired + private ServerInstanceService serverInstance; + + @PostConstruct + public void init() { + log.info("Initializing..."); + } + + @Override + public void publishCurrentServer() { + + } + + @Override + public void unpublishCurrentServer() { + + } + + @Override + public ServerInstance getCurrentServer() { + return serverInstance.getSelf(); + } + + @Override + public List getOtherServers() { + return Collections.emptyList(); + } + + @Override + public boolean addListener(DiscoveryServiceListener listener) { + return false; + } + + @Override + public boolean removeListener(DiscoveryServiceListener listener) { + return false; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java new file mode 100644 index 00000000000..5f9d31ba0f6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016 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.cluster.discovery; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.gen.discovery.ServerInstanceProtos.ServerInfo; + +/** + * @author Andrew Shvayka + */ +@ToString +@EqualsAndHashCode(exclude = {"serverInfo", "serverAddress"}) +public final class ServerInstance implements Comparable { + + @Getter(AccessLevel.PACKAGE) + private final ServerInfo serverInfo; + @Getter + private final String host; + @Getter + private final int port; + @Getter + private final ServerAddress serverAddress; + + public ServerInstance(ServerInfo serverInfo) { + this.serverInfo = serverInfo; + this.host = serverInfo.getHost(); + this.port = serverInfo.getPort(); + this.serverAddress = new ServerAddress(host, port); + } + + @Override + public int compareTo(ServerInstance o) { + return this.serverAddress.compareTo(o.serverAddress); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java new file mode 100644 index 00000000000..af023ed854b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 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.cluster.discovery; + +/** + * @author Andrew Shvayka + */ +public interface ServerInstanceService { + + ServerInstance getSelf(); +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java new file mode 100644 index 00000000000..29e9b3cce41 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java @@ -0,0 +1,207 @@ +/** + * Copyright © 2016 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.cluster.discovery; + +import com.google.protobuf.InvalidProtocolBufferException; +import lombok.extern.slf4j.Slf4j; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.PathChildrenCache; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; +import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; +import org.apache.curator.retry.RetryForever; +import org.apache.curator.utils.CloseableUtils; +import org.apache.zookeeper.CreateMode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.thingsboard.server.gen.discovery.ServerInstanceProtos.ServerInfo; +import org.thingsboard.server.utils.MiscUtils; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +/** + * @author Andrew Shvayka + */ +@Service +@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false) +@Slf4j +public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener, ApplicationListener { + + @Value("${zk.url}") + private String zkUrl; + @Value("${zk.retry_interval_ms}") + private Integer zkRetryInterval; + @Value("${zk.connection_timeout_ms}") + private Integer zkConnectionTimeout; + @Value("${zk.session_timeout_ms}") + private Integer zkSessionTimeout; + @Value("${zk.zk_dir}") + private String zkDir; + + private String zkNodesDir; + + @Autowired + private ServerInstanceService serverInstance; + + private final List listeners = new CopyOnWriteArrayList<>(); + + private CuratorFramework client; + private PathChildrenCache cache; + private String nodePath; + + + @PostConstruct + public void init() { + log.info("Initializing..."); + Assert.hasLength(zkUrl, MiscUtils.missingProperty("zk.url")); + Assert.notNull(zkRetryInterval, MiscUtils.missingProperty("zk.retry_interval_ms")); + Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); + Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms")); + + log.info("Initializing discovery service using ZK connect string: {}", zkUrl); + + zkNodesDir = zkDir + "/nodes"; + try { + client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval)); + client.start(); + client.blockUntilConnected(); + cache = new PathChildrenCache(client, zkNodesDir, true); + cache.getListenable().addListener(this); + cache.start(); + } catch (Exception e) { + log.error("Failed to connect to ZK: {}", e.getMessage(), e); + CloseableUtils.closeQuietly(client); + throw new RuntimeException(e); + } + } + + @PreDestroy + public void destroy() { + unpublishCurrentServer(); + CloseableUtils.closeQuietly(client); + log.info("Stopped discovery service"); + } + + @Override + public void publishCurrentServer() { + try { + ServerInstance self = this.serverInstance.getSelf(); + log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort()); + nodePath = client.create() + .creatingParentsIfNeeded() + .withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", self.getServerInfo().toByteArray()); + log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath); + } catch (Exception e) { + log.error("Failed to create ZK node", e); + throw new RuntimeException(e); + } + } + + @Override + public void unpublishCurrentServer() { + try { + if (nodePath != null) { + client.delete().forPath(nodePath); + } + } catch (Exception e) { + log.error("Failed to delete ZK node {}", nodePath, e); + throw new RuntimeException(e); + } + } + + @Override + public ServerInstance getCurrentServer() { + return serverInstance.getSelf(); + } + + @Override + public List getOtherServers() { + return cache.getCurrentData().stream() + .filter(cd -> !cd.getPath().equals(nodePath)) + .map(cd -> { + try { + return new ServerInstance(ServerInfo.parseFrom(cd.getData())); + } catch (InvalidProtocolBufferException e) { + log.error("Failed to decode ZK node", e); + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + } + + @Override + public boolean addListener(DiscoveryServiceListener listener) { + return listeners.add(listener); + } + + @Override + public boolean removeListener(DiscoveryServiceListener listener) { + return listeners.remove(listener); + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + publishCurrentServer(); + getOtherServers().stream().forEach( + server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort()) + ); + } + + @Override + public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception { + ChildData data = pathChildrenCacheEvent.getData(); + if (data == null) { + log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent); + return; + } else if (data.getData() == null) { + log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent); + return; + } else if (nodePath != null && nodePath.equals(data.getPath())) { + log.debug("Ignoring event about current server {}", pathChildrenCacheEvent); + return; + } + ServerInstance instance; + try { + instance = new ServerInstance(ServerInfo.parseFrom(data.getData())); + } catch (IOException e) { + log.error("Failed to decode server instance for node {}", data.getPath(), e); + throw e; + } + log.info("Processing [{}] event for [{}:{}]", pathChildrenCacheEvent.getType(), instance.getHost(), instance.getPort()); + switch (pathChildrenCacheEvent.getType()) { + case CHILD_ADDED: + listeners.stream().forEach(listener -> listener.onServerAdded(instance)); + break; + case CHILD_UPDATED: + listeners.stream().forEach(listener -> listener.onServerUpdated(instance)); + break; + case CHILD_REMOVED: + listeners.stream().forEach(listener -> listener.onServerRemoved(instance)); + break; + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java new file mode 100644 index 00000000000..352d89e81a8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016 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.cluster.routing; + +import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.service.cluster.discovery.ServerInstance; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +public interface ClusterRoutingService { + + ServerAddress getCurrentServer(); + + Optional resolve(UUIDBased entityId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java new file mode 100644 index 00000000000..3c9ecf8a9b8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java @@ -0,0 +1,142 @@ +/** + * Copyright © 2016 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.cluster.routing; + +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener; +import org.thingsboard.server.service.cluster.discovery.ServerInstance; +import org.thingsboard.server.utils.MiscUtils; + +import javax.annotation.PostConstruct; +import java.util.Optional; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Cluster service implementation based on consistent hash ring + */ + +@Service +@Slf4j +public class ConsistentClusterRoutingService implements ClusterRoutingService, DiscoveryServiceListener { + + @Autowired + private DiscoveryService discoveryService; + + @Value("${cluster.hash_function_name}") + private String hashFunctionName; + @Value("${cluster.vitrual_nodes_size}") + private Integer virtualNodesSize; + + private ServerInstance currentServer; + + private HashFunction hashFunction; + + private final ConcurrentNavigableMap circle = + new ConcurrentSkipListMap<>(); + + @PostConstruct + public void init() { + log.info("Initializing Cluster routing service!"); + hashFunction = MiscUtils.forName(hashFunctionName); + discoveryService.addListener(this); + this.currentServer = discoveryService.getCurrentServer(); + addNode(discoveryService.getCurrentServer()); + for (ServerInstance instance : discoveryService.getOtherServers()) { + addNode(instance); + } + logCircle(); + log.info("Cluster routing service initialized!"); + } + + @Override + public ServerAddress getCurrentServer() { + return discoveryService.getCurrentServer().getServerAddress(); + } + + @Override + public Optional resolve(UUIDBased entityId) { + Assert.notNull(entityId); + if (circle.isEmpty()) { + return Optional.empty(); + } + Long hash = hashFunction.newHasher().putLong(entityId.getId().getMostSignificantBits()) + .putLong(entityId.getId().getLeastSignificantBits()).hash().asLong(); + if (!circle.containsKey(hash)) { + ConcurrentNavigableMap tailMap = + circle.tailMap(hash); + hash = tailMap.isEmpty() ? + circle.firstKey() : tailMap.firstKey(); + } + ServerInstance result = circle.get(hash); + if (!currentServer.equals(result)) { + return Optional.of(result.getServerAddress()); + } else { + return Optional.empty(); + } + } + + @Override + public void onServerAdded(ServerInstance server) { + log.debug("On server added event: {}", server); + addNode(server); + logCircle(); + } + + @Override + public void onServerUpdated(ServerInstance server) { + log.debug("Ignoring server onUpdate event: {}", server); + } + + @Override + public void onServerRemoved(ServerInstance server) { + log.debug("On server removed event: {}", server); + removeNode(server); + logCircle(); + } + + private void addNode(ServerInstance instance) { + for (int i = 0; i < virtualNodesSize; i++) { + circle.put(hash(instance, i).asLong(), instance); + } + } + + private void removeNode(ServerInstance instance) { + for (int i = 0; i < virtualNodesSize; i++) { + circle.remove(hash(instance, i).asLong()); + } + } + + private HashCode hash(ServerInstance instance, int i) { + return hashFunction.newHasher().putString(instance.getHost(), MiscUtils.UTF8).putInt(instance.getPort()).putInt(i).hash(); + } + + private void logCircle() { + log.trace("Consistent Hash Circle Start"); + circle.entrySet().stream().forEach((e) -> log.debug("{} -> {}", e.getKey(), e.getValue().getServerAddress())); + log.trace("Consistent Hash Circle End"); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java new file mode 100644 index 00000000000..3755c0bacc9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java @@ -0,0 +1,273 @@ +/** + * Copyright © 2016 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.cluster.rpc; + +import com.google.protobuf.ByteString; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.stub.StreamObserver; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.SerializationUtils; +import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; +import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; +import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; +import org.thingsboard.server.actors.service.ActorService; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; +import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.cluster.discovery.ServerInstance; +import org.thingsboard.server.service.cluster.discovery.ServerInstanceService; +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Andrew Shvayka + */ +@Service +@Slf4j +public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceImplBase implements ClusterRpcService { + + @Autowired + private ServerInstanceService instanceService; + + private RpcMsgListener listener; + + private Server server; + + private ServerInstance instance; + + private ConcurrentMap pendingSessionMap = new ConcurrentHashMap<>(); + + public void init(RpcMsgListener listener) { + this.listener = listener; + log.info("Initializing RPC service!"); + instance = instanceService.getSelf(); + server = ServerBuilder.forPort(instance.getPort()).addService(this).build(); + log.info("Going to start RPC server using port: {}", instance.getPort()); + try { + server.start(); + } catch (IOException e) { + log.error("Failed to start RPC server!", e); + throw new RuntimeException("Failed to start RPC server!"); + } + log.info("RPC service initialized!"); + } + + @Override + public void onSessionCreated(UUID msgUid, StreamObserver msg) { + RpcSessionCreationFuture future = pendingSessionMap.remove(msgUid); + if (future != null) { + try { + future.onMsg(msg); + } catch (InterruptedException e) { + log.warn("Failed to report created session!"); + } + } else { + log.warn("Failed to lookup pending session!"); + } + } + + @Override + public StreamObserver handlePluginMsgs(StreamObserver responseObserver) { + log.info("Processing new session."); + return createSession(new RpcSessionCreateRequestMsg(UUID.randomUUID(), null, responseObserver)); + } + + @PreDestroy + public void stop() { + if (server != null) { + log.info("Going to onStop RPC server"); + server.shutdownNow(); + try { + server.awaitTermination(); + log.info("RPC server stopped!"); + } catch (InterruptedException e) { + log.warn("Failed to onStop RPC server!"); + } + } + } + + @Override + public void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward) { + ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() + .setToDeviceActorRpcMsg(toProtoMsg(toForward)).build(); + tell(serverAddress, msg); + } + + @Override + public void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward) { + ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() + .setToDeviceActorNotificationRpcMsg(toProtoMsg(toForward)).build(); + tell(serverAddress, msg); + } + + @Override + public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) { + ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() + .setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build(); + tell(serverAddress, msg); + } + + @Override + public void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward) { + ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() + .setToPluginRpcResponseRpcMsg(toProtoMsg(toForward)).build(); + tell(serverAddress, msg); + } + + @Override + public void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward) { + ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() + .setToDeviceSessionActorRpcMsg(toProtoMsg(toForward)).build(); + tell(serverAddress, msg); + } + + @Override + public void tell(PluginRpcMsg toForward) { + ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() + .setToPluginRpcMsg(toProtoMsg(toForward)).build(); + tell(toForward.getRpcMsg().getServerAddress(), msg); + } + + @Override + public void broadcast(ToAllNodesMsg toForward) { + ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() + .setToAllNodesRpcMsg(toProtoMsg(toForward)).build(); + listener.onMsg(new RpcBroadcastMsg(msg)); + } + + private void tell(ServerAddress serverAddress, ClusterAPIProtos.ToRpcServerMessage msg) { + listener.onMsg(new RpcSessionTellMsg(serverAddress, msg)); + } + + private StreamObserver createSession(RpcSessionCreateRequestMsg msg) { + RpcSessionCreationFuture future = new RpcSessionCreationFuture(); + pendingSessionMap.put(msg.getMsgUid(), future); + listener.onMsg(msg); + try { + StreamObserver observer = future.get(); + log.info("Processed new session."); + return observer; + } catch (Exception e) { + log.info("Failed to process session.", e); + throw new RuntimeException(e); + } + } + + private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(ToDeviceActorMsg msg) { + return ClusterAPIProtos.ToDeviceActorRpcMessage.newBuilder().setData( + ByteString.copyFrom(SerializationUtils.serialize(msg)) + ).build(); + } + + private static ClusterAPIProtos.ToDeviceActorNotificationRpcMessage toProtoMsg(ToDeviceActorNotificationMsg msg) { + return ClusterAPIProtos.ToDeviceActorNotificationRpcMessage.newBuilder().setData( + ByteString.copyFrom(SerializationUtils.serialize(msg)) + ).build(); + } + + private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) { + ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder(); + ToDeviceRpcRequest request = msg.getMsg(); + + builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder() + .setTenantId(toUid(msg.getPluginTenantId().getId())) + .setPluginId(toUid(msg.getPluginId().getId())) + .build()); + + builder.setDeviceTenantId(toUid(msg.getTenantId())); + builder.setDeviceId(toUid(msg.getDeviceId())); + + builder.setMsgId(toUid(request.getId())); + builder.setOneway(request.isOneway()); + builder.setExpTime(request.getExpirationTime()); + builder.setMethod(request.getBody().getMethod()); + builder.setParams(request.getBody().getParams()); + + return builder.build(); + } + + private static ClusterAPIProtos.ToPluginRpcResponseRpcMessage toProtoMsg(ToPluginRpcResponseDeviceMsg msg) { + ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder(); + FromDeviceRpcResponse request = msg.getResponse(); + + builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder() + .setTenantId(toUid(msg.getPluginTenantId().getId())) + .setPluginId(toUid(msg.getPluginId().getId())) + .build()); + + builder.setMsgId(toUid(request.getId())); + request.getResponse().ifPresent(builder::setResponse); + request.getError().ifPresent(e -> builder.setError(e.name())); + + return builder.build(); + } + + private ClusterAPIProtos.ToAllNodesRpcMessage toProtoMsg(ToAllNodesMsg msg) { + return ClusterAPIProtos.ToAllNodesRpcMessage.newBuilder().setData( + ByteString.copyFrom(SerializationUtils.serialize(msg)) + ).build(); + } + + + private ClusterAPIProtos.ToPluginRpcMessage toProtoMsg(PluginRpcMsg msg) { + return ClusterAPIProtos.ToPluginRpcMessage.newBuilder() + .setClazz(msg.getRpcMsg().getMsgClazz()) + .setData(ByteString.copyFrom(msg.getRpcMsg().getMsgData())) + .setAddress(ClusterAPIProtos.PluginAddress.newBuilder() + .setTenantId(toUid(msg.getPluginTenantId().getId())) + .setPluginId(toUid(msg.getPluginId().getId())) + .build() + ).build(); + } + + private static ClusterAPIProtos.Uid toUid(EntityId uuid) { + return toUid(uuid.getId()); + } + + private static ClusterAPIProtos.Uid toUid(UUID uuid) { + return ClusterAPIProtos.Uid.newBuilder().setPluginUuidMsb(uuid.getMostSignificantBits()).setPluginUuidLsb( + uuid.getLeastSignificantBits()).build(); + } + + private static ClusterAPIProtos.ToDeviceSessionActorRpcMessage toProtoMsg(ToDeviceSessionActorMsg msg) { + return ClusterAPIProtos.ToDeviceSessionActorRpcMessage.newBuilder().setData( + ByteString.copyFrom(SerializationUtils.serialize(msg)) + ).build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java new file mode 100644 index 00000000000..0f1316466c3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016 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.cluster.rpc; + +import io.grpc.stub.StreamObserver; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +public interface ClusterRpcService { + + void init(RpcMsgListener listener); + + void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward); + + void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward); + + void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward); + + void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward); + + void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward); + + void tell(PluginRpcMsg toForward); + + void broadcast(ToAllNodesMsg msg); + + void onSessionCreated(UUID msgUid, StreamObserver inputStream); +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java new file mode 100644 index 00000000000..0f5383943bb --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java @@ -0,0 +1,130 @@ +/** + * Copyright © 2016 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.cluster.rpc; + +import io.grpc.stub.StreamObserver; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +import java.io.Closeable; +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Data +@Slf4j +final public class GrpcSession implements Closeable { + private final UUID sessionId; + private final boolean client; + private final GrpcSessionListener listener; + private StreamObserver inputStream; + private StreamObserver outputStream; + + private boolean connected; + private ServerAddress remoteServer; + + public GrpcSession(GrpcSessionListener listener) { + this(null, listener); + } + + public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener) { + this.sessionId = UUID.randomUUID(); + this.listener = listener; + if (remoteServer != null) { + this.client = true; + this.connected = true; + this.remoteServer = remoteServer; + } else { + this.client = false; + } + } + + public void initInputStream() { + this.inputStream = new StreamObserver() { + @Override + public void onNext(ClusterAPIProtos.ToRpcServerMessage msg) { + if (!connected) { + if (msg.hasConnectMsg()) { + connected = true; + ClusterAPIProtos.ServerAddress rpcAddress = msg.getConnectMsg().getServerAddress(); + remoteServer = new ServerAddress(rpcAddress.getHost(), rpcAddress.getPort()); + listener.onConnected(GrpcSession.this); + } + } + if (connected) { + if (msg.hasToPluginRpcMsg()) { + listener.onToPluginRpcMsg(GrpcSession.this, msg.getToPluginRpcMsg()); + } + if (msg.hasToDeviceActorRpcMsg()) { + listener.onToDeviceActorRpcMsg(GrpcSession.this, msg.getToDeviceActorRpcMsg()); + } + if (msg.hasToDeviceSessionActorRpcMsg()) { + listener.onToDeviceSessionActorRpcMsg(GrpcSession.this, msg.getToDeviceSessionActorRpcMsg()); + } + if (msg.hasToDeviceActorNotificationRpcMsg()) { + listener.onToDeviceActorNotificationRpcMsg(GrpcSession.this, msg.getToDeviceActorNotificationRpcMsg()); + } + if (msg.hasToDeviceRpcRequestRpcMsg()) { + listener.onToDeviceRpcRequestRpcMsg(GrpcSession.this, msg.getToDeviceRpcRequestRpcMsg()); + } + if (msg.hasToPluginRpcResponseRpcMsg()) { + listener.onFromDeviceRpcResponseRpcMsg(GrpcSession.this, msg.getToPluginRpcResponseRpcMsg()); + } + if (msg.hasToAllNodesRpcMsg()) { + listener.onToAllNodesRpcMessage(GrpcSession.this, msg.getToAllNodesRpcMsg()); + } + } + } + + @Override + public void onError(Throwable t) { + listener.onError(GrpcSession.this, t); + } + + @Override + public void onCompleted() { + outputStream.onCompleted(); + listener.onDisconnected(GrpcSession.this); + } + }; + } + + public void initOutputStream() { + if (client) { + listener.onConnected(GrpcSession.this); + } + } + + public void sendMsg(ClusterAPIProtos.ToRpcServerMessage msg) { + outputStream.onNext(msg); + } + + public void onError(Throwable t) { + outputStream.onError(t); + } + + @Override + public void close() { + try { + outputStream.onCompleted(); + } catch (IllegalStateException e) { + log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage()); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSessionListener.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSessionListener.java new file mode 100644 index 00000000000..f80b394bd8c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSessionListener.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016 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.cluster.rpc; + +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +/** + * @author Andrew Shvayka + */ +public interface GrpcSessionListener { + + void onConnected(GrpcSession session); + + void onDisconnected(GrpcSession session); + + void onToPluginRpcMsg(GrpcSession session, ClusterAPIProtos.ToPluginRpcMessage msg); + + void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg); + + void onToDeviceActorNotificationRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToDeviceActorNotificationRpcMessage msg); + + void onToDeviceSessionActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceSessionActorRpcMessage msg); + + void onToAllNodesRpcMessage(GrpcSession grpcSession, ClusterAPIProtos.ToAllNodesRpcMessage toAllNodesRpcMessage); + + void onToDeviceRpcRequestRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toDeviceRpcRequestRpcMsg); + + void onFromDeviceRpcResponseRpcMsg(GrpcSession grpcSession, ClusterAPIProtos.ToPluginRpcResponseRpcMessage toPluginRpcResponseRpcMsg); + + void onError(GrpcSession session, Throwable t); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java new file mode 100644 index 00000000000..982803adc74 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016 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.cluster.rpc; + +import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; +import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; +import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; +import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +/** + * @author Andrew Shvayka + */ +public interface RpcMsgListener { + + void onMsg(ToDeviceActorMsg msg); + + void onMsg(ToDeviceActorNotificationMsg msg); + + void onMsg(ToDeviceSessionActorMsg msg); + + void onMsg(ToAllNodesMsg nodeMsg); + + void onMsg(ToPluginActorMsg msg); + + void onMsg(RpcSessionCreateRequestMsg msg); + + void onMsg(RpcSessionTellMsg rpcSessionTellMsg); + + void onMsg(RpcBroadcastMsg rpcBroadcastMsg); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcSessionCreationFuture.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcSessionCreationFuture.java new file mode 100644 index 00000000000..49665533ad7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcSessionCreationFuture.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 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.cluster.rpc; + +import io.grpc.stub.StreamObserver; +import org.thingsboard.server.gen.cluster.ClusterAPIProtos; + +import java.util.concurrent.*; + +/** + * @author Andrew Shvayka + */ +public class RpcSessionCreationFuture implements Future> { + + private final BlockingQueue> queue = new ArrayBlockingQueue<>(1); + + public void onMsg(StreamObserver result) throws InterruptedException { + queue.put(result); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return false; + } + + @Override + public StreamObserver get() throws InterruptedException, ExecutionException { + return this.queue.take(); + } + + @Override + public StreamObserver get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + StreamObserver result = this.queue.poll(timeout, unit); + if (result == null) { + throw new TimeoutException(); + } else { + return result; + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java new file mode 100644 index 00000000000..a51464c4f44 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -0,0 +1,190 @@ +/** + * Copyright © 2016 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.ObjectMapper; +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +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.type.filter.AnnotationTypeFilter; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.plugin.ComponentDescriptor; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.dao.component.ComponentDescriptorService; +import org.thingsboard.server.extensions.api.component.*; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class AnnotationComponentDiscoveryService implements ComponentDiscoveryService { + + @Value("${plugins.scan_packages}") + private String[] scanPackages; + + @Autowired + private ComponentDescriptorService componentDescriptorService; + + private Map components = new HashMap<>(); + + private Map> componentsMap = new HashMap<>(); + + private ObjectMapper mapper = new ObjectMapper(); + + @PostConstruct + public void init() { + registerComponents(ComponentType.FILTER, Filter.class); + + registerComponents(ComponentType.PROCESSOR, Processor.class); + + registerComponents(ComponentType.ACTION, Action.class); + + registerComponents(ComponentType.PLUGIN, Plugin.class); + + log.info("Found following definitions: {}", components.values()); + } + + private void registerComponents(ComponentType type, Class annotation) { + List components = persist(getBeanDefinitions(annotation), type); + componentsMap.put(type, components); + registerComponents(components); + } + + private void registerComponents(Collection comps) { + comps.stream().forEach(c -> components.put(c.getClazz(), c)); + } + + private List persist(Set filterDefs, ComponentType type) { + List result = new ArrayList<>(); + for (BeanDefinition def : filterDefs) { + ComponentDescriptor scannedComponent = new ComponentDescriptor(); + String clazzName = def.getBeanClassName(); + try { + scannedComponent.setType(type); + Class clazz = Class.forName(clazzName); + String descriptorResourceName; + switch (type) { + case FILTER: + Filter filterAnnotation = clazz.getAnnotation(Filter.class); + scannedComponent.setName(filterAnnotation.name()); + scannedComponent.setScope(filterAnnotation.scope()); + descriptorResourceName = filterAnnotation.descriptor(); + break; + case PROCESSOR: + Processor processorAnnotation = clazz.getAnnotation(Processor.class); + scannedComponent.setName(processorAnnotation.name()); + scannedComponent.setScope(processorAnnotation.scope()); + descriptorResourceName = processorAnnotation.descriptor(); + break; + case ACTION: + Action actionAnnotation = clazz.getAnnotation(Action.class); + scannedComponent.setName(actionAnnotation.name()); + scannedComponent.setScope(actionAnnotation.scope()); + descriptorResourceName = actionAnnotation.descriptor(); + break; + case PLUGIN: + Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class); + scannedComponent.setName(pluginAnnotation.name()); + scannedComponent.setScope(pluginAnnotation.scope()); + descriptorResourceName = pluginAnnotation.descriptor(); + for (Class actionClazz : pluginAnnotation.actions()) { + ComponentDescriptor actionComponent = getComponent(actionClazz.getName()) + .orElseThrow(() -> { + log.error("Can't initialize plugin {}, due to missing action {}!", def.getBeanClassName(), actionClazz.getName()); + return new ClassNotFoundException("Action: " + actionClazz.getName() + "is missing!"); + }); + if (actionComponent.getType() != ComponentType.ACTION) { + log.error("Plugin {} action {} has wrong component type!", def.getBeanClassName(), actionClazz.getName(), actionComponent.getType()); + throw new RuntimeException("Plugin " + def.getBeanClassName() + "action " + actionClazz.getName() + " has wrong component type!"); + } + } + scannedComponent.setActions(Arrays.asList(pluginAnnotation.actions()).stream().map(action -> action.getName()).collect(Collectors.joining(","))); + break; + default: + throw new RuntimeException(type + " is not supported yet!"); + } + scannedComponent.setConfigurationDescriptor(mapper.readTree( + Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8))); + scannedComponent.setClazz(clazzName); + log.info("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(clazzName); + if (persistedComponent == null) { + log.info("Persisting new component: {}", scannedComponent); + scannedComponent = componentDescriptorService.saveComponent(scannedComponent); + } else if (scannedComponent.equals(persistedComponent)) { + log.info("Component is already persisted: {}", persistedComponent); + scannedComponent = persistedComponent; + } else { + log.info("Component {} will be updated to {}", persistedComponent, scannedComponent); + componentDescriptorService.deleteByClazz(persistedComponent.getClazz()); + scannedComponent.setId(persistedComponent.getId()); + scannedComponent = componentDescriptorService.saveComponent(scannedComponent); + } + result.add(scannedComponent); + } + return result; + } + + private Set getBeanDefinitions(Class componentType) { + ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new AnnotationTypeFilter(componentType)); + Set defs = new HashSet<>(); + for (String scanPackage : scanPackages) { + defs.addAll(scanner.findCandidateComponents(scanPackage)); + } + return defs; + } + + @Override + public List getComponents(ComponentType type) { + return Collections.unmodifiableList(componentsMap.get(type)); + } + + @Override + public Optional getComponent(String clazz) { + return Optional.ofNullable(components.get(clazz)); + } + + @Override + public List getPluginActions(String pluginClazz) { + Optional pluginOpt = getComponent(pluginClazz); + if (pluginOpt.isPresent()) { + ComponentDescriptor plugin = pluginOpt.get(); + if (ComponentType.PLUGIN != plugin.getType()) { + throw new IllegalArgumentException(pluginClazz + " is not a plugin!"); + } + List result = new ArrayList<>(); + for (String action : plugin.getActions().split(",")) { + getComponent(action).ifPresent(v -> result.add(v)); + } + return result; + } else { + throw new IllegalArgumentException(pluginClazz + " is not a component!"); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java new file mode 100644 index 00000000000..d14a60fa19c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016 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 java.util.List; +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +public interface ComponentDiscoveryService { + + List getComponents(ComponentType type); + + Optional getComponent(String clazz); + + List getPluginActions(String pluginClazz); + +} diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java new file mode 100644 index 00000000000..3af69b4a0dc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016 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.utils; + +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + +import java.nio.charset.Charset; + + +/** + * @author Andrew Shvayka + */ +public class MiscUtils { + + public static final Charset UTF8 = Charset.forName("UTF-8"); + + public static String missingProperty(String propertyName) { + return "The " + propertyName + " property need to be set!"; + } + + public static HashFunction forName(String name) { + switch (name) { + case "murmur3_32": + return Hashing.murmur3_32(); + case "murmur3_128": + return Hashing.murmur3_128(); + case "crc32": + return Hashing.crc32(); + case "md5": + return Hashing.md5(); + default: + throw new IllegalArgumentException("Can't find hash function with name " + name); + } + } +} diff --git a/application/src/main/proto/cluster.proto b/application/src/main/proto/cluster.proto new file mode 100644 index 00000000000..4ac58cf844a --- /dev/null +++ b/application/src/main/proto/cluster.proto @@ -0,0 +1,97 @@ +/** + * Copyright © 2016 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. + */ +syntax = "proto3"; +package cluster; + +option java_package = "org.thingsboard.server.gen.cluster"; +option java_outer_classname = "ClusterAPIProtos"; + +message ServerAddress { + string host = 1; + int32 port = 2; +} + +message Uid { + sint64 pluginUuidMsb = 1; + sint64 pluginUuidLsb = 2; +} + +message PluginAddress { + Uid pluginId = 1; + Uid tenantId = 2; +} + +message ToPluginRpcMessage { + PluginAddress address = 1; + int32 clazz = 2; + bytes data = 3; +} + +message ToDeviceActorRpcMessage { + bytes data = 1; +} + +message ToDeviceSessionActorRpcMessage { + bytes data = 1; +} + +message ToDeviceActorNotificationRpcMessage { + bytes data = 1; +} + +message ToAllNodesRpcMessage { + bytes data = 1; +} + +message ConnectRpcMessage { + ServerAddress serverAddress = 1; +} + +message ToDeviceRpcRequestRpcMessage { + PluginAddress address = 1; + Uid deviceTenantId = 2; + Uid deviceId = 3; + + Uid msgId = 4; + bool oneway = 5; + int64 expTime = 6; + string method = 7; + string params = 8; +} + +message ToPluginRpcResponseRpcMessage { + PluginAddress address = 1; + + Uid msgId = 2; + string response = 3; + string error = 4; +} + +message ToRpcServerMessage { + ConnectRpcMessage connectMsg = 1; + ToPluginRpcMessage toPluginRpcMsg = 2; + ToDeviceActorRpcMessage toDeviceActorRpcMsg = 3; + ToDeviceSessionActorRpcMessage toDeviceSessionActorRpcMsg = 4; + ToDeviceActorNotificationRpcMessage toDeviceActorNotificationRpcMsg = 5; + ToAllNodesRpcMessage toAllNodesRpcMsg = 6; + ToDeviceRpcRequestRpcMessage toDeviceRpcRequestRpcMsg = 7; + ToPluginRpcResponseRpcMessage toPluginRpcResponseRpcMsg = 8; +} + +service ClusterRpcService { + rpc handlePluginMsgs(stream ToRpcServerMessage) returns (stream ToRpcServerMessage) {} +} + diff --git a/application/src/main/proto/discovery.proto b/application/src/main/proto/discovery.proto new file mode 100644 index 00000000000..17db5c16ccc --- /dev/null +++ b/application/src/main/proto/discovery.proto @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 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. + */ +syntax = "proto3"; +package discovery; + +option java_package = "org.thingsboard.server.gen.discovery"; +option java_outer_classname = "ServerInstanceProtos"; + +message ServerInfo { + string host = 1; + int32 port = 2; + int64 ts = 3; +} diff --git a/application/src/main/resources/actor-system.conf b/application/src/main/resources/actor-system.conf new file mode 100644 index 00000000000..7bd80ab95d7 --- /dev/null +++ b/application/src/main/resources/actor-system.conf @@ -0,0 +1,163 @@ +# +# Copyright © 2016 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. +# + + +akka { + # JVM shutdown, System.exit(-1), in case of a fatal error, + # such as OutOfMemoryError + jvm-exit-on-fatal-error = off + loglevel = "INFO" + loggers = ["akka.event.slf4j.Slf4jLogger"] +} + +# This dispatcher is used for app +app-dispatcher { + type = Dispatcher + executor = "fork-join-executor" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 2 + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 12 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 1.0 + } + # How long time the dispatcher will wait for new actors until it shuts down + shutdown-timeout = 1s + + # Throughput defines the number of messages that are processed in a batch + # before the thread is returned to the pool. Set to 1 for as fair as possible. + throughput = 5 +} + +# This dispatcher is used for rpc actors +rpc-dispatcher { + type = Dispatcher + executor = "fork-join-executor" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 2 + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 12 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 0.5 + } + # How long time the dispatcher will wait for new actors until it shuts down + shutdown-timeout = 1s + + # Throughput defines the number of messages that are processed in a batch + # before the thread is returned to the pool. Set to 1 for as fair as possible. + throughput = 5 +} + +# This dispatcher is used for auth +core-dispatcher { + type = Dispatcher + executor = "fork-join-executor" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 2 + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 12 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 1.0 + } + # How long time the dispatcher will wait for new actors until it shuts down + shutdown-timeout = 1s + + # Throughput defines the number of messages that are processed in a batch + # before the thread is returned to the pool. Set to 1 for as fair as possible. + throughput = 5 +} + +# This dispatcher is used for rule actors +rule-dispatcher { + type = Dispatcher + executor = "fork-join-executor" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 2 + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 12 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 1.0 + } + # How long time the dispatcher will wait for new actors until it shuts down + shutdown-timeout = 1s + + # Throughput defines the number of messages that are processed in a batch + # before the thread is returned to the pool. Set to 1 for as fair as possible. + throughput = 5 +} + +# This dispatcher is used for rule actors +plugin-dispatcher { + type = Dispatcher + executor = "fork-join-executor" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 2 + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 12 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 1.0 + } + # How long time the dispatcher will wait for new actors until it shuts down + shutdown-timeout = 1s + + # Throughput defines the number of messages that are processed in a batch + # before the thread is returned to the pool. Set to 1 for as fair as possible. + throughput = 5 +} + + +# This dispatcher is used for rule actors +session-dispatcher { + type = Dispatcher + executor = "fork-join-executor" + fork-join-executor { + # Min number of threads to cap factor-based parallelism number to + parallelism-min = 2 + # Max number of threads to cap factor-based parallelism number to + parallelism-max = 12 + + # The parallelism factor is used to determine thread pool size using the + # following formula: ceil(available processors * factor). Resulting size + # is then bounded by the parallelism-min and parallelism-max values. + parallelism-factor = 1.0 + } + # How long time the dispatcher will wait for new actors until it shuts down + shutdown-timeout = 1s + + # Throughput defines the number of messages that are processed in a batch + # before the thread is returned to the pool. Set to 1 for as fair as possible. + throughput = 5 +} \ No newline at end of file diff --git a/application/src/main/resources/banner.txt b/application/src/main/resources/banner.txt new file mode 100644 index 00000000000..791f8783663 --- /dev/null +++ b/application/src/main/resources/banner.txt @@ -0,0 +1,3 @@ + =================================================== + :: ${application.title} :: ${application.formatted-version} + =================================================== diff --git a/application/src/main/resources/i18n/messages.properties b/application/src/main/resources/i18n/messages.properties new file mode 100644 index 00000000000..a78fbe07b3b --- /dev/null +++ b/application/src/main/resources/i18n/messages.properties @@ -0,0 +1,5 @@ +test.message.subject=Test message from Thingsboard +activation.subject=Your account activation on Thingsboard +account.activated.subject=Thingsboard - your account has been activated +reset.password.subject=Thingsboard - Password reset has been requested +password.was.reset.subject=Thingsboard - your account password has been reset diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml new file mode 100644 index 00000000000..550657894d4 --- /dev/null +++ b/application/src/main/resources/logback.xml @@ -0,0 +1,35 @@ + + + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/resources/templates/account.activated.vm b/application/src/main/resources/templates/account.activated.vm new file mode 100644 index 00000000000..4913acc3f24 --- /dev/null +++ b/application/src/main/resources/templates/account.activated.vm @@ -0,0 +1,122 @@ +#* + * Copyright © 2016 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. + *# + + + + + +Thingsboard - Account Activated + + + + + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + +
+

Your Thingsboard account has been activated

+
+ Congratulations! Your Thingsboard account has been activated. +
+ Now you can login to your Thingsboard space. +
+ +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/activation.vm b/application/src/main/resources/templates/activation.vm new file mode 100644 index 00000000000..7d2beb3edba --- /dev/null +++ b/application/src/main/resources/templates/activation.vm @@ -0,0 +1,122 @@ +#* + * Copyright © 2016 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. + *# + + + + + +Thingsboard - Account Activation + + + + + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + +
+

Activate your Thingsboard account

+
+ To confirm your email address and choose a password, just click the button below. +
+ We may need to send you critical information about our service and it is important that we have an accurate email address. +
+ +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/password.was.reset.vm b/application/src/main/resources/templates/password.was.reset.vm new file mode 100644 index 00000000000..22beff63611 --- /dev/null +++ b/application/src/main/resources/templates/password.was.reset.vm @@ -0,0 +1,122 @@ +#* + * Copyright © 2016 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. + *# + + + + + +Thingsboard - Account Password Has Been Reset + + + + + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + +
+

Your Thingsboard account password has been reset

+
+ You have successfully created new password for your Thingsboard account. +
+ Now you can login to your Thingsboard space using your newly created password. +
+ +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/reset.password.vm b/application/src/main/resources/templates/reset.password.vm new file mode 100644 index 00000000000..18ebcc00f89 --- /dev/null +++ b/application/src/main/resources/templates/reset.password.vm @@ -0,0 +1,122 @@ +#* + * Copyright © 2016 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. + *# + + + + + +Thingsboard - Reset Password Request + + + + + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + +
+

Password reset has been requested

+
+ You have requested password reset for your Thingsboard account. +
+ Click below in order to proceed password reset procedure. +
+ +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/templates/test.vm b/application/src/main/resources/templates/test.vm new file mode 100644 index 00000000000..ecd3d15ce58 --- /dev/null +++ b/application/src/main/resources/templates/test.vm @@ -0,0 +1,112 @@ +#* + * Copyright © 2016 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. + *# + + + + + +Thingsboard - Test Message + + + + + + + + + + + +
+
+ + +
+ + + + + + + + + +
+

Test message from Thingsboard

+
+ This email is indicating that your outgoing mail settings were set up correctly. +
+ — The Thingsboard +
+ +
+
+ + diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml new file mode 100644 index 00000000000..7a8f58fc513 --- /dev/null +++ b/application/src/main/resources/thingsboard.yml @@ -0,0 +1,176 @@ +# +# Copyright © 2016 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. +# + +server: + # Server bind address + address: "${HTTP_BIND_ADDRESS:0.0.0.0}" + # Server bind port + port: "${HTTP_BIND_PORT:8080}" +# Uncomment the following section to enable ssl +# ssl: +# key-store: classpath:keystore/keystore.p12 +# key-store-password: thingsboard +# keyStoreType: PKCS12 +# keyAlias: tomcat + +# Zookeeper connection parameters. Used for service discovery. +zk: + # Enable/disable zookeeper discovery service. + enabled: "${ZOOKEEPER_ENABLED:false}" + # Zookeeper connect string + url: "${ZOOKEEPER_URL:localhost:2181}" + # Zookeeper retry interval in milliseconds + retry_interval_ms: "${ZOOKEEPER_RETRY_INTERVAL_MS:3000}" + # Zookeeper connection timeout in milliseconds + connection_timeout_ms: "${ZOOKEEPER_CONNECTION_TIMEOUT_MS:3000}" + # Zookeeper session timeout in milliseconds + session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" + # Name of the directory in zookeeper 'filesystem' + zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + +# RPC connection parameters. Used only in cluster mode only. +rpc: + bind_host: "${RPC_HOST:localhost}" + bind_port: "${RPC_PORT:9001}" + +# Clustering properties related to consistent-hashing. See architecture docs for more details. +cluster: + # Name of hash function used for consistent hash ring. + hash_function_name: "${CLUSTER_HASH_FUNCTION_NAME:murmur3_128}" + # Amount of virtual nodes in consistent hash ring. + vitrual_nodes_size: "${CLUSTER_VIRTUAL_NODES_SIZE:16}" + +# Plugins configuration parameters +plugins: + # Comma seperated package list used during classpath scanning for plugins + scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions}" + +# JWT Token parameters +security.jwt: + tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins) + refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour) + tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}" + tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}" + +# Device communication protocol parameters +http: + request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" + +# MQTT server parameters +mqtt: + bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" + bind_port: "${MQTT_BIND_PORT:1883}" + adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}" + timeout: "${MQTT_TIMEOUT:10000}" +# Uncomment the following lines to enable ssl for MQTT +# ssl: +# key-store: keystore/mqttserver.jks +# key-store-password: password +# keyStoreType: JKS +# TrustStore can be the same as KeyStore +# trust-store: keystore/mqttserver.jks +# trust-store-password: password +# trustStoreType: JKS + +# CoAP server parameters +coap: + bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" + bind_port: "${COAP_BIND_PORT:5683}" + adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}" + timeout: "${COAP_TIMEOUT:10000}" + +# Cassandra driver configuration parameters +cassandra: + # Thingsboard cluster name + cluster_name: "${CASSANDRA_CLUSTER_NAME:Thingsboard Cluster}" + # Thingsboard keyspace name + keyspace_name: "${CASSANDRA_KEYSPACE_NAME:thingsboard}" + # Specify node list + url: "${CASSANDRA_URL:127.0.0.1:9042}" + # Enable/disable secure connection + ssl: "${CASSANDRA_USE_SSL:false}" + # Enable/disable JMX + jmx: "${CASSANDRA_USE_JMX:true}" + # Enable/disable metrics collection. + metrics: "${CASSANDRA_DISABLE_METRICS:true}" + # NONE SNAPPY LZ4 + compression: "${CASSANDRA_COMPRESSION:none}" + # Specify cassandra claster initialization timeout (if no hosts available during startup) + init_timeout_ms: "${CASSANDRA_CLUSTER_INIT_TIMEOUT_MS:300000}" + # Specify cassandra claster initialization retry interval (if no hosts available during startup) + init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}" + + # Credential parameters # + credentials: "${CASSANDRA_USE_CREDENTIALS:false}" + # Specify your username + username: "${CASSANDRA_USERNAME:}" + # Specify your password + password: "${CASSANDRA_PASSWORD:}" + + # Cassandra cluster connection socket parameters # + socket: + connect_timeout: "${CASSANDRA_SOCKET_TIMEOUT:5000}" + read_timeout: "${CASSANDRA_SOCKET_READ_TIMEOUT:20000}" + keep_alive: "${CASSANDRA_SOCKET_KEEP_ALIVE:true}" + reuse_address: "${CASSANDRA_SOCKET_REUSE_ADDRESS:true}" + so_linger: "${CASSANDRA_SOCKET_SO_LINGER:}" + tcp_no_delay: "${CASSANDRA_SOCKET_TCP_NO_DELAY:false}" + receive_buffer_size: "${CASSANDRA_SOCKET_RECEIVE_BUFFER_SIZE:}" + send_buffer_size: "${CASSANDRA_SOCKET_SEND_BUFFER_SIZE:}" + + # Cassandra cluster connection query parameters # + query: + read_consistency_level: "${CASSANDRA_READ_CONSISTENCY_LEVEL:ONE}" + write_consistency_level: "${CASSANDRA_WRITE_CONSISTENCY_LEVEL:ONE}" + default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}" + # Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS + ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" + # Specify max partitions per request + max_limit_per_request: "${TS_KV_MAX_LIMIT_PER_REQUEST:1000}" + +# Actor system parameters +actors: + session: + sync: + # Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds + timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}" + plugin: + # Default timeout for termination of the plugin actor after it is stopped + termination.delay: "${ACTORS_PLUGIN_TERMINATION_DELAY:60000}" + # Default timeout for processing of particular message by particular plugin + processing.timeout: "${ACTORS_PLUGIN_TIMEOUT:60000}" + # Errors for particular actor are persisted once per specified amount of milliseconds + error_persist_frequency: "${ACTORS_PLUGIN_ERROR_FREQUENCY:3000}" + rule: + # Default timeout for termination of the rule actor after it is stopped + termination.delay: "${ACTORS_RULE_TERMINATION_DELAY:30000}" + # Errors for particular actor are persisted once per specified amount of milliseconds + error_persist_frequency: "${ACTORS_RULE_ERROR_FREQUENCY:3000}" + statistics: + # Enable/disable actor statistics + enabled: "${ACTORS_STATISTICS_ENABLED:true}" + persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:60000}" + +# Cache parameters +cache: + # Enable/disable cache functionality. + enabled: "${CACHE_ENABLED:true}" + device_credentials: + # default time to store device credentials in cache, in seconds + time_to_live: "${DEVICE_CREDENTIAL_CACHE_TTL:3600}" + # default maximum size of device credentials cache + max_size: "${DEVICE_CREDENTIAL_CACHE_MAX_SIZE:1000000}" + diff --git a/application/src/main/scripts/control/deb/postinst b/application/src/main/scripts/control/deb/postinst new file mode 100644 index 00000000000..d4066c027b0 --- /dev/null +++ b/application/src/main/scripts/control/deb/postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} +update-rc.d ${pkg.name} defaults + diff --git a/application/src/main/scripts/control/deb/postrm b/application/src/main/scripts/control/deb/postrm new file mode 100644 index 00000000000..61865803c38 --- /dev/null +++ b/application/src/main/scripts/control/deb/postrm @@ -0,0 +1,3 @@ +#!/bin/sh + +update-rc.d -f ${pkg.name} remove diff --git a/application/src/main/scripts/control/deb/preinst b/application/src/main/scripts/control/deb/preinst new file mode 100644 index 00000000000..6be59592853 --- /dev/null +++ b/application/src/main/scripts/control/deb/preinst @@ -0,0 +1,18 @@ +#!/bin/sh + +if ! getent group ${pkg.name} >/dev/null; then + addgroup --system ${pkg.name} +fi + +if ! getent passwd ${pkg.name} >/dev/null; then + adduser --quiet \ + --system \ + --ingroup ${pkg.name} \ + --quiet \ + --disabled-login \ + --disabled-password \ + --home ${pkg.installFolder} \ + --no-create-home \ + -gecos "Thingsboard application" \ + ${pkg.name} +fi diff --git a/application/src/main/scripts/control/deb/prerm b/application/src/main/scripts/control/deb/prerm new file mode 100644 index 00000000000..898d3efd5cc --- /dev/null +++ b/application/src/main/scripts/control/deb/prerm @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then + service ${pkg.name} stop +fi diff --git a/application/src/main/scripts/control/rpm/postinst b/application/src/main/scripts/control/rpm/postinst new file mode 100644 index 00000000000..8a7a88f7e09 --- /dev/null +++ b/application/src/main/scripts/control/rpm/postinst @@ -0,0 +1,9 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} + +if [ $1 -eq 1 ] ; then + # Initial installation + systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/application/src/main/scripts/control/rpm/postrm b/application/src/main/scripts/control/rpm/postrm new file mode 100644 index 00000000000..8e1f8a20485 --- /dev/null +++ b/application/src/main/scripts/control/rpm/postrm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -ge 1 ] ; then + # Package upgrade, not uninstall + systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/application/src/main/scripts/control/rpm/preinst b/application/src/main/scripts/control/rpm/preinst new file mode 100644 index 00000000000..e19fc884c83 --- /dev/null +++ b/application/src/main/scripts/control/rpm/preinst @@ -0,0 +1,6 @@ +#!/bin/sh + +getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} +getent passwd ${pkg.name} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +-c "Thingsboard application" diff --git a/application/src/main/scripts/control/rpm/prerm b/application/src/main/scripts/control/rpm/prerm new file mode 100644 index 00000000000..accb487b8eb --- /dev/null +++ b/application/src/main/scripts/control/rpm/prerm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : +fi diff --git a/application/src/main/scripts/control/thingsboard.service b/application/src/main/scripts/control/thingsboard.service new file mode 100644 index 00000000000..d456fc03c0f --- /dev/null +++ b/application/src/main/scripts/control/thingsboard.service @@ -0,0 +1,11 @@ +[Unit] +Description=${pkg.name} +After=syslog.target + +[Service] +User=${pkg.name} +ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target diff --git a/application/src/test/java/org/thingsboard/server/ThingsboardApplicationTests.java b/application/src/test/java/org/thingsboard/server/ThingsboardApplicationTests.java new file mode 100644 index 00000000000..63b62d26ec9 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/ThingsboardApplicationTests.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016 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; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = ThingsboardServerApplication.class) +@WebAppConfiguration +@IntegrationTest("server.port:0") +public class ThingsboardApplicationTests { + + @Test + public void contextLoads() { + String test = "[ \n" + + " {\n" + + " \"key\": \"name\",\n" + + "\t\"type\": \"text\" \n" + + " },\n" + + " {\n" + + "\t\"key\": \"name2\",\n" + + "\t\"type\": \"color\"\n" + + " },\n" + + " {\n" + + "\t\"key\": \"name3\",\n" + + "\t\"type\": \"javascript\"\n" + + " } \n" + + "]"; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java b/application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java new file mode 100644 index 00000000000..481868b9443 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.actors; + +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.runner.RunWith; + +/** + * @author Andrew Shvayka + */ +@RunWith(ClasspathSuite.class) +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.actors.*Test"}) +public class ActorsTestSuite { +} diff --git a/application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java b/application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java new file mode 100644 index 00000000000..10471d6d157 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java @@ -0,0 +1,242 @@ +/** + * Copyright © 2016 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.actors; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.*; + +import org.thingsboard.server.actors.service.DefaultActorService; +import org.thingsboard.server.common.data.id.*; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.plugin.ComponentDescriptor; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.session.*; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.event.EventService; +import org.thingsboard.server.gen.discovery.ServerInstanceProtos; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.cluster.discovery.ServerInstance; +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; +import org.thingsboard.server.service.component.ComponentDiscoveryService; +import org.thingsboard.server.common.transport.auth.DeviceAuthResult; +import org.thingsboard.server.common.transport.auth.DeviceAuthService; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.data.rule.RuleMetaData; +import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; +import org.thingsboard.server.common.data.security.DeviceTokenCredentials; +import org.thingsboard.server.common.msg.core.BasicTelemetryUploadRequest; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.rule.RuleService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class DefaultActorServiceTest { + + private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); + + private static final String PLUGIN_ID = "9fb2e951-e298-4acb-913a-db69af8a15f4"; + private static final String FILTERS_CONFIGURATION = + "[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", \"name\":\"TelemetryFilter\", \"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]"; + private static final String ACTION_CONFIGURATION = "{\"pluginToken\":\"telemetry\", \"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", \"configuration\":{}}"; + private static final String PLUGIN_CONFIGURATION = "{}"; + private DefaultActorService actorService; + private ActorSystemContext actorContext; + + private PluginService pluginService; + private RuleService ruleService; + private DeviceAuthService deviceAuthService; + private DeviceService deviceService; + private TimeseriesService tsService; + private TenantService tenantService; + private ClusterRpcService rpcService; + private DiscoveryService discoveryService; + private ClusterRoutingService routingService; + private AttributesService attributesService; + private ComponentDiscoveryService componentService; + private EventService eventService; + private ServerInstance serverInstance; + + private RuleMetaData ruleMock; + private PluginMetaData pluginMock; + private RuleId ruleId = new RuleId(UUID.randomUUID()); + private PluginId pluginId = new PluginId(UUID.fromString(PLUGIN_ID)); + private TenantId tenantId = new TenantId(UUID.randomUUID()); + + + @Before + public void before() throws Exception { + actorService = new DefaultActorService(); + actorContext = new ActorSystemContext(); + + tenantService = mock(TenantService.class); + pluginService = mock(PluginService.class); + ruleService = mock(RuleService.class); + deviceAuthService = mock(DeviceAuthService.class); + deviceService = mock(DeviceService.class); + tsService = mock(TimeseriesService.class); + rpcService = mock(ClusterRpcService.class); + discoveryService = mock(DiscoveryService.class); + routingService = mock(ClusterRoutingService.class); + attributesService = mock(AttributesService.class); + componentService = mock(ComponentDiscoveryService.class); + eventService = mock(EventService.class); + serverInstance = new ServerInstance(ServerInstanceProtos.ServerInfo.newBuilder().setHost("localhost").setPort(8080).build()); + + ReflectionTestUtils.setField(actorService, "actorContext", actorContext); + ReflectionTestUtils.setField(actorService, "rpcService", rpcService); + ReflectionTestUtils.setField(actorService, "discoveryService", discoveryService); + + ReflectionTestUtils.setField(actorContext, "syncSessionTimeout", 10000L); + ReflectionTestUtils.setField(actorContext, "pluginActorTerminationDelay", 10000L); + ReflectionTestUtils.setField(actorContext, "pluginErrorPersistFrequency", 10000L); + ReflectionTestUtils.setField(actorContext, "ruleActorTerminationDelay", 10000L); + ReflectionTestUtils.setField(actorContext, "ruleErrorPersistFrequency", 10000L); + ReflectionTestUtils.setField(actorContext, "pluginProcessingTimeout", 60000L); + ReflectionTestUtils.setField(actorContext, "tenantService", tenantService); + ReflectionTestUtils.setField(actorContext, "pluginService", pluginService); + ReflectionTestUtils.setField(actorContext, "ruleService", ruleService); + ReflectionTestUtils.setField(actorContext, "deviceAuthService", deviceAuthService); + ReflectionTestUtils.setField(actorContext, "deviceService", deviceService); + ReflectionTestUtils.setField(actorContext, "tsService", tsService); + ReflectionTestUtils.setField(actorContext, "rpcService", rpcService); + ReflectionTestUtils.setField(actorContext, "discoveryService", discoveryService); + ReflectionTestUtils.setField(actorContext, "tsService", tsService); + ReflectionTestUtils.setField(actorContext, "routingService", routingService); + ReflectionTestUtils.setField(actorContext, "attributesService", attributesService); + ReflectionTestUtils.setField(actorContext, "componentService", componentService); + ReflectionTestUtils.setField(actorContext, "eventService", eventService); + + + when(routingService.resolve(any())).thenReturn(Optional.empty()); + + when(discoveryService.getCurrentServer()).thenReturn(serverInstance); + + ruleMock = mock(RuleMetaData.class); + when(ruleMock.getId()).thenReturn(ruleId); + when(ruleMock.getState()).thenReturn(ComponentLifecycleState.ACTIVE); + when(ruleMock.getPluginToken()).thenReturn("telemetry"); + TextPageData systemRules = new TextPageData<>(Collections.emptyList(), null, false); + TextPageData tenantRules = new TextPageData<>(Collections.singletonList(ruleMock), null, false); + when(ruleService.findSystemRules(any())).thenReturn(systemRules); + when(ruleService.findTenantRules(any(), any())).thenReturn(tenantRules); + when(ruleService.findRuleById(ruleId)).thenReturn(ruleMock); + + pluginMock = mock(PluginMetaData.class); + when(pluginMock.getTenantId()).thenReturn(SYSTEM_TENANT); + when(pluginMock.getId()).thenReturn(pluginId); + when(pluginMock.getState()).thenReturn(ComponentLifecycleState.ACTIVE); + TextPageData systemPlugins = new TextPageData<>(Collections.singletonList(pluginMock), null, false); + TextPageData tenantPlugins = new TextPageData<>(Collections.emptyList(), null, false); + when(pluginService.findSystemPlugins(any())).thenReturn(systemPlugins); + when(pluginService.findTenantPlugins(any(), any())).thenReturn(tenantPlugins); + when(pluginService.findPluginByApiToken("telemetry")).thenReturn(pluginMock); + when(pluginService.findPluginById(pluginId)).thenReturn(pluginMock); + + TextPageData tenants = new TextPageData<>(Collections.emptyList(), null, false); + when(tenantService.findTenants(any())).thenReturn(tenants); + } + + private void initActorSystem() { + actorService.initActorSystem(); + } + + @After + public void after() { + actorService.stopActorSystem(); + } + + @Test + public void testBasicPostWithSyncSession() throws Exception { + SessionContext ssnCtx = mock(SessionContext.class); + KvEntry entry1 = new StringDataEntry("key1", "value1"); + KvEntry entry2 = new StringDataEntry("key2", "value2"); + BasicTelemetryUploadRequest telemetry = new BasicTelemetryUploadRequest(); + long ts = 42; + telemetry.add(ts, entry1); + telemetry.add(ts, entry2); + BasicAdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ssnCtx, telemetry); + + DeviceId deviceId = new DeviceId(UUID.randomUUID()); + + DeviceCredentialsFilter filter = new DeviceTokenCredentials("token1"); + Device device = mock(Device.class); + + when(device.getId()).thenReturn(deviceId); + when(device.getTenantId()).thenReturn(tenantId); + when(ssnCtx.getSessionId()).thenReturn(new DummySessionID("session1")); + when(ssnCtx.getSessionType()).thenReturn(SessionType.SYNC); + when(deviceAuthService.process(filter)).thenReturn(DeviceAuthResult.of(deviceId)); + when(deviceService.findDeviceById(deviceId)).thenReturn(device); + + ObjectMapper ruleMapper = new ObjectMapper(); + when(ruleMock.getFilters()).thenReturn(ruleMapper.readTree(FILTERS_CONFIGURATION)); + when(ruleMock.getAction()).thenReturn(ruleMapper.readTree(ACTION_CONFIGURATION)); + + ComponentDescriptor filterComp = new ComponentDescriptor(); + filterComp.setClazz("org.thingsboard.server.extensions.core.filter.MsgTypeFilter"); + filterComp.setType(ComponentType.FILTER); + when(componentService.getComponent("org.thingsboard.server.extensions.core.filter.MsgTypeFilter")) + .thenReturn(Optional.of(filterComp)); + + ComponentDescriptor actionComp = new ComponentDescriptor(); + actionComp.setClazz("org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction"); + actionComp.setType(ComponentType.ACTION); + when(componentService.getComponent("org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction")) + .thenReturn(Optional.of(actionComp)); + + ObjectMapper pluginMapper = new ObjectMapper(); + JsonNode pluginAdditionalInfo = pluginMapper.readTree(PLUGIN_CONFIGURATION); + when(pluginMock.getConfiguration()).thenReturn(pluginAdditionalInfo); + when(pluginMock.getClazz()).thenReturn(TelemetryStoragePlugin.class.getName()); + + when(attributesService.findAll(deviceId, DataConstants.CLIENT_SCOPE)).thenReturn(Collections.emptyList()); + + initActorSystem(); + Thread.sleep(1000); + actorService.process(new BasicToDeviceActorSessionMsg(device, msg)); + + // Check that device data was saved to DB; + List expected = new ArrayList<>(); + expected.add(new BasicTsKvEntry(ts, entry1)); + expected.add(new BasicTsKvEntry(ts, entry2)); + verify(tsService, Mockito.timeout(5000)).save(DataConstants.DEVICE, deviceId, expected); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/actors/DummySessionID.java b/application/src/test/java/org/thingsboard/server/actors/DummySessionID.java new file mode 100644 index 00000000000..ccb91387fe8 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/actors/DummySessionID.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 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.actors; + +import org.thingsboard.server.common.data.id.SessionId; + +public class DummySessionID implements SessionId { + + @Override + public String toString() { + return id; + } + + private final String id; + + public DummySessionID(String id) { + this.id = id; + } + + @Override + public String toUidStr() { + return id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DummySessionID other = (DummySessionID) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java new file mode 100644 index 00000000000..538e8f9ea91 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -0,0 +1,375 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.Jwts; +import org.apache.commons.lang3.StringUtils; +import org.hamcrest.Matcher; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationContextLoader; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.mock.http.MockHttpInputMessage; +import org.springframework.mock.http.MockHttpOutputMessage; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.WebApplicationContext; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.config.ThingsboardSecurityConfiguration; +import org.thingsboard.server.exception.ThingsboardException; +import org.thingsboard.server.service.mail.MailService; +import org.thingsboard.server.service.mail.TestMailService; +import org.thingsboard.server.service.security.auth.rest.LoginRequest; +import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +@ActiveProfiles("test") +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes=AbstractControllerTest.class, loader=SpringApplicationContextLoader.class) +@TestPropertySource("classpath:cassandra-test.properties") +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@Configuration +@EnableAutoConfiguration +@ComponentScan({"org.thingsboard.server"}) +@WebAppConfiguration +@IntegrationTest("server.port:0") +public abstract class AbstractControllerTest { + + protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org"; + private static final String SYS_ADMIN_PASSWORD = "sysadmin"; + + protected static final String TENANT_ADMIN_EMAIL = "tenant@thingsboard.org"; + private static final String TENANT_ADMIN_PASSWORD = "tenant"; + + protected static final String CUSTOMER_USER_EMAIL = "customer@thingsboard.org"; + private static final String CUSTOMER_USER_PASSWORD = "customer"; + + protected MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), + MediaType.APPLICATION_JSON.getSubtype(), + Charset.forName("utf8")); + + + protected MockMvc mockMvc; + + protected String token; + protected String refreshToken; + protected String username; + + private TenantId tenantId; + + @SuppressWarnings("rawtypes") + private HttpMessageConverter mappingJackson2HttpMessageConverter; + + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + void setConverters(HttpMessageConverter[] converters) { + + this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter( + hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get(); + + Assert.assertNotNull("the JSON message converter must not be null", + this.mappingJackson2HttpMessageConverter); + } + + @Before + public void setup() throws Exception { + if (this.mockMvc == null) { + this.mockMvc = webAppContextSetup(webApplicationContext) + .apply(springSecurity()).build(); + } + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("Tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + tenantId = savedTenant.getId(); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(tenantId); + tenantAdmin.setEmail(TENANT_ADMIN_EMAIL); + + createUserAndLogin(tenantAdmin, TENANT_ADMIN_PASSWORD); + + Customer customer = new Customer(); + customer.setTitle("Customer"); + customer.setTenantId(tenantId); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + User customerUser = new User(); + customerUser.setAuthority(Authority.CUSTOMER_USER); + customerUser.setTenantId(tenantId); + customerUser.setCustomerId(savedCustomer.getId()); + customerUser.setEmail(CUSTOMER_USER_EMAIL); + + createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD); + + logout(); + } + + @After + public void teardown() throws Exception { + loginSysAdmin(); + doDelete("/api/tenant/"+tenantId.getId().toString()) + .andExpect(status().isOk()); + } + + protected void loginSysAdmin() throws Exception { + login(SYS_ADMIN_EMAIL, SYS_ADMIN_PASSWORD); + } + + protected void loginTenantAdmin() throws Exception { + login(TENANT_ADMIN_EMAIL, TENANT_ADMIN_PASSWORD); + } + + protected void loginCustomerUser() throws Exception { + login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD); + } + + protected User createUserAndLogin(User user, String password) throws Exception { + User savedUser = doPost("/api/user", user, User.class); + logout(); + doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) + .andExpect(status().isPermanentRedirect()) + .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", "activateToken", TestMailService.currentActivateToken, "password", password).andExpect(status().isOk()), JsonNode.class); + validateAndSetJwtToken(tokenInfo, user.getEmail()); + return savedUser; + } + + protected void login(String username, String password) throws Exception { + this.token = null; + this.refreshToken = null; + this.username = null; + JsonNode tokenInfo = readResponse(doPost("/api/auth/login", new LoginRequest(username, password)).andExpect(status().isOk()), JsonNode.class); + validateAndSetJwtToken(tokenInfo, username); + } + + protected void refreshToken() throws Exception { + this.token = null; + JsonNode tokenInfo = readResponse(doPost("/api/auth/token", new RefreshTokenRequest(this.refreshToken)).andExpect(status().isOk()), JsonNode.class); + validateAndSetJwtToken(tokenInfo, this.username); + } + + protected void validateAndSetJwtToken(JsonNode tokenInfo, String username) { + Assert.assertNotNull(tokenInfo); + Assert.assertTrue(tokenInfo.has("token")); + Assert.assertTrue(tokenInfo.has("refreshToken")); + String token = tokenInfo.get("token").asText(); + String refreshToken = tokenInfo.get("refreshToken").asText(); + validateJwtToken(token, username); + validateJwtToken(refreshToken, username); + this.token = token; + this.refreshToken = refreshToken; + this.username = username; + } + + protected void validateJwtToken(String token, String username) { + Assert.assertNotNull(token); + Assert.assertFalse(token.isEmpty()); + int i = token.lastIndexOf('.'); + Assert.assertTrue(i>0); + String withoutSignature = token.substring(0, i+1); + Jwt jwsClaims = Jwts.parser().parseClaimsJwt(withoutSignature); + Claims claims = jwsClaims.getBody(); + String subject = claims.getSubject(); + Assert.assertEquals(username, subject); + } + + protected void logout() throws Exception { + this.token = null; + this.refreshToken = null; + this.username = null; + } + + protected void setJwtToken(MockHttpServletRequestBuilder request) { + if (this.token != null) { + request.header(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM, "Bearer " + this.token); + } + } + + protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception { + MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables); + setJwtToken(getRequest); + return mockMvc.perform(getRequest); + } + + protected T doGet(String urlTemplate, Class responseClass, Object... urlVariables) throws Exception { + return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); + } + + protected T doGetTyped(String urlTemplate, TypeReference responseType, Object... urlVariables) throws Exception { + return readResponse(doGet(urlTemplate, urlVariables).andExpect(status().isOk()), responseType); + } + + protected T doGetTypedWithPageLink(String urlTemplate, TypeReference responseType, + TextPageLink pageLink, + Object... urlVariables) throws Exception { + List pageLinkVariables = new ArrayList<>(); + urlTemplate += "limit={limit}"; + pageLinkVariables.add(pageLink.getLimit()); + if (StringUtils.isNotEmpty(pageLink.getTextSearch())) { + urlTemplate += "&textSearch={textSearch}"; + pageLinkVariables.add(pageLink.getTextSearch()); + } + if (pageLink.getIdOffset() != null) { + urlTemplate += "&idOffset={idOffset}"; + pageLinkVariables.add(pageLink.getIdOffset().toString()); + } + if (StringUtils.isNotEmpty(pageLink.getTextOffset())) { + urlTemplate += "&textOffset={textOffset}"; + pageLinkVariables.add(pageLink.getTextOffset()); + } + + Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; + System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); + System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size()); + + return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); + } + + protected T doPost(String urlTemplate, Class responseClass, String... params) throws Exception { + return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); + } + + protected T doPost(String urlTemplate, T content, Class responseClass, String... params) throws Exception { + return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass); + } + + protected T doDelete(String urlTemplate, Class responseClass, String... params) throws Exception { + return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass); + } + + protected ResultActions doPost(String urlTemplate, String... params) throws Exception { + MockHttpServletRequestBuilder postRequest = post(urlTemplate); + setJwtToken(postRequest); + populateParams(postRequest, params); + return mockMvc.perform(postRequest); + } + + protected ResultActions doPost(String urlTemplate, T content, String... params) throws Exception { + MockHttpServletRequestBuilder postRequest = post(urlTemplate); + setJwtToken(postRequest); + String json = json(content); + postRequest.contentType(contentType).content(json); + populateParams(postRequest, params); + return mockMvc.perform(postRequest); + } + + protected ResultActions doDelete(String urlTemplate, String... params) throws Exception { + MockHttpServletRequestBuilder deleteRequest = delete(urlTemplate); + setJwtToken(deleteRequest); + populateParams(deleteRequest, params); + return mockMvc.perform(deleteRequest); + } + + protected void populateParams(MockHttpServletRequestBuilder request, String... params) { + if (params != null && params.length > 0) { + Assert.assertEquals(params.length % 2, 0); + MultiValueMap paramsMap = new LinkedMultiValueMap(); + for (int i=0;i T readResponse(ResultActions result, Class responseClass) throws Exception { + byte[] content = result.andReturn().getResponse().getContentAsByteArray(); + MockHttpInputMessage mockHttpInputMessage = new MockHttpInputMessage(content); + return (T) this.mappingJackson2HttpMessageConverter.read(responseClass, mockHttpInputMessage); + } + + protected T readResponse(ResultActions result, TypeReference type) throws Exception { + byte[] content = result.andReturn().getResponse().getContentAsByteArray(); + ObjectMapper mapper = new ObjectMapper(); + return mapper.readerFor(type).readValue(content); + } + + class IdComparator> implements Comparator { + @Override + public int compare(D o1, D o2) { + return o1.getId().getId().compareTo(o2.getId().getId()); + } + } + + protected static ResultMatcher statusReason(Matcher matcher) { + return jsonPath("$.message", matcher); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java new file mode 100644 index 00000000000..6712aa28b6e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java @@ -0,0 +1,146 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.thingsboard.server.common.data.AdminSettings; +import org.junit.Test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class AdminControllerTest extends AbstractControllerTest { + + @Test + public void testFindAdminSettingsByKey() throws Exception { + loginSysAdmin(); + doGet("/api/admin/settings/general") + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", notNullValue())) + .andExpect(jsonPath("$.key", is("general"))) + .andExpect(jsonPath("$.jsonValue.baseUrl", is("http://localhost:8080"))); + + doGet("/api/admin/settings/mail") + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", notNullValue())) + .andExpect(jsonPath("$.key", is("mail"))) + .andExpect(jsonPath("$.jsonValue.smtpProtocol", is("smtp"))) + .andExpect(jsonPath("$.jsonValue.smtpHost", is("localhost"))) + .andExpect(jsonPath("$.jsonValue.smtpPort", is("25"))); + + doGet("/api/admin/settings/unknown") + .andExpect(status().isNotFound()); + + } + + @Test + public void testSaveAdminSettings() throws Exception { + loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/general", AdminSettings.class); + + JsonNode jsonValue = adminSettings.getJsonValue(); + ((ObjectNode) jsonValue).put("baseUrl", "http://myhost.org"); + adminSettings.setJsonValue(jsonValue); + + doPost("/api/admin/settings", adminSettings).andExpect(status().isOk()); + + doGet("/api/admin/settings/general") + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.jsonValue.baseUrl", is("http://myhost.org"))); + + ((ObjectNode) jsonValue).put("baseUrl", "http://localhost:8080"); + adminSettings.setJsonValue(jsonValue); + + doPost("/api/admin/settings", adminSettings) + .andExpect(status().isOk()); + } + + @Test + public void testCreateAdminSettings() throws Exception { + loginSysAdmin(); + + AdminSettings adminSettings = new AdminSettings(); + adminSettings.setKey("someKey"); + adminSettings.setJsonValue(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + + doPost("/api/admin/settings", adminSettings) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("is prohibited"))); + } + + @Test + public void testSaveAdminSettingsWithEmptyKey() throws Exception { + loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + adminSettings.setKey(null); + doPost("/api/admin/settings", adminSettings) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Key should be specified"))); + } + + @Test + public void testChangeAdminSettingsKey() throws Exception { + loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + adminSettings.setKey("newKey"); + doPost("/api/admin/settings", adminSettings) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("is prohibited"))); + } + + @Test + public void testSaveAdminSettingsWithNewJsonStructure() throws Exception { + loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + JsonNode json = adminSettings.getJsonValue(); + ((ObjectNode) json).put("newKey", "my new value"); + adminSettings.setJsonValue(json); + doPost("/api/admin/settings", adminSettings) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Provided json structure is different"))); + } + + @Test + public void testSaveAdminSettingsWithNonTextValue() throws Exception { + loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + JsonNode json = adminSettings.getJsonValue(); + ((ObjectNode) json).put("timeout", 10000L); + adminSettings.setJsonValue(json); + doPost("/api/admin/settings", adminSettings) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Provided json structure can't contain non-text values"))); + } + + @Test + public void testSendTestMail() throws Exception { + loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + doPost("/api/admin/settings/testMail", adminSettings) + .andExpect(status().isOk()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java new file mode 100644 index 00000000000..9fb9fd4a338 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java @@ -0,0 +1,79 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.thingsboard.server.common.data.security.Authority; +import org.junit.Test; + +public class AuthControllerTest extends AbstractControllerTest { + + @Test + public void testGetUser() throws Exception { + + doGet("/api/auth/user") + .andExpect(status().isUnauthorized()); + + loginSysAdmin(); + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.SYS_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(SYS_ADMIN_EMAIL))); + + loginTenantAdmin(); + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(TENANT_ADMIN_EMAIL))); + + loginCustomerUser(); + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.CUSTOMER_USER.name()))) + .andExpect(jsonPath("$.email",is(CUSTOMER_USER_EMAIL))); + } + + @Test + public void testLoginLogout() throws Exception { + loginSysAdmin(); + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.SYS_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(SYS_ADMIN_EMAIL))); + + logout(); + doGet("/api/auth/user") + .andExpect(status().isUnauthorized()); + } + + @Test + public void testRefreshToken() throws Exception { + loginSysAdmin(); + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.SYS_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(SYS_ADMIN_EMAIL))); + + refreshToken(); + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.SYS_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(SYS_ADMIN_EMAIL))); + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/ComponentDescriptorControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/ComponentDescriptorControllerTest.java new file mode 100644 index 00000000000..b90196622af --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/ComponentDescriptorControllerTest.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.plugin.ComponentDescriptor; +import org.thingsboard.server.common.data.plugin.ComponentScope; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction; +import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin; + +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class ComponentDescriptorControllerTest extends AbstractControllerTest { + + private static final int AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS = 5; + private Tenant savedTenant; + private User tenantAdmin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testGetByClazz() throws Exception { + ComponentDescriptor descriptor = + doGet("/api/component/" + TelemetryStoragePlugin.class.getName(), ComponentDescriptor.class); + + Assert.assertNotNull(descriptor); + Assert.assertNotNull(descriptor.getId()); + Assert.assertNotNull(descriptor.getName()); + Assert.assertEquals(ComponentScope.TENANT, descriptor.getScope()); + Assert.assertEquals(ComponentType.PLUGIN, descriptor.getType()); + Assert.assertEquals(descriptor.getClazz(), descriptor.getClazz()); + } + + @Test + public void testGetByType() throws Exception { + List descriptors = readResponse( + doGet("/api/components/" + ComponentType.PLUGIN).andExpect(status().isOk()), new TypeReference>() { + }); + + Assert.assertNotNull(descriptors); + Assert.assertEquals(AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS, descriptors.size()); + + for (ComponentType type : ComponentType.values()) { + doGet("/api/components/" + type).andExpect(status().isOk()); + } + } + + @Test + public void testGetActionsByType() throws Exception { + List descriptors = readResponse( + doGet("/api/components/actions/" + TelemetryStoragePlugin.class.getName()).andExpect(status().isOk()), new TypeReference>() { + }); + + Assert.assertNotNull(descriptors); + Assert.assertEquals(1, descriptors.size()); + Assert.assertEquals(TelemetryPluginAction.class.getName(), descriptors.get(0).getClazz()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerTestSuite.java new file mode 100644 index 00000000000..9ac768cc26d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerTestSuite.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; +import org.junit.ClassRule; +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters; +import org.junit.runner.RunWith; +import org.thingsboard.server.dao.CustomCassandraCQLUnit; + +import java.util.Arrays; + +@RunWith(ClasspathSuite.class) +@ClassnameFilters({"org.thingsboard.server.controller.*Test"}) +public class ControllerTestSuite { + + @ClassRule + public static CustomCassandraCQLUnit cassandraUnit = + new CustomCassandraCQLUnit(Arrays.asList( + new ClassPathCQLDataSet("schema.cql", false, false), + new ClassPathCQLDataSet("system-data.cql", false, false), + new ClassPathCQLDataSet("system-test.cql", false, false)), + "cassandra-test.yaml", 30000l); + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java new file mode 100644 index 00000000000..b71a2d10644 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/CustomerControllerTest.java @@ -0,0 +1,366 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.security.Authority; +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.core.type.TypeReference; + +public class CustomerControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + @Test + public void testSaveCustomer() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + Assert.assertNotNull(savedCustomer); + Assert.assertNotNull(savedCustomer.getId()); + Assert.assertTrue(savedCustomer.getCreatedTime() > 0); + Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle()); + savedCustomer.setTitle("My new customer"); + doPost("/api/customer", savedCustomer, Customer.class); + + Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); + Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle()); + + doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) + .andExpect(status().isOk()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindCustomerById() throws Exception { + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); + Assert.assertNotNull(foundCustomer); + Assert.assertEquals(savedCustomer, foundCustomer); + + doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) + .andExpect(status().isOk()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testDeleteCustomer() throws Exception { + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/customer/"+savedCustomer.getId().getId().toString()) + .andExpect(status().isNotFound()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveCustomerWithEmptyTitle() throws Exception { + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Customer customer = new Customer(); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Customer title should be specified"))); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveCustomerWithInvalidEmail() throws Exception { + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + customer.setEmail("invalid@mail"); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Invalid email address format 'invalid@mail'"))); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindCustomers() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + TenantId tenantId = savedTenant.getId(); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(tenantId); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + List customers = new ArrayList<>(); + for (int i=0;i<135;i++) { + Customer customer = new Customer(); + customer.setTenantId(tenantId); + customer.setTitle("Customer"+i); + customers.add(doPost("/api/customer", customer, Customer.class)); + } + + List loadedCustomers = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(23); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + loadedCustomers.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(customers, idComparator); + Collections.sort(loadedCustomers, idComparator); + + Assert.assertEquals(customers, loadedCustomers); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindCustomersByTitle() throws Exception { + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + TenantId tenantId = savedTenant.getId(); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(tenantId); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + String title1 = "Customer title 1"; + List customersTitle1 = new ArrayList<>(); + for (int i=0;i<143;i++) { + Customer customer = new Customer(); + customer.setTenantId(tenantId); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title1+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + customer.setTitle(title); + customersTitle1.add(doPost("/api/customer", customer, Customer.class)); + } + String title2 = "Customer title 2"; + List customersTitle2 = new ArrayList<>(); + for (int i=0;i<175;i++) { + Customer customer = new Customer(); + customer.setTenantId(tenantId); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title2+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + customer.setTitle(title); + customersTitle2.add(doPost("/api/customer", customer, Customer.class)); + } + + List loadedCustomersTitle1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(15, title1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + loadedCustomersTitle1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(customersTitle1, idComparator); + Collections.sort(loadedCustomersTitle1, idComparator); + + Assert.assertEquals(customersTitle1, loadedCustomersTitle1); + + List loadedCustomersTitle2 = new ArrayList<>(); + pageLink = new TextPageLink(4, title2); + do { + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + loadedCustomersTitle2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(customersTitle2, idComparator); + Collections.sort(loadedCustomersTitle2, idComparator); + + Assert.assertEquals(customersTitle2, loadedCustomersTitle2); + + for (Customer customer : loadedCustomersTitle1) { + doDelete("/api/customer/"+customer.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title1); + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (Customer customer : loadedCustomersTitle2) { + doDelete("/api/customer/"+customer.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title2); + pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java new file mode 100644 index 00000000000..7520807d42b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java @@ -0,0 +1,431 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import static org.hamcrest.Matchers.containsString; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.model.ModelConstants; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.core.type.TypeReference; + +public class DashboardControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + private Tenant savedTenant; + private User tenantAdmin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveDashboard() throws Exception { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("My dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + + Assert.assertNotNull(savedDashboard); + Assert.assertNotNull(savedDashboard.getId()); + Assert.assertTrue(savedDashboard.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId()); + Assert.assertNotNull(savedDashboard.getCustomerId()); + Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId()); + Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); + + savedDashboard.setTitle("My new dashboard"); + doPost("/api/dashboard", savedDashboard, Dashboard.class); + + Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle()); + } + + @Test + public void testFindDashboardById() throws Exception { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("My dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + Assert.assertNotNull(foundDashboard); + Assert.assertEquals(savedDashboard, foundDashboard); + } + + @Test + public void testDeleteDashboard() throws Exception { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("My dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + + doDelete("/api/dashboard/"+savedDashboard.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/dashboard/"+savedDashboard.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveDashboardWithEmptyTitle() throws Exception { + Dashboard dashboard = new Dashboard(); + doPost("/api/dashboard", dashboard) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Dashboard title should be specified"))); + } + + @Test + public void testAssignUnassignDashboardToCustomer() throws Exception { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("My dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + Assert.assertEquals(savedCustomer.getId(), assignedDashboard.getCustomerId()); + + Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId()); + + Dashboard unassignedDashboard = + doDelete("/api/customer/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDashboard.getCustomerId().getId()); + + foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + Assert.assertEquals(ModelConstants.NULL_UUID, foundDashboard.getCustomerId().getId()); + } + + @Test + public void testAssignDashboardToNonExistentCustomer() throws Exception { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("My dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + + doPost("/api/customer/" + UUIDs.timeBased().toString() + + "/dashboard/" + savedDashboard.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testAssignDashboardToCustomerFromDifferentTenant() throws Exception { + loginSysAdmin(); + + Tenant tenant2 = new Tenant(); + tenant2.setTitle("Different tenant"); + Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class); + Assert.assertNotNull(savedTenant2); + + User tenantAdmin2 = new User(); + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin2.setTenantId(savedTenant2.getId()); + tenantAdmin2.setEmail("tenant3@thingsboard.org"); + tenantAdmin2.setFirstName("Joe"); + tenantAdmin2.setLastName("Downs"); + + tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("Different customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + login(tenantAdmin.getEmail(), "testPassword1"); + + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("My dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + + "/dashboard/" + savedDashboard.getId().getId().toString()) + .andExpect(status().isForbidden()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant2.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindTenantDashboards() throws Exception { + List dashboards = new ArrayList<>(); + for (int i=0;i<173;i++) { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("Dashboard"+i); + dashboards.add(doPost("/api/dashboard", dashboard, Dashboard.class)); + } + List loadedDashboards = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(24); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>(){}, pageLink); + loadedDashboards.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(dashboards, idComparator); + Collections.sort(loadedDashboards, idComparator); + + Assert.assertEquals(dashboards, loadedDashboards); + } + + @Test + public void testFindTenantDashboardsByTitle() throws Exception { + String title1 = "Dashboard title 1"; + List dashboardsTitle1 = new ArrayList<>(); + for (int i=0;i<134;i++) { + Dashboard dashboard = new Dashboard(); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title1+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + dashboard.setTitle(title); + dashboardsTitle1.add(doPost("/api/dashboard", dashboard, Dashboard.class)); + } + String title2 = "Dashboard title 2"; + List dashboardsTitle2 = new ArrayList<>(); + for (int i=0;i<112;i++) { + Dashboard dashboard = new Dashboard(); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title2+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + dashboard.setTitle(title); + dashboardsTitle2.add(doPost("/api/dashboard", dashboard, Dashboard.class)); + } + + List loadedDashboardsTitle1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(15, title1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>(){}, pageLink); + loadedDashboardsTitle1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(dashboardsTitle1, idComparator); + Collections.sort(loadedDashboardsTitle1, idComparator); + + Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); + + List loadedDashboardsTitle2 = new ArrayList<>(); + pageLink = new TextPageLink(4, title2); + do { + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>(){}, pageLink); + loadedDashboardsTitle2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(dashboardsTitle2, idComparator); + Collections.sort(loadedDashboardsTitle2, idComparator); + + Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); + + for (Dashboard dashboard : loadedDashboardsTitle1) { + doDelete("/api/dashboard/"+dashboard.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title1); + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (Dashboard dashboard : loadedDashboardsTitle2) { + doDelete("/api/dashboard/"+dashboard.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title2); + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + } + + @Test + public void testFindCustomerDashboards() throws Exception { + Customer customer = new Customer(); + customer.setTitle("Test customer"); + customer = doPost("/api/customer", customer, Customer.class); + CustomerId customerId = customer.getId(); + + List dashboards = new ArrayList<>(); + for (int i=0;i<173;i++) { + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("Dashboard"+i); + dashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + dashboards.add(doPost("/api/customer/" + customerId.getId().toString() + + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)); + } + + List loadedDashboards = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(21); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + new TypeReference>(){}, pageLink); + loadedDashboards.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(dashboards, idComparator); + Collections.sort(loadedDashboards, idComparator); + + Assert.assertEquals(dashboards, loadedDashboards); + } + + @Test + public void testFindCustomerDashboardsByTitle() throws Exception { + Customer customer = new Customer(); + customer.setTitle("Test customer"); + customer = doPost("/api/customer", customer, Customer.class); + CustomerId customerId = customer.getId(); + + String title1 = "Dashboard title 1"; + List dashboardsTitle1 = new ArrayList<>(); + for (int i=0;i<125;i++) { + Dashboard dashboard = new Dashboard(); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title1+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + dashboard.setTitle(title); + dashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + dashboardsTitle1.add(doPost("/api/customer/" + customerId.getId().toString() + + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)); + } + String title2 = "Dashboard title 2"; + List dashboardsTitle2 = new ArrayList<>(); + for (int i=0;i<143;i++) { + Dashboard dashboard = new Dashboard(); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title2+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + dashboard.setTitle(title); + dashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + dashboardsTitle2.add(doPost("/api/customer/" + customerId.getId().toString() + + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)); + } + + List loadedDashboardsTitle1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(18, title1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + new TypeReference>(){}, pageLink); + loadedDashboardsTitle1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(dashboardsTitle1, idComparator); + Collections.sort(loadedDashboardsTitle1, idComparator); + + Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); + + List loadedDashboardsTitle2 = new ArrayList<>(); + pageLink = new TextPageLink(7, title2); + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + new TypeReference>(){}, pageLink); + loadedDashboardsTitle2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(dashboardsTitle2, idComparator); + Collections.sort(loadedDashboardsTitle2, idComparator); + + Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); + + for (Dashboard dashboard : loadedDashboardsTitle1) { + doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(5, title1); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (Dashboard dashboard : loadedDashboardsTitle2) { + doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(9, title2); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java new file mode 100644 index 00000000000..50ceb754905 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java @@ -0,0 +1,549 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import static org.hamcrest.Matchers.containsString; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceCredentialsId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.model.ModelConstants; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.core.type.TypeReference; + +public class DeviceControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + private Tenant savedTenant; + private User tenantAdmin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + + Assert.assertNotNull(savedDevice); + Assert.assertNotNull(savedDevice.getId()); + Assert.assertTrue(savedDevice.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedDevice.getTenantId()); + Assert.assertNotNull(savedDevice.getCustomerId()); + Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId()); + Assert.assertEquals(device.getName(), savedDevice.getName()); + + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + + Assert.assertNotNull(deviceCredentials); + Assert.assertNotNull(deviceCredentials.getId()); + Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); + Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType()); + Assert.assertNotNull(deviceCredentials.getCredentialsId()); + Assert.assertEquals(20, deviceCredentials.getCredentialsId().length()); + + savedDevice.setName("My new device"); + doPost("/api/device", savedDevice, Device.class); + + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); + } + + @Test + public void testFindDeviceById() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + Assert.assertNotNull(foundDevice); + Assert.assertEquals(savedDevice, foundDevice); + } + + @Test + public void testDeleteDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + + doDelete("/api/device/"+savedDevice.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/device/"+savedDevice.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveDeviceWithEmptyName() throws Exception { + Device device = new Device(); + doPost("/api/device", device) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Device name should be specified"))); + } + + @Test + public void testAssignUnassignDeviceToCustomer() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + + "/device/" + savedDevice.getId().getId().toString(), Device.class); + Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId()); + + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId()); + + Device unassignedDevice = + doDelete("/api/customer/device/" + savedDevice.getId().getId().toString(), Device.class); + Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); + + foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId()); + } + + @Test + public void testAssignDeviceToNonExistentCustomer() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + + doPost("/api/customer/" + UUIDs.timeBased().toString() + + "/device/" + savedDevice.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testAssignDeviceToCustomerFromDifferentTenant() throws Exception { + loginSysAdmin(); + + Tenant tenant2 = new Tenant(); + tenant2.setTitle("Different tenant"); + Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class); + Assert.assertNotNull(savedTenant2); + + User tenantAdmin2 = new User(); + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin2.setTenantId(savedTenant2.getId()); + tenantAdmin2.setEmail("tenant3@thingsboard.org"); + tenantAdmin2.setFirstName("Joe"); + tenantAdmin2.setLastName("Downs"); + + tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("Different customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + login(tenantAdmin.getEmail(), "testPassword1"); + + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + + "/device/" + savedDevice.getId().getId().toString()) + .andExpect(status().isForbidden()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant2.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindDeviceCredentialsByDeviceId() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); + } + + @Test + public void testSaveDeviceCredentials() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + deviceCredentials.setCredentialsId("access_token"); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isOk()); + + DeviceCredentials foundDeviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + + Assert.assertEquals(deviceCredentials, foundDeviceCredentials); + } + + @Test + public void testSaveDeviceCredentialsWithEmptyDevice() throws Exception { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isBadRequest()); + } + + @Test + public void testSaveDeviceCredentialsWithEmptyCredentialsType() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + deviceCredentials.setCredentialsType(null); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Device credentials type should be specified"))); + } + + @Test + public void testSaveDeviceCredentialsWithEmptyCredentialsId() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + deviceCredentials.setCredentialsId(null); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Device credentials id should be specified"))); + } + + @Test + public void testSaveNonExistentDeviceCredentials() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(UUIDs.timeBased())); + newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime()); + newDeviceCredentials.setDeviceId(deviceCredentials.getDeviceId()); + newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType()); + newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId()); + doPost("/api/device/credentials", newDeviceCredentials) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Unable to update non-existent device credentials"))); + } + + @Test + public void testSaveDeviceCredentialsWithNonExistentDevice() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + deviceCredentials.setDeviceId(new DeviceId(UUIDs.timeBased())); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveDeviceCredentialsWithInvalidCredemtialsIdLength() throws Exception { + Device device = new Device(); + device.setName("My device"); + Device savedDevice = doPost("/api/device", device, Device.class); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(21)); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Incorrect access token length"))); + } + + @Test + public void testFindTenantDevices() throws Exception { + List devices = new ArrayList<>(); + for (int i=0;i<178;i++) { + Device device = new Device(); + device.setName("Device"+i); + devices.add(doPost("/api/device", device, Device.class)); + } + List loadedDevices = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(23); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/tenant/devices?", + new TypeReference>(){}, pageLink); + loadedDevices.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(devices, idComparator); + Collections.sort(loadedDevices, idComparator); + + Assert.assertEquals(devices, loadedDevices); + } + + @Test + public void testFindTenantDevicesByName() throws Exception { + String title1 = "Device title 1"; + List devicesTitle1 = new ArrayList<>(); + for (int i=0;i<143;i++) { + Device device = new Device(); + String suffix = RandomStringUtils.randomAlphanumeric(15); + String name = title1+suffix; + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); + device.setName(name); + devicesTitle1.add(doPost("/api/device", device, Device.class)); + } + String title2 = "Device title 2"; + List devicesTitle2 = new ArrayList<>(); + for (int i=0;i<75;i++) { + Device device = new Device(); + String suffix = RandomStringUtils.randomAlphanumeric(15); + String name = title2+suffix; + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); + device.setName(name); + devicesTitle2.add(doPost("/api/device", device, Device.class)); + } + + List loadedDevicesTitle1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(15, title1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/tenant/devices?", + new TypeReference>(){}, pageLink); + loadedDevicesTitle1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(devicesTitle1, idComparator); + Collections.sort(loadedDevicesTitle1, idComparator); + + Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); + + List loadedDevicesTitle2 = new ArrayList<>(); + pageLink = new TextPageLink(4, title2); + do { + pageData = doGetTypedWithPageLink("/api/tenant/devices?", + new TypeReference>(){}, pageLink); + loadedDevicesTitle2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(devicesTitle2, idComparator); + Collections.sort(loadedDevicesTitle2, idComparator); + + Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); + + for (Device device : loadedDevicesTitle1) { + doDelete("/api/device/"+device.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title1); + pageData = doGetTypedWithPageLink("/api/tenant/devices?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (Device device : loadedDevicesTitle2) { + doDelete("/api/device/"+device.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title2); + pageData = doGetTypedWithPageLink("/api/tenant/devices?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + } + + @Test + public void testFindCustomerDevices() throws Exception { + Customer customer = new Customer(); + customer.setTitle("Test customer"); + customer = doPost("/api/customer", customer, Customer.class); + CustomerId customerId = customer.getId(); + + List devices = new ArrayList<>(); + for (int i=0;i<128;i++) { + Device device = new Device(); + device.setName("Device"+i); + device = doPost("/api/device", device, Device.class); + devices.add(doPost("/api/customer/" + customerId.getId().toString() + + "/device/" + device.getId().getId().toString(), Device.class)); + } + + List loadedDevices = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(23); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", + new TypeReference>(){}, pageLink); + loadedDevices.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(devices, idComparator); + Collections.sort(loadedDevices, idComparator); + + Assert.assertEquals(devices, loadedDevices); + } + + @Test + public void testFindCustomerDevicesByName() throws Exception { + Customer customer = new Customer(); + customer.setTitle("Test customer"); + customer = doPost("/api/customer", customer, Customer.class); + CustomerId customerId = customer.getId(); + + String title1 = "Device title 1"; + List devicesTitle1 = new ArrayList<>(); + for (int i=0;i<125;i++) { + Device device = new Device(); + String suffix = RandomStringUtils.randomAlphanumeric(15); + String name = title1+suffix; + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); + device.setName(name); + device = doPost("/api/device", device, Device.class); + devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString() + + "/device/" + device.getId().getId().toString(), Device.class)); + } + String title2 = "Device title 2"; + List devicesTitle2 = new ArrayList<>(); + for (int i=0;i<143;i++) { + Device device = new Device(); + String suffix = RandomStringUtils.randomAlphanumeric(15); + String name = title2+suffix; + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); + device.setName(name); + device = doPost("/api/device", device, Device.class); + devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString() + + "/device/" + device.getId().getId().toString(), Device.class)); + } + + List loadedDevicesTitle1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(15, title1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", + new TypeReference>(){}, pageLink); + loadedDevicesTitle1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(devicesTitle1, idComparator); + Collections.sort(loadedDevicesTitle1, idComparator); + + Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); + + List loadedDevicesTitle2 = new ArrayList<>(); + pageLink = new TextPageLink(4, title2); + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", + new TypeReference>(){}, pageLink); + loadedDevicesTitle2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(devicesTitle2, idComparator); + Collections.sort(loadedDevicesTitle2, idComparator); + + Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); + + for (Device device : loadedDevicesTitle1) { + doDelete("/api/customer/device/" + device.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title1); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (Device device : loadedDevicesTitle2) { + doDelete("/api/customer/device/" + device.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title2); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/PluginControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/PluginControllerTest.java new file mode 100644 index 00000000000..5d978e36e9b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/PluginControllerTest.java @@ -0,0 +1,232 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.data.rule.RuleMetaData; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class PluginControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + private final ObjectMapper mapper = new ObjectMapper(); + private Tenant savedTenant; + private User tenantAdmin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSavePlugin() throws Exception { + PluginMetaData plugin = new PluginMetaData(); + doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); + plugin.setName("My plugin"); + doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); + plugin.setApiToken("myplugin"); + doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); + plugin.setConfiguration(mapper.readTree("{}")); + doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); + plugin.setClazz(TelemetryStoragePlugin.class.getName()); + PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); + + Assert.assertNotNull(savedPlugin); + Assert.assertNotNull(savedPlugin.getId()); + Assert.assertTrue(savedPlugin.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedPlugin.getTenantId()); + } + + @Test + public void testFindPluginById() throws Exception { + PluginMetaData plugin = new PluginMetaData(); + plugin.setName("My plugin"); + plugin.setApiToken("myplugin"); + plugin.setConfiguration(mapper.readTree("{}")); + plugin.setClazz(TelemetryStoragePlugin.class.getName()); + + PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); + PluginMetaData foundPlugin = doGet("/api/plugin/" + savedPlugin.getId().getId().toString(), PluginMetaData.class); + Assert.assertNotNull(foundPlugin); + Assert.assertEquals(savedPlugin, foundPlugin); + } + + @Test + public void testActivatePlugin() throws Exception { + PluginMetaData plugin = new PluginMetaData(); + plugin.setName("My plugin"); + plugin.setApiToken("myplugin"); + plugin.setConfiguration(mapper.readTree("{}")); + plugin.setClazz(TelemetryStoragePlugin.class.getName()); + + PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); + + doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); + } + + @Test + public void testSuspendPlugin() throws Exception { + PluginMetaData plugin = new PluginMetaData(); + plugin.setName("My plugin"); + plugin.setApiToken("myplugin"); + plugin.setConfiguration(mapper.readTree("{}")); + plugin.setClazz(TelemetryStoragePlugin.class.getName()); + + PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); + + doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); + + RuleMetaData rule = RuleControllerTest.createRuleMetaData(savedPlugin); + RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); + doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk()); + + doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/suspend").andExpect(status().isBadRequest()); + + doPost("/api/rule/" + savedRule.getId().getId().toString() + "/suspend").andExpect(status().isOk()); + + doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/suspend").andExpect(status().isOk()); + } + + @Test + public void testDeletePluginById() throws Exception { + PluginMetaData plugin = new PluginMetaData(); + plugin.setName("My plugin"); + plugin.setApiToken("myplugin"); + plugin.setConfiguration(mapper.readTree("{}")); + plugin.setClazz(TelemetryStoragePlugin.class.getName()); + + PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); + + RuleMetaData rule = RuleControllerTest.createRuleMetaData(savedPlugin); + RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); + + doDelete("/api/plugin/" + savedPlugin.getId().getId()).andExpect(status().isBadRequest()); + + doDelete("/api/rule/" + savedRule.getId().getId()).andExpect(status().isOk()); + + doDelete("/api/plugin/" + savedPlugin.getId().getId()).andExpect(status().isOk()); + doGet("/api/plugin/" + savedPlugin.getId().getId().toString()).andExpect(status().isNotFound()); + } + + @Test + public void testFindPluginByToken() throws Exception { + PluginMetaData plugin = new PluginMetaData(); + plugin.setName("My plugin"); + plugin.setApiToken("myplugin"); + plugin.setConfiguration(mapper.readTree("{}")); + plugin.setClazz(TelemetryStoragePlugin.class.getName()); + + PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); + PluginMetaData foundPlugin = doGet("/api/plugin/token/" + "myplugin", PluginMetaData.class); + Assert.assertNotNull(foundPlugin); + Assert.assertEquals(savedPlugin, foundPlugin); + } + + @Test + public void testFindCurrentTenantPlugins() throws Exception { + List plugins = testPluginsCreation("/api/plugin"); + for (PluginMetaData plugin : plugins) { + doDelete("/api/plugin/" + plugin.getId().getId()).andExpect(status().isOk()); + } + } + + @Test + public void testFindSystemPlugins() throws Exception { + loginSysAdmin(); + List plugins = testPluginsCreation("/api/plugin/system"); + for (PluginMetaData plugin : plugins) { + doDelete("/api/plugin/" + plugin.getId().getId()).andExpect(status().isOk()); + } + } + + private List testPluginsCreation(String url) throws Exception { + List plugins = new ArrayList<>(); + for (int i = 0; i < 111; i++) { + PluginMetaData plugin = new PluginMetaData(); + plugin.setName("My plugin"); + plugin.setApiToken("myplugin" + i); + plugin.setConfiguration(mapper.readTree("{}")); + plugin.setClazz(TelemetryStoragePlugin.class.getName()); + plugins.add(doPost("/api/plugin", plugin, PluginMetaData.class)); + } + + List loadedPlugins = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(23); + TextPageData pageData; + do { + pageData = doGetTypedWithPageLink(url + "?", + new TypeReference>() { + }, pageLink); + loadedPlugins.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + loadedPlugins = loadedPlugins.stream() + .filter(p -> !p.getName().equals("System Telemetry Plugin")) + .filter(p -> !p.getName().equals("Mail Sender Plugin")) + .filter(p -> !p.getName().equals("System RPC Plugin")) + .collect(Collectors.toList()); + + Collections.sort(plugins, idComparator); + Collections.sort(loadedPlugins, idComparator); + + Assert.assertEquals(plugins, loadedPlugins); + return loadedPlugins; + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/RuleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/RuleControllerTest.java new file mode 100644 index 00000000000..ddbc833f081 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/RuleControllerTest.java @@ -0,0 +1,246 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.plugin.PluginMetaData; +import org.thingsboard.server.common.data.rule.RuleMetaData; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class RuleControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + private static final ObjectMapper mapper = new ObjectMapper(); + private Tenant savedTenant; + private User tenantAdmin; + private PluginMetaData sysPlugin; + private PluginMetaData tenantPlugin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + sysPlugin = new PluginMetaData(); + sysPlugin.setName("Sys plugin"); + sysPlugin.setApiToken("sysplugin"); + sysPlugin.setConfiguration(mapper.readTree("{}")); + sysPlugin.setClazz(TelemetryStoragePlugin.class.getName()); + sysPlugin = doPost("/api/plugin", sysPlugin, PluginMetaData.class); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + tenantPlugin = new PluginMetaData(); + tenantPlugin.setName("My plugin"); + tenantPlugin.setApiToken("myplugin"); + tenantPlugin.setConfiguration(mapper.readTree("{}")); + tenantPlugin.setClazz(TelemetryStoragePlugin.class.getName()); + tenantPlugin = doPost("/api/plugin", tenantPlugin, PluginMetaData.class); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + + doDelete("/api/plugin/" + sysPlugin.getId().getId()).andExpect(status().isOk()); + } + + @Test + public void testSaveRule() throws Exception { + RuleMetaData rule = new RuleMetaData(); + doPost("/api/rule", rule).andExpect(status().isBadRequest()); + rule.setName("My Rule"); + doPost("/api/rule", rule).andExpect(status().isBadRequest()); + rule.setPluginToken(tenantPlugin.getApiToken()); + doPost("/api/rule", rule).andExpect(status().isBadRequest()); + rule.setFilters(mapper.readTree("[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", " + + "\"name\":\"TelemetryFilter\", " + + "\"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]")); + doPost("/api/rule", rule).andExpect(status().isBadRequest()); + rule.setAction(mapper.readTree("{\"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", \"configuration\":{}}")); + + RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); + Assert.assertNotNull(savedRule); + Assert.assertNotNull(savedRule.getId()); + Assert.assertTrue(savedRule.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedRule.getTenantId()); + } + + @Test + public void testFindRuleById() throws Exception { + RuleMetaData rule = createRuleMetaData(tenantPlugin); + RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); + + RuleMetaData foundRule = doGet("/api/rule/" + savedRule.getId().getId().toString(), RuleMetaData.class); + Assert.assertNotNull(foundRule); + Assert.assertEquals(savedRule, foundRule); + } + + @Test + public void testFindRuleByPluginToken() throws Exception { + RuleMetaData rule = createRuleMetaData(tenantPlugin); + RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); + + List foundRules = doGetTyped("/api/rule/token/" + savedRule.getPluginToken(), + new TypeReference>() { + }); + Assert.assertNotNull(foundRules); + Assert.assertEquals(1, foundRules.size()); + Assert.assertEquals(savedRule, foundRules.get(0)); + } + + @Test + public void testActivateRule() throws Exception { + RuleMetaData rule = createRuleMetaData(tenantPlugin); + RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); + + doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isBadRequest()); + + doPost("/api/plugin/" + tenantPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); + + doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk()); + } + + @Test + public void testSuspendRule() throws Exception { + RuleMetaData rule = createRuleMetaData(tenantPlugin); + RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); + + doPost("/api/plugin/" + tenantPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); + doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk()); + doPost("/api/rule/" + savedRule.getId().getId().toString() + "/suspend").andExpect(status().isOk()); + } + + @Test + public void testFindSystemRules() throws Exception { + loginSysAdmin(); + List rules = testRulesCreation("/api/rule/system", sysPlugin); + for (RuleMetaData rule : rules) { + doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk()); + } + loginTenantAdmin(); + } + + @Test + public void testFindCurrentTenantPlugins() throws Exception { + List rules = testRulesCreation("/api/rule", tenantPlugin); + for (RuleMetaData rule : rules) { + doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk()); + } + } + + @Test + public void testFindTenantPlugins() throws Exception { + List rules = testRulesCreation("/api/rule", tenantPlugin); + loginSysAdmin(); + List loadedRules = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(3); + TextPageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/rule/tenant/" + savedTenant.getId().getId().toString() + "?", + new TypeReference>() { + }, pageLink); + loadedRules.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(rules, idComparator); + Collections.sort(loadedRules, idComparator); + + Assert.assertEquals(rules, loadedRules); + + for (RuleMetaData rule : rules) { + doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk()); + } + } + + private List testRulesCreation(String url, PluginMetaData plugin) throws Exception { + List rules = new ArrayList<>(); + for (int i = 0; i < 6; i++) { + RuleMetaData rule = createRuleMetaData(plugin); + rule.setPluginToken(plugin.getApiToken()); + rule.setName(rule.getName() + i); + rules.add(doPost("/api/rule", rule, RuleMetaData.class)); + } + + List loadedRules = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(3); + TextPageData pageData; + do { + pageData = doGetTypedWithPageLink(url + "?", + new TypeReference>() { + }, pageLink); + loadedRules.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + loadedRules = loadedRules.stream().filter(p -> !p.getName().equals("System Telemetry Rule")).collect(Collectors.toList()); + + Collections.sort(rules, idComparator); + Collections.sort(loadedRules, idComparator); + + Assert.assertEquals(rules, loadedRules); + return loadedRules; + } + + public static RuleMetaData createRuleMetaData(PluginMetaData plugin) throws IOException { + RuleMetaData rule = new RuleMetaData(); + rule.setName("My Rule"); + rule.setPluginToken(plugin.getApiToken()); + rule.setFilters(mapper.readTree("[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", " + + "\"name\":\"TelemetryFilter\", " + + "\"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]")); + rule.setAction(mapper.readTree("{\"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", \"configuration\":{}}")); + return rule; + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java new file mode 100644 index 00000000000..03f2d69839b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java @@ -0,0 +1,220 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.junit.Assert; +import org.junit.Test; + +import com.fasterxml.jackson.core.type.TypeReference; + +public class TenantControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + @Test + public void testSaveTenant() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + Assert.assertNotNull(savedTenant.getId()); + Assert.assertTrue(savedTenant.getCreatedTime() > 0); + Assert.assertEquals(tenant.getTitle(), savedTenant.getTitle()); + savedTenant.setTitle("My new tenant"); + doPost("/api/tenant", savedTenant, Tenant.class); + Tenant foundTenant = doGet("/api/tenant/"+savedTenant.getId().getId().toString(), Tenant.class); + Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle()); + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindTenantById() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Tenant foundTenant = doGet("/api/tenant/"+savedTenant.getId().getId().toString(), Tenant.class); + Assert.assertNotNull(foundTenant); + Assert.assertEquals(savedTenant, foundTenant); + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveTenantWithEmptyTitle() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); + doPost("/api/tenant", tenant) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Tenant title should be specified"))); + } + + @Test + public void testSaveTenantWithInvalidEmail() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + tenant.setEmail("invalid@mail"); + doPost("/api/tenant", tenant) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Invalid email address format"))); + } + + @Test + public void testDeleteTenant() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + doGet("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testFindTenants() throws Exception { + loginSysAdmin(); + List tenants = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(17); + TextPageData pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(1, pageData.getData().size()); + tenants.addAll(pageData.getData()); + + for (int i=0;i<56;i++) { + Tenant tenant = new Tenant(); + tenant.setTitle("Tenant"+i); + tenants.add(doPost("/api/tenant", tenant, Tenant.class)); + } + + List loadedTenants = new ArrayList<>(); + pageLink = new TextPageLink(17); + do { + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + loadedTenants.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(tenants, idComparator); + Collections.sort(loadedTenants, idComparator); + + Assert.assertEquals(tenants, loadedTenants); + + for (Tenant tenant : loadedTenants) { + if (!tenant.getTitle().equals("Tenant")) { + doDelete("/api/tenant/"+tenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + } + + pageLink = new TextPageLink(17); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(1, pageData.getData().size()); + } + + @Test + public void testFindTenantsByTitle() throws Exception { + loginSysAdmin(); + String title1 = "Tenant title 1"; + List tenantsTitle1 = new ArrayList<>(); + for (int i=0;i<134;i++) { + Tenant tenant = new Tenant(); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title1+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + tenant.setTitle(title); + tenantsTitle1.add(doPost("/api/tenant", tenant, Tenant.class)); + } + String title2 = "Tenant title 2"; + List tenantsTitle2 = new ArrayList<>(); + for (int i=0;i<127;i++) { + Tenant tenant = new Tenant(); + String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); + String title = title2+suffix; + title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); + tenant.setTitle(title); + tenantsTitle2.add(doPost("/api/tenant", tenant, Tenant.class)); + } + + List loadedTenantsTitle1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(15, title1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + loadedTenantsTitle1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(tenantsTitle1, idComparator); + Collections.sort(loadedTenantsTitle1, idComparator); + + Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1); + + List loadedTenantsTitle2 = new ArrayList<>(); + pageLink = new TextPageLink(4, title2); + do { + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + loadedTenantsTitle2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(tenantsTitle2, idComparator); + Collections.sort(loadedTenantsTitle2, idComparator); + + Assert.assertEquals(tenantsTitle2, loadedTenantsTitle2); + + for (Tenant tenant : loadedTenantsTitle1) { + doDelete("/api/tenant/"+tenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title1); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (Tenant tenant : loadedTenantsTitle2) { + doDelete("/api/tenant/"+tenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, title2); + pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java new file mode 100644 index 00000000000..a48630c5876 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java @@ -0,0 +1,618 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.service.mail.TestMailService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +public class UserControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + @Test + public void testSaveUser() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + String email = "tenant2@thingsboard.org"; + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(savedTenant.getId()); + user.setEmail(email); + user.setFirstName("Joe"); + user.setLastName("Downs"); + User savedUser = doPost("/api/user", user, User.class); + Assert.assertNotNull(savedUser); + Assert.assertNotNull(savedUser.getId()); + Assert.assertTrue(savedUser.getCreatedTime() > 0); + Assert.assertEquals(user.getEmail(), savedUser.getEmail()); + + User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); + Assert.assertEquals(foundUser, savedUser); + + logout(); + doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) + .andExpect(status().isPermanentRedirect()) + .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); + + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", "activateToken", TestMailService.currentActivateToken, "password", "testPassword").andExpect(status().isOk()), JsonNode.class); + validateAndSetJwtToken(tokenInfo, email); + + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(email))); + + logout(); + + login(email, "testPassword"); + + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(email))); + + loginSysAdmin(); + doDelete("/api/user/"+savedUser.getId().getId().toString()) + .andExpect(status().isOk()); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testResetPassword() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + String email = "tenant2@thingsboard.org"; + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(savedTenant.getId()); + user.setEmail(email); + user.setFirstName("Joe"); + user.setLastName("Downs"); + + User savedUser = createUserAndLogin(user, "testPassword1"); + logout(); + doPost("/api/noauth/resetPasswordByEmail", "email", email) + .andExpect(status().isOk()); + doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) + .andExpect(status().isPermanentRedirect()) + .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); + + JsonNode tokenInfo = readResponse(doPost("/api/noauth/resetPassword", "resetToken", TestMailService.currentResetPasswordToken, "password", "testPassword2").andExpect(status().isOk()), JsonNode.class); + validateAndSetJwtToken(tokenInfo, email); + + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(email))); + + logout(); + + login(email, "testPassword2"); + doGet("/api/auth/user") + .andExpect(status().isOk()) + .andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) + .andExpect(jsonPath("$.email",is(email))); + + loginSysAdmin(); + doDelete("/api/user/"+savedUser.getId().getId().toString()) + .andExpect(status().isOk()); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindUserById() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + String email = "tenant2@thingsboard.org"; + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(savedTenant.getId()); + user.setEmail(email); + user.setFirstName("Joe"); + user.setLastName("Downs"); + + User savedUser = doPost("/api/user", user, User.class); + User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); + Assert.assertNotNull(foundUser); + Assert.assertEquals(savedUser, foundUser); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveUserWithSameEmail() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + String email = "tenant@thingsboard.org"; + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(savedTenant.getId()); + user.setEmail(email); + user.setFirstName("Joe"); + user.setLastName("Downs"); + + doPost("/api/user", user) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("User with email '" + email + "' already present in database"))); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveUserWithInvalidEmail() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + String email = "tenant_thingsboard.org"; + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(savedTenant.getId()); + user.setEmail(email); + user.setFirstName("Joe"); + user.setLastName("Downs"); + + doPost("/api/user", user) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Invalid email address format '" + email + "'"))); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveUserWithEmptyEmail() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(savedTenant.getId()); + user.setFirstName("Joe"); + user.setLastName("Downs"); + + doPost("/api/user", user) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("User email should be specified"))); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveUserWithoutTenant() throws Exception { + loginSysAdmin(); + + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setEmail("tenant2@thingsboard.org"); + user.setFirstName("Joe"); + user.setLastName("Downs"); + + doPost("/api/user", user) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Tenant administrator should be assigned to tenant"))); + } + + @Test + public void testDeleteUser() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + String email = "tenant2@thingsboard.org"; + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(savedTenant.getId()); + user.setEmail(email); + user.setFirstName("Joe"); + user.setLastName("Downs"); + + User savedUser = doPost("/api/user", user, User.class); + User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); + Assert.assertNotNull(foundUser); + + doDelete("/api/user/"+savedUser.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/user/"+savedUser.getId().getId().toString()) + .andExpect(status().isNotFound()); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindTenantAdmins() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + TenantId tenantId = savedTenant.getId(); + + List tenantAdmins = new ArrayList<>(); + for (int i=0;i<64;i++) { + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(tenantId); + user.setEmail("testTenant" + i + "@thingsboard.org"); + tenantAdmins.add(doPost("/api/user", user, User.class)); + } + + List loadedTenantAdmins = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(33); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + loadedTenantAdmins.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(tenantAdmins, idComparator); + Collections.sort(loadedTenantAdmins, idComparator); + + Assert.assertEquals(tenantAdmins, loadedTenantAdmins); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + + pageLink = new TextPageLink(33); + pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertTrue(pageData.getData().isEmpty()); + } + + @Test + public void testFindTenantAdminsByEmail() throws Exception { + + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + TenantId tenantId = savedTenant.getId(); + + String email1 = "testEmail1"; + List tenantAdminsEmail1 = new ArrayList<>(); + + for (int i=0;i<124;i++) { + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(tenantId); + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); + String email = email1+suffix+ "@thingsboard.org"; + email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); + user.setEmail(email); + tenantAdminsEmail1.add(doPost("/api/user", user, User.class)); + } + + String email2 = "testEmail2"; + List tenantAdminsEmail2 = new ArrayList<>(); + + for (int i=0;i<112;i++) { + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setTenantId(tenantId); + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); + String email = email2+suffix+ "@thingsboard.org"; + email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); + user.setEmail(email); + tenantAdminsEmail2.add(doPost("/api/user", user, User.class)); + } + + List loadedTenantAdminsEmail1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(33, email1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + loadedTenantAdminsEmail1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(tenantAdminsEmail1, idComparator); + Collections.sort(loadedTenantAdminsEmail1, idComparator); + + Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1); + + List loadedTenantAdminsEmail2 = new ArrayList<>(); + pageLink = new TextPageLink(16, email2); + do { + pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + loadedTenantAdminsEmail2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(tenantAdminsEmail2, idComparator); + Collections.sort(loadedTenantAdminsEmail2, idComparator); + + Assert.assertEquals(tenantAdminsEmail2, loadedTenantAdminsEmail2); + + for (User user : loadedTenantAdminsEmail1) { + doDelete("/api/user/"+user.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, email1); + pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (User user : loadedTenantAdminsEmail2) { + doDelete("/api/user/"+user.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, email2); + pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindCustomerUsers() throws Exception { + + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + TenantId tenantId = savedTenant.getId(); + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(tenantId); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + CustomerId customerId = savedCustomer.getId(); + + List customerUsers = new ArrayList<>(); + for (int i=0;i<56;i++) { + User user = new User(); + user.setAuthority(Authority.CUSTOMER_USER); + user.setCustomerId(customerId); + user.setEmail("testCustomer" + i + "@thingsboard.org"); + customerUsers.add(doPost("/api/user", user, User.class)); + } + + List loadedCustomerUsers = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(33); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + loadedCustomerUsers.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(customerUsers, idComparator); + Collections.sort(loadedCustomerUsers, idComparator); + + Assert.assertEquals(customerUsers, loadedCustomerUsers); + + doDelete("/api/customer/"+customerId.getId().toString()) + .andExpect(status().isOk()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testFindCustomerUsersByEmail() throws Exception { + + loginSysAdmin(); + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + TenantId tenantId = savedTenant.getId(); + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(tenantId); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Customer customer = new Customer(); + customer.setTitle("My customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + CustomerId customerId = savedCustomer.getId(); + + String email1 = "testEmail1"; + List customerUsersEmail1 = new ArrayList<>(); + + for (int i=0;i<74;i++) { + User user = new User(); + user.setAuthority(Authority.CUSTOMER_USER); + user.setCustomerId(customerId); + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); + String email = email1+suffix+ "@thingsboard.org"; + email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); + user.setEmail(email); + customerUsersEmail1.add(doPost("/api/user", user, User.class)); + } + + String email2 = "testEmail2"; + List customerUsersEmail2 = new ArrayList<>(); + + for (int i=0;i<92;i++) { + User user = new User(); + user.setAuthority(Authority.CUSTOMER_USER); + user.setCustomerId(customerId); + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); + String email = email2+suffix+ "@thingsboard.org"; + email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); + user.setEmail(email); + customerUsersEmail2.add(doPost("/api/user", user, User.class)); + } + + List loadedCustomerUsersEmail1 = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(33, email1); + TextPageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + loadedCustomerUsersEmail1.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(customerUsersEmail1, idComparator); + Collections.sort(loadedCustomerUsersEmail1, idComparator); + + Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1); + + List loadedCustomerUsersEmail2 = new ArrayList<>(); + pageLink = new TextPageLink(16, email2); + do { + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + loadedCustomerUsersEmail2.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(customerUsersEmail2, idComparator); + Collections.sort(loadedCustomerUsersEmail2, idComparator); + + Assert.assertEquals(customerUsersEmail2, loadedCustomerUsersEmail2); + + for (User user : loadedCustomerUsersEmail1) { + doDelete("/api/user/"+user.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, email1); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + for (User user : loadedCustomerUsersEmail2) { + doDelete("/api/user/"+user.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(4, email2); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", + new TypeReference>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(0, pageData.getData().size()); + + doDelete("/api/customer/"+customerId.getId().toString()) + .andExpect(status().isOk()); + + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java new file mode 100644 index 00000000000..3a650553617 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java @@ -0,0 +1,233 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetsBundle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class WidgetTypeControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + private Tenant savedTenant; + private WidgetsBundle savedWidgetsBundle; + private User tenantAdmin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("My widgets bundle"); + savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveWidgetType() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + + Assert.assertNotNull(savedWidgetType); + Assert.assertNotNull(savedWidgetType.getId()); + Assert.assertNotNull(savedWidgetType.getAlias()); + Assert.assertTrue(savedWidgetType.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedWidgetType.getTenantId()); + Assert.assertEquals(widgetType.getName(), savedWidgetType.getName()); + Assert.assertEquals(widgetType.getDescriptor(), savedWidgetType.getDescriptor()); + Assert.assertEquals(savedWidgetsBundle.getAlias(), savedWidgetType.getBundleAlias()); + + savedWidgetType.setName("New Widget Type"); + + doPost("/api/widgetType", savedWidgetType, WidgetType.class); + + WidgetType foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetType.class); + Assert.assertEquals(foundWidgetType.getName(), savedWidgetType.getName()); + } + + @Test + public void testFindWidgetTypeById() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetType foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetType.class); + Assert.assertNotNull(foundWidgetType); + Assert.assertEquals(savedWidgetType, foundWidgetType); + } + + @Test + public void testDeleteWidgetType() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + + doDelete("/api/widgetType/"+savedWidgetType.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/widgetType/"+savedWidgetType.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveWidgetTypeWithEmptyName() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + doPost("/api/widgetType", widgetType) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Widgets type name should be specified"))); + } + + @Test + public void testSaveWidgetTypeWithEmptyBundleAlias() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + doPost("/api/widgetType", widgetType) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Widgets type bundle alias should be specified"))); + } + + @Test + public void testSaveWidgetTypeWithEmptyDescriptor() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{}", JsonNode.class)); + doPost("/api/widgetType", widgetType) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Widgets type descriptor can't be empty"))); + } + + @Test + public void testSaveWidgetTypeWithInvalidBundleAlias() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias("some_alias"); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + doPost("/api/widgetType", widgetType) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Widget type is referencing to non-existent widgets bundle"))); + } + + @Test + public void testUpdateWidgetTypeBundleAlias() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + savedWidgetType.setBundleAlias("some_alias"); + doPost("/api/widgetType", savedWidgetType) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Update of widget type bundle alias is prohibited"))); + + } + + @Test + public void testUpdateWidgetTypeAlias() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + savedWidgetType.setAlias("some_alias"); + doPost("/api/widgetType", savedWidgetType) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Update of widget type alias is prohibited"))); + + } + + @Test + public void testGetBundleWidgetTypes() throws Exception { + List widgetTypes = new ArrayList<>(); + for (int i=0;i<89;i++) { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type " + i); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + widgetTypes.add(doPost("/api/widgetType", widgetType, WidgetType.class)); + } + + List loadedWidgetTypes = doGetTyped("/api/widgetTypes?isSystem={isSystem}&bundleAlias={bundleAlias}", + new TypeReference>(){}, false, savedWidgetsBundle.getAlias()); + + Collections.sort(widgetTypes, idComparator); + Collections.sort(loadedWidgetTypes, idComparator); + + Assert.assertEquals(widgetTypes, loadedWidgetTypes); + } + + @Test + public void testGetWidgetType() throws Exception { + WidgetType widgetType = new WidgetType(); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetType.setName("Widget Type"); + widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class)); + WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + WidgetType foundWidgetType = doGet("/api/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}", + WidgetType.class, false, savedWidgetsBundle.getAlias(), savedWidgetType.getAlias()); + Assert.assertNotNull(foundWidgetType); + Assert.assertEquals(savedWidgetType, foundWidgetType); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java new file mode 100644 index 00000000000..d558188d10e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/WidgetsBundleControllerTest.java @@ -0,0 +1,316 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.widget.WidgetsBundle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class WidgetsBundleControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + private Tenant savedTenant; + private User tenantAdmin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveWidgetsBundle() throws Exception { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("My widgets bundle"); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + + Assert.assertNotNull(savedWidgetsBundle); + Assert.assertNotNull(savedWidgetsBundle.getId()); + Assert.assertNotNull(savedWidgetsBundle.getAlias()); + Assert.assertTrue(savedWidgetsBundle.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedWidgetsBundle.getTenantId()); + Assert.assertEquals(widgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); + + savedWidgetsBundle.setTitle("My new widgets bundle"); + doPost("/api/widgetsBundle", savedWidgetsBundle, WidgetsBundle.class); + + WidgetsBundle foundWidgetsBundle = doGet("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString(), WidgetsBundle.class); + Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); + } + + @Test + public void testFindWidgetsBundleById() throws Exception { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("My widgets bundle"); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + WidgetsBundle foundWidgetsBundle = doGet("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString(), WidgetsBundle.class); + Assert.assertNotNull(foundWidgetsBundle); + Assert.assertEquals(savedWidgetsBundle, foundWidgetsBundle); + } + + @Test + public void testDeleteWidgetsBundle() throws Exception { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("My widgets bundle"); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + + doDelete("/api/widgetsBundle/"+savedWidgetsBundle.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/widgetsBundle/"+savedWidgetsBundle.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveWidgetsBundleWithEmptyTitle() throws Exception { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + doPost("/api/widgetsBundle", widgetsBundle) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Widgets bundle title should be specified"))); + } + + @Test + public void testUpdateWidgetsBundleAlias() throws Exception { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("My widgets bundle"); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + savedWidgetsBundle.setAlias("new_alias"); + doPost("/api/widgetsBundle", savedWidgetsBundle) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Update of widgets bundle alias is prohibited"))); + + } + + @Test + public void testFindTenantWidgetsBundlesByPageLink() throws Exception { + + login(tenantAdmin.getEmail(), "testPassword1"); + + List sysWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + + List widgetsBundles = new ArrayList<>(); + for (int i=0;i<73;i++) { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("Widgets bundle"+i); + widgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class)); + } + + widgetsBundles.addAll(sysWidgetsBundles); + + List loadedWidgetsBundles = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(14); + TextPageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/widgetsBundles?", + new TypeReference>(){}, pageLink); + loadedWidgetsBundles.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(widgetsBundles, idComparator); + Collections.sort(loadedWidgetsBundles, idComparator); + + Assert.assertEquals(widgetsBundles, loadedWidgetsBundles); + } + + @Test + public void testFindSystemWidgetsBundlesByPageLink() throws Exception { + + loginSysAdmin(); + + List sysWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + List createdWidgetsBundles = new ArrayList<>(); + for (int i=0;i<120;i++) { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("Widgets bundle"+i); + createdWidgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class)); + } + + List widgetsBundles = new ArrayList<>(createdWidgetsBundles); + widgetsBundles.addAll(sysWidgetsBundles); + + List loadedWidgetsBundles = new ArrayList<>(); + TextPageLink pageLink = new TextPageLink(14); + TextPageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/widgetsBundles?", + new TypeReference>(){}, pageLink); + loadedWidgetsBundles.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(widgetsBundles, idComparator); + Collections.sort(loadedWidgetsBundles, idComparator); + + Assert.assertEquals(widgetsBundles, loadedWidgetsBundles); + + for (WidgetsBundle widgetsBundle : createdWidgetsBundles) { + doDelete("/api/widgetsBundle/"+widgetsBundle.getId().getId().toString()) + .andExpect(status().isOk()); + } + + pageLink = new TextPageLink(17); + loadedWidgetsBundles.clear(); + do { + pageData = doGetTypedWithPageLink("/api/widgetsBundles?", + new TypeReference>(){}, pageLink); + loadedWidgetsBundles.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(sysWidgetsBundles, idComparator); + Collections.sort(loadedWidgetsBundles, idComparator); + + Assert.assertEquals(sysWidgetsBundles, loadedWidgetsBundles); + } + + + @Test + public void testFindTenantWidgetsBundles() throws Exception { + + login(tenantAdmin.getEmail(), "testPassword1"); + + List sysWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + List widgetsBundles = new ArrayList<>(); + for (int i=0;i<73;i++) { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("Widgets bundle"+i); + widgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class)); + } + + widgetsBundles.addAll(sysWidgetsBundles); + + List loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + Collections.sort(widgetsBundles, idComparator); + Collections.sort(loadedWidgetsBundles, idComparator); + + Assert.assertEquals(widgetsBundles, loadedWidgetsBundles); + } + + @Test + public void testFindSystemAndTenantWidgetsBundles() throws Exception { + + loginSysAdmin(); + + + List sysWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + List createdSystemWidgetsBundles = new ArrayList<>(); + for (int i=0;i<82;i++) { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("Sys widgets bundle"+i); + createdSystemWidgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class)); + } + + List systemWidgetsBundles = new ArrayList<>(createdSystemWidgetsBundles); + systemWidgetsBundles.addAll(sysWidgetsBundles); + + List widgetsBundles = new ArrayList<>(); + widgetsBundles.addAll(systemWidgetsBundles); + + login(tenantAdmin.getEmail(), "testPassword1"); + + for (int i=0;i<127;i++) { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("Tenant widgets bundle"+i); + widgetsBundles.add(doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class)); + } + + List loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + Collections.sort(widgetsBundles, idComparator); + Collections.sort(loadedWidgetsBundles, idComparator); + + Assert.assertEquals(widgetsBundles, loadedWidgetsBundles); + + loginSysAdmin(); + + loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + Collections.sort(systemWidgetsBundles, idComparator); + Collections.sort(loadedWidgetsBundles, idComparator); + + Assert.assertEquals(systemWidgetsBundles, loadedWidgetsBundles); + + for (WidgetsBundle widgetsBundle : createdSystemWidgetsBundles) { + doDelete("/api/widgetsBundle/"+widgetsBundle.getId().getId().toString()) + .andExpect(status().isOk()); + } + + loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?", + new TypeReference>(){}); + + Collections.sort(sysWidgetsBundles, idComparator); + Collections.sort(loadedWidgetsBundles, idComparator); + + Assert.assertEquals(sysWidgetsBundles, loadedWidgetsBundles); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java b/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java new file mode 100644 index 00000000000..6ba02076baf --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java @@ -0,0 +1,57 @@ +/** + * Copyright © 2016 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.mail; + +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.thingsboard.server.exception.ThingsboardException; + +@Profile("test") +@Configuration +public class TestMailService { + + public static String currentActivateToken; + public static String currentResetPasswordToken; + + @Bean + @Primary + public MailService mailService() throws ThingsboardException { + MailService mailService = Mockito.mock(MailService.class); + Mockito.doAnswer(new Answer() { + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + String activationLink = (String) args[0]; + currentActivateToken = activationLink.split("=")[1]; + return null; + } + }).when(mailService).sendActivationEmail(Mockito.anyString(), Mockito.anyString()); + Mockito.doAnswer(new Answer() { + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + String passwordResetLink = (String) args[0]; + currentResetPasswordToken = passwordResetLink.split("=")[1]; + return null; + } + }).when(mailService).sendResetPasswordEmail(Mockito.anyString(), Mockito.anyString()); + return mailService; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java b/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java new file mode 100644 index 00000000000..10d41be9380 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016 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.system; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.controller.AbstractControllerTest; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Andrew Shvayka + */ +public class HttpDeviceApiTest extends AbstractControllerTest { + + private static final AtomicInteger idSeq = new AtomicInteger(new Random(System.currentTimeMillis()).nextInt()); + + protected Device device; + protected DeviceCredentials deviceCredentials; + + @Before + public void before() throws Exception { + loginTenantAdmin(); + device = new Device(); + device.setName("My device"); + device = doPost("/api/device", device, Device.class); + + deviceCredentials = + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); + } + + @Test + public void testGetAttributes() throws Exception { + doGetAsync("/api/v1/" + "WRONG_TOKEN" + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isUnauthorized()); + doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isNotFound()); + + Map attrMap = new HashMap<>(); + attrMap.put("keyA", "valueA"); + mockMvc.perform( + asyncDispatch(doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes", attrMap, new String[]{}).andReturn())) + .andExpect(status().isOk()); + doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk()); + } + + protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception { + MockHttpServletRequestBuilder getRequest; + getRequest = get(urlTemplate, urlVariables); + setJwtToken(getRequest); + return mockMvc.perform(asyncDispatch(mockMvc.perform(getRequest).andExpect(request().asyncStarted()).andReturn())); + } + + protected ResultActions doPostAsync(String urlTemplate, Object... urlVariables) throws Exception { + MockHttpServletRequestBuilder getRequest = post(urlTemplate, urlVariables); + setJwtToken(getRequest); + return mockMvc.perform(asyncDispatch(mockMvc.perform(getRequest).andExpect(request().asyncStarted()).andReturn())); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/system/SystemTestSuite.java b/application/src/test/java/org/thingsboard/server/system/SystemTestSuite.java new file mode 100644 index 00000000000..9d565f7249e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/system/SystemTestSuite.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 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.system; + +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; +import org.junit.ClassRule; +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.runner.RunWith; +import org.thingsboard.server.dao.CustomCassandraCQLUnit; + +import java.util.Arrays; + +/** + * @author Andrew Shvayka + */ +@RunWith(ClasspathSuite.class) +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.system.*Test"}) +public class SystemTestSuite { + + @ClassRule + public static CustomCassandraCQLUnit cassandraUnit = + new CustomCassandraCQLUnit(Arrays.asList( + new ClassPathCQLDataSet("schema.cql", false, false), + new ClassPathCQLDataSet("system-data.cql", false, false)), + "cassandra-test.yaml", 30000l); +} diff --git a/application/src/test/resources/logback.xml b/application/src/test/resources/logback.xml new file mode 100644 index 00000000000..f32acec015c --- /dev/null +++ b/application/src/test/resources/logback.xml @@ -0,0 +1,21 @@ + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + diff --git a/common/data/pom.xml b/common/data/pom.xml new file mode 100644 index 00000000000..b458ef1e37f --- /dev/null +++ b/common/data/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + org.thingsboard.server + 0.0.1-SNAPSHOT + common + + org.thingsboard.server.common + data + jar + + Thingsboard Server Common Data + http://thingsboard.org + + + UTF-8 + ${basedir}/../.. + + + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + com.fasterxml.jackson.core + jackson-databind + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + + + + + + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java new file mode 100644 index 00000000000..c91e4ee379e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java @@ -0,0 +1,105 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import org.thingsboard.server.common.data.id.AdminSettingsId; + +import com.fasterxml.jackson.databind.JsonNode; + +public class AdminSettings extends BaseData { + + private static final long serialVersionUID = -7670322981725511892L; + + private String key; + private JsonNode jsonValue; + + public AdminSettings() { + super(); + } + + public AdminSettings(AdminSettingsId id) { + super(id); + } + + public AdminSettings(AdminSettings adminSettings) { + super(adminSettings); + this.key = adminSettings.getKey(); + this.jsonValue = adminSettings.getJsonValue(); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public JsonNode getJsonValue() { + return jsonValue; + } + + public void setJsonValue(JsonNode jsonValue) { + this.jsonValue = jsonValue; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((jsonValue == null) ? 0 : jsonValue.hashCode()); + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + AdminSettings other = (AdminSettings) obj; + if (jsonValue == null) { + if (other.jsonValue != null) + return false; + } else if (!jsonValue.equals(other.jsonValue)) + return false; + if (key == null) { + if (other.key != null) + return false; + } else if (!key.equals(other.key)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AdminSettings [key="); + builder.append(key); + builder.append(", jsonValue="); + builder.append(jsonValue); + builder.append(", createdTime="); + builder.append(createdTime); + builder.append(", id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java new file mode 100644 index 00000000000..452efa02029 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java @@ -0,0 +1,84 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import java.io.Serializable; + +import org.thingsboard.server.common.data.id.IdBased; +import org.thingsboard.server.common.data.id.UUIDBased; + +public abstract class BaseData extends IdBased implements Serializable { + + private static final long serialVersionUID = 5422817607129962637L; + + protected long createdTime; + + public BaseData() { + super(); + } + + public BaseData(I id) { + super(id); + } + + public BaseData(BaseData data) { + super(data.getId()); + this.createdTime = data.getCreatedTime(); + } + + public long getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(long createdTime) { + this.createdTime = createdTime; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (int) (createdTime ^ (createdTime >>> 32)); + return result; + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + BaseData other = (BaseData) obj; + if (createdTime != other.createdTime) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BaseData [createdTime="); + builder.append(createdTime); + builder.append(", id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java new file mode 100644 index 00000000000..ae62f419586 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public class CacheConstants { + public static final String DEVICE_CREDENTIALS_CACHE = "deviceCredentials"; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java new file mode 100644 index 00000000000..397435e95ab --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ContactBased.java @@ -0,0 +1,185 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import org.thingsboard.server.common.data.id.UUIDBased; + +public abstract class ContactBased extends SearchTextBased { + + private static final long serialVersionUID = 5047448057830660988L; + + protected String country; + protected String state; + protected String city; + protected String address; + protected String address2; + protected String zip; + protected String phone; + protected String email; + + public ContactBased() { + super(); + } + + public ContactBased(I id) { + super(id); + } + + public ContactBased(ContactBased contact) { + super(contact); + this.country = contact.getCountry(); + this.state = contact.getState(); + this.city = contact.getCity(); + this.address = contact.getAddress(); + this.address2 = contact.getAddress2(); + this.zip = contact.getZip(); + this.phone = contact.getPhone(); + this.email = contact.getEmail(); + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getAddress2() { + return address2; + } + + public void setAddress2(String address2) { + this.address2 = address2; + } + + public String getZip() { + return zip; + } + + public void setZip(String zip) { + this.zip = zip; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((address == null) ? 0 : address.hashCode()); + result = prime * result + ((address2 == null) ? 0 : address2.hashCode()); + result = prime * result + ((city == null) ? 0 : city.hashCode()); + result = prime * result + ((country == null) ? 0 : country.hashCode()); + result = prime * result + ((email == null) ? 0 : email.hashCode()); + result = prime * result + ((phone == null) ? 0 : phone.hashCode()); + result = prime * result + ((state == null) ? 0 : state.hashCode()); + result = prime * result + ((zip == null) ? 0 : zip.hashCode()); + return result; + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + ContactBased other = (ContactBased) obj; + if (address == null) { + if (other.address != null) + return false; + } else if (!address.equals(other.address)) + return false; + if (address2 == null) { + if (other.address2 != null) + return false; + } else if (!address2.equals(other.address2)) + return false; + if (city == null) { + if (other.city != null) + return false; + } else if (!city.equals(other.city)) + return false; + if (country == null) { + if (other.country != null) + return false; + } else if (!country.equals(other.country)) + return false; + if (email == null) { + if (other.email != null) + return false; + } else if (!email.equals(other.email)) + return false; + if (phone == null) { + if (other.phone != null) + return false; + } else if (!phone.equals(other.phone)) + return false; + if (state == null) { + if (other.state != null) + return false; + } else if (!state.equals(other.state)) + return false; + if (zip == null) { + if (other.zip != null) + return false; + } else if (!zip.equals(other.zip)) + return false; + return true; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java new file mode 100644 index 00000000000..3d06bdc49ec --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java @@ -0,0 +1,145 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; + +import com.fasterxml.jackson.databind.JsonNode; + +public class Customer extends ContactBased{ + + private static final long serialVersionUID = -1599722990298929275L; + + private String title; + private TenantId tenantId; + private JsonNode additionalInfo; + + public Customer() { + super(); + } + + public Customer(CustomerId id) { + super(id); + } + + public Customer(Customer customer) { + super(customer); + this.tenantId = customer.getTenantId(); + this.title = customer.getTitle(); + this.additionalInfo = customer.getAdditionalInfo(); + } + + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public JsonNode getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(JsonNode additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public String getSearchText() { + return title; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode()); + result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); + result = prime * result + ((title == null) ? 0 : title.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Customer other = (Customer) obj; + if (additionalInfo == null) { + if (other.additionalInfo != null) + return false; + } else if (!additionalInfo.equals(other.additionalInfo)) + return false; + if (tenantId == null) { + if (other.tenantId != null) + return false; + } else if (!tenantId.equals(other.tenantId)) + return false; + if (title == null) { + if (other.title != null) + return false; + } else if (!title.equals(other.title)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Customer [title="); + builder.append(title); + builder.append(", tenantId="); + builder.append(tenantId); + builder.append(", additionalInfo="); + builder.append(additionalInfo); + builder.append(", country="); + builder.append(country); + builder.append(", state="); + builder.append(state); + builder.append(", city="); + builder.append(city); + builder.append(", address="); + builder.append(address); + builder.append(", address2="); + builder.append(address2); + builder.append(", zip="); + builder.append(zip); + builder.append(", phone="); + builder.append(phone); + builder.append(", email="); + builder.append(email); + builder.append(", createdTime="); + builder.append(createdTime); + builder.append(", id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java new file mode 100644 index 00000000000..72879c96813 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java @@ -0,0 +1,144 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; + +import com.fasterxml.jackson.databind.JsonNode; + +public class Dashboard extends SearchTextBased { + + private static final long serialVersionUID = 872682138346187503L; + + private TenantId tenantId; + private CustomerId customerId; + private String title; + private JsonNode configuration; + + public Dashboard() { + super(); + } + + public Dashboard(DashboardId id) { + super(id); + } + + public Dashboard(Dashboard dashboard) { + super(dashboard); + this.tenantId = dashboard.getTenantId(); + this.customerId = dashboard.getCustomerId(); + this.title = dashboard.getTitle(); + this.configuration = dashboard.getConfiguration(); + } + + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public CustomerId getCustomerId() { + return customerId; + } + + public void setCustomerId(CustomerId customerId) { + this.customerId = customerId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public JsonNode getConfiguration() { + return configuration; + } + + public void setConfiguration(JsonNode configuration) { + this.configuration = configuration; + } + + @Override + public String getSearchText() { + return title; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((configuration == null) ? 0 : configuration.hashCode()); + result = prime * result + ((customerId == null) ? 0 : customerId.hashCode()); + result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); + result = prime * result + ((title == null) ? 0 : title.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Dashboard other = (Dashboard) obj; + if (configuration == null) { + if (other.configuration != null) + return false; + } else if (!configuration.equals(other.configuration)) + return false; + if (customerId == null) { + if (other.customerId != null) + return false; + } else if (!customerId.equals(other.customerId)) + return false; + if (tenantId == null) { + if (other.tenantId != null) + return false; + } else if (!tenantId.equals(other.tenantId)) + return false; + if (title == null) { + if (other.title != null) + return false; + } else if (!title.equals(other.title)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Dashboard [tenantId="); + builder.append(tenantId); + builder.append(", customerId="); + builder.append(customerId); + builder.append(", title="); + builder.append(title); + builder.append(", configuration="); + builder.append(configuration); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java new file mode 100644 index 00000000000..34ae9b21104 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +/** + * @author Andrew Shvayka + */ +public class DataConstants { + + public static final String SYSTEM = "SYSTEM"; + public static final String TENANT = "TENANT"; + public static final String CUSTOMER = "CUSTOMER"; + public static final String DEVICE = "DEVICE"; + + public static final String CLIENT_SCOPE = "CLIENT_SCOPE"; + public static final String SERVER_SCOPE = "SERVER_SCOPE"; + public static final String SHARED_SCOPE = "SHARED_SCOPE"; + + public static final String ALARM = "ALARM"; + public static final String ERROR = "ERROR"; + public static final String LC_EVENT = "LC_EVENT"; + public static final String STATS = "STATS"; + + public static final String ONEWAY = "ONEWAY"; + public static final String TWOWAY = "TWOWAY"; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java new file mode 100644 index 00000000000..aece6912cd2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -0,0 +1,148 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +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 com.fasterxml.jackson.databind.JsonNode; + +public class Device extends SearchTextBased { + + private static final long serialVersionUID = 2807343040519543363L; + + private TenantId tenantId; + private CustomerId customerId; + private String name; + private JsonNode additionalInfo; + + public Device() { + super(); + } + + public Device(DeviceId id) { + super(id); + } + + public Device(Device device) { + super(device); + this.tenantId = device.getTenantId(); + this.customerId = device.getCustomerId(); + this.name = device.getName(); + this.additionalInfo = device.getAdditionalInfo(); + } + + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public CustomerId getCustomerId() { + return customerId; + } + + public void setCustomerId(CustomerId customerId) { + this.customerId = customerId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public JsonNode getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(JsonNode additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public String getSearchText() { + return name; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode()); + result = prime * result + ((customerId == null) ? 0 : customerId.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Device other = (Device) obj; + if (additionalInfo == null) { + if (other.additionalInfo != null) + return false; + } else if (!additionalInfo.equals(other.additionalInfo)) + return false; + if (customerId == null) { + if (other.customerId != null) + return false; + } else if (!customerId.equals(other.customerId)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (tenantId == null) { + if (other.tenantId != null) + return false; + } else if (!tenantId.equals(other.tenantId)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Device [tenantId="); + builder.append(tenantId); + builder.append(", customerId="); + builder.append(customerId); + builder.append(", name="); + builder.append(name); + builder.append(", additionalInfo="); + builder.append(additionalInfo); + builder.append(", createdTime="); + builder.append(createdTime); + builder.append(", id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java new file mode 100644 index 00000000000..b1247eaf5a7 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +/** + * @author Andrew Shvayka + */ +public enum EntityType { + TENANT, DEVICE, CUSTOMER, RULE, PLUGIN +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Event.java b/common/data/src/main/java/org/thingsboard/server/common/data/Event.java new file mode 100644 index 00000000000..1298907e3ac --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Event.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EventId; +import org.thingsboard.server.common.data.id.TenantId; + +/** + * @author Andrew Shvayka + */ +@Data +public class Event extends BaseData { + + private TenantId tenantId; + private String type; + private String uid; + private EntityId entityId; + private JsonNode body; + + public Event() { + super(); + } + + public Event(EventId id) { + super(id); + } + + public Event(Event event) { + super(event); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBased.java new file mode 100644 index 00000000000..c63d8ca1b28 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBased.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import org.thingsboard.server.common.data.id.UUIDBased; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public abstract class SearchTextBased extends BaseData { + + private static final long serialVersionUID = -539812997348227609L; + + public SearchTextBased() { + super(); + } + + public SearchTextBased(I id) { + super(id); + } + + public SearchTextBased(SearchTextBased searchTextBased) { + super(searchTextBased); + } + + @JsonIgnore + public abstract String getSearchText(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java new file mode 100644 index 00000000000..2c22967c332 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java @@ -0,0 +1,144 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import org.thingsboard.server.common.data.id.TenantId; + +import com.fasterxml.jackson.databind.JsonNode; + +public class Tenant extends ContactBased{ + + private static final long serialVersionUID = 8057243243859922101L; + + private String title; + private String region; + private JsonNode additionalInfo; + + public Tenant() { + super(); + } + + public Tenant(TenantId id) { + super(id); + } + + public Tenant(Tenant tenant) { + super(tenant); + this.title = tenant.getTitle(); + this.region = tenant.getRegion(); + this.additionalInfo = tenant.getAdditionalInfo(); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public JsonNode getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(JsonNode additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public String getSearchText() { + return title; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode()); + result = prime * result + ((region == null) ? 0 : region.hashCode()); + result = prime * result + ((title == null) ? 0 : title.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + Tenant other = (Tenant) obj; + if (additionalInfo == null) { + if (other.additionalInfo != null) + return false; + } else if (!additionalInfo.equals(other.additionalInfo)) + return false; + if (region == null) { + if (other.region != null) + return false; + } else if (!region.equals(other.region)) + return false; + if (title == null) { + if (other.title != null) + return false; + } else if (!title.equals(other.title)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Tenant [title="); + builder.append(title); + builder.append(", region="); + builder.append(region); + builder.append(", additionalInfo="); + builder.append(additionalInfo); + builder.append(", country="); + builder.append(country); + builder.append(", state="); + builder.append(state); + builder.append(", city="); + builder.append(city); + builder.append(", address="); + builder.append(address); + builder.append(", address2="); + builder.append(address2); + builder.append(", zip="); + builder.append(zip); + builder.append(", phone="); + builder.append(phone); + builder.append(", email="); + builder.append(email); + builder.append(", createdTime="); + builder.append(createdTime); + builder.append(", id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java new file mode 100644 index 00000000000..2124454186b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -0,0 +1,200 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +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.security.Authority; + +import com.fasterxml.jackson.databind.JsonNode; + +public class User extends SearchTextBased { + + private static final long serialVersionUID = 8250339805336035966L; + + private TenantId tenantId; + private CustomerId customerId; + private String email; + private Authority authority; + private String firstName; + private String lastName; + private JsonNode additionalInfo; + + public User() { + super(); + } + + public User(UserId id) { + super(id); + } + + public User(User user) { + super(user); + this.tenantId = user.getTenantId(); + this.customerId = user.getCustomerId(); + this.email = user.getEmail(); + this.authority = user.getAuthority(); + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + this.additionalInfo = user.getAdditionalInfo(); + } + + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public CustomerId getCustomerId() { + return customerId; + } + + public void setCustomerId(CustomerId customerId) { + this.customerId = customerId; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Authority getAuthority() { + return authority; + } + + public void setAuthority(Authority authority) { + this.authority = authority; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public JsonNode getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(JsonNode additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public String getSearchText() { + return email; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((additionalInfo == null) ? 0 : additionalInfo.hashCode()); + result = prime * result + ((authority == null) ? 0 : authority.hashCode()); + result = prime * result + ((customerId == null) ? 0 : customerId.hashCode()); + result = prime * result + ((email == null) ? 0 : email.hashCode()); + result = prime * result + ((firstName == null) ? 0 : firstName.hashCode()); + result = prime * result + ((lastName == null) ? 0 : lastName.hashCode()); + result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + User other = (User) obj; + if (additionalInfo == null) { + if (other.additionalInfo != null) + return false; + } else if (!additionalInfo.equals(other.additionalInfo)) + return false; + if (authority != other.authority) + return false; + if (customerId == null) { + if (other.customerId != null) + return false; + } else if (!customerId.equals(other.customerId)) + return false; + if (email == null) { + if (other.email != null) + return false; + } else if (!email.equals(other.email)) + return false; + if (firstName == null) { + if (other.firstName != null) + return false; + } else if (!firstName.equals(other.firstName)) + return false; + if (lastName == null) { + if (other.lastName != null) + return false; + } else if (!lastName.equals(other.lastName)) + return false; + if (tenantId == null) { + if (other.tenantId != null) + return false; + } else if (!tenantId.equals(other.tenantId)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("User [tenantId="); + builder.append(tenantId); + builder.append(", customerId="); + builder.append(customerId); + builder.append(", email="); + builder.append(email); + builder.append(", authority="); + builder.append(authority); + builder.append(", firstName="); + builder.append(firstName); + builder.append(", lastName="); + builder.append(lastName); + builder.append(", additionalInfo="); + builder.append(additionalInfo); + builder.append(", createdTime="); + builder.append(createdTime); + builder.append(", id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java new file mode 100644 index 00000000000..2f3113627e3 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AdminSettingsId extends UUIDBased { + + @JsonCreator + public AdminSettingsId(@JsonProperty("id") UUID id){ + super(id); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/ComponentDescriptorId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/ComponentDescriptorId.java new file mode 100644 index 00000000000..43b023bdd52 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/ComponentDescriptorId.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public final class ComponentDescriptorId extends UUIDBased { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public ComponentDescriptorId(@JsonProperty("id") UUID id) { + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java new file mode 100644 index 00000000000..83fc95f1a45 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +public final class CustomerId extends UUIDBased implements EntityId { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public CustomerId(@JsonProperty("id") UUID id) { + super(id); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.CUSTOMER; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java new file mode 100644 index 00000000000..16968a47bc4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DashboardId extends UUIDBased { + + @JsonCreator + public DashboardId(@JsonProperty("id") UUID id){ + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceCredentialsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceCredentialsId.java new file mode 100644 index 00000000000..58b86d0c2b2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceCredentialsId.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DeviceCredentialsId extends UUIDBased { + + @JsonCreator + public DeviceCredentialsId(@JsonProperty("id") UUID id) { + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java new file mode 100644 index 00000000000..04d67907be0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +public class DeviceId extends UUIDBased implements EntityId { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public DeviceId(@JsonProperty("id") UUID id) { + super(id); + } + + public static DeviceId fromString(String deviceId) { + return new DeviceId(UUID.fromString(deviceId)); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.DEVICE; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java new file mode 100644 index 00000000000..30612a17d86 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +public interface EntityId { + + UUID NULL_UUID = UUID.fromString("13814000-1dd2-11b2-8080-808080808080"); + + UUID getId(); + + EntityType getEntityType(); + + @JsonIgnore + default boolean isNullUid() { + return NULL_UUID.equals(getId()); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EventId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EventId.java new file mode 100644 index 00000000000..12fd9d1ac5f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EventId.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class EventId extends UUIDBased { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public EventId(@JsonProperty("id") UUID id) { + super(id); + } + + public static EventId fromString(String eventId) { + return new EventId(UUID.fromString(eventId)); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java new file mode 100644 index 00000000000..1f98db895a3 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.UUID; + +public abstract class IdBased { + + protected I id; + + public IdBased() { + super(); + } + + public IdBased(I id) { + super(); + this.id = id; + } + + public void setId(I id) { + this.id = id; + } + + public I getId() { + return id; + } + + @JsonIgnore + public UUID getUuidId() { + if (id != null) { + return id.getId(); + } + return null; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + IdBased other = (IdBased) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NodeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NodeId.java new file mode 100644 index 00000000000..e77302d9ba2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NodeId.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +public class NodeId extends UUIDBased { + + public NodeId(UUID id){ + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/PluginId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/PluginId.java new file mode 100644 index 00000000000..f86e8dd0a75 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/PluginId.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public final class PluginId extends UUIDBased implements EntityId { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public PluginId(@JsonProperty("id") UUID id) { + super(id); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.PLUGIN; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleId.java new file mode 100644 index 00000000000..05d822f3b08 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleId.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class RuleId extends UUIDBased implements EntityId { + + @JsonCreator + public RuleId(@JsonProperty("id") UUID id) { + super(id); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.RULE; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/SessionId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/SessionId.java new file mode 100644 index 00000000000..dc6f883ebb1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/SessionId.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.io.Serializable; + +public interface SessionId extends Serializable { + + String toUidStr(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java new file mode 100644 index 00000000000..8ef157b183a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +public final class TenantId extends UUIDBased implements EntityId { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public TenantId(@JsonProperty("id") UUID id) { + super(id); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.TENANT; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java new file mode 100644 index 00000000000..b8d07e036cb --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java @@ -0,0 +1,72 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.io.Serializable; +import java.util.UUID; + +public abstract class UUIDBased implements Serializable { + + public static final UUID EMPTY = new UUID(0L, 0L); + + private static final long serialVersionUID = 1L; + + private final UUID id; + + public UUIDBased() { + this(UUID.randomUUID()); + } + + public UUIDBased(UUID id) { + super(); + this.id = id; + } + + public UUID getId() { + return id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UUIDBased other = (UUIDBased) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + @Override + public String toString() { + return id.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserCredentialsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserCredentialsId.java new file mode 100644 index 00000000000..d17279397bd --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserCredentialsId.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +public class UserCredentialsId extends UUIDBased { + + public UserCredentialsId(UUID id){ + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java new file mode 100644 index 00000000000..53531d76e88 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class UserId extends UUIDBased { + + @JsonCreator + public UserId(@JsonProperty("id") UUID id){ + super(id); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java new file mode 100644 index 00000000000..39d651897ce --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public final class WidgetTypeId extends UUIDBased { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public WidgetTypeId(@JsonProperty("id") UUID id) { + super(id); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java new file mode 100644 index 00000000000..2e97310e315 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public final class WidgetsBundleId extends UUIDBased { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public WidgetsBundleId(@JsonProperty("id") UUID id) { + super(id); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKey.java new file mode 100644 index 00000000000..ae23773bea5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKey.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +@Data +public class AttributeKey implements Serializable { + private final String scope; + private final String attributeKey; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKvEntry.java new file mode 100644 index 00000000000..42af8544c06 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/AttributeKvEntry.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +/** + * @author Andrew Shvayka + */ +public interface AttributeKvEntry extends KvEntry { + + long getLastUpdateTs(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java new file mode 100644 index 00000000000..afe430c230a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java @@ -0,0 +1,104 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +public class BaseAttributeKvEntry implements AttributeKvEntry { + + private final long lastUpdateTs; + private final KvEntry kv; + + public BaseAttributeKvEntry(KvEntry kv, long lastUpdateTs) { + this.kv = kv; + this.lastUpdateTs = lastUpdateTs; + } + + @Override + public long getLastUpdateTs() { + return lastUpdateTs; + } + + @Override + public String getKey() { + return kv.getKey(); + } + + @Override + public DataType getDataType() { + return kv.getDataType(); + } + + @Override + public Optional getStrValue() { + return kv.getStrValue(); + } + + @Override + public Optional getLongValue() { + return kv.getLongValue(); + } + + @Override + public Optional getBooleanValue() { + return kv.getBooleanValue(); + } + + @Override + public Optional getDoubleValue() { + return kv.getDoubleValue(); + } + + @Override + public String getValueAsString() { + return kv.getValueAsString(); + } + + @Override + public Object getValue() { + return kv.getValue(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BaseAttributeKvEntry that = (BaseAttributeKvEntry) o; + + if (lastUpdateTs != that.lastUpdateTs) return false; + return kv.equals(that.kv); + + } + + @Override + public int hashCode() { + int result = (int) (lastUpdateTs ^ (lastUpdateTs >>> 32)); + result = 31 * result + kv.hashCode(); + return result; + } + + @Override + public String toString() { + return "BaseAttributeKvEntry{" + + "lastUpdateTs=" + lastUpdateTs + + ", kv=" + kv + + '}'; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java new file mode 100644 index 00000000000..e74a91e6674 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Optional; + +public class BaseTsKvQuery implements TsKvQuery { + + private String key; + private Optional startTs; + private Optional endTs; + private Optional limit; + + public BaseTsKvQuery(String key, Optional startTs, Optional endTs, Optional limit) { + this.key = key; + this.startTs = startTs; + this.endTs = endTs; + this.limit = limit; + } + + public BaseTsKvQuery(String key, Long startTs, Long endTs, Integer limit) { + this(key, Optional.ofNullable(startTs), Optional.ofNullable(endTs), Optional.ofNullable(limit)); + } + + public BaseTsKvQuery(String key, Long startTs, Integer limit) { + this(key, startTs, null, limit); + } + + public BaseTsKvQuery(String key, Long startTs, Long endTs) { + this(key, startTs, endTs, null); + } + + public BaseTsKvQuery(String key, Long startTs) { + this(key, startTs, null, null); + } + + public BaseTsKvQuery(String key, Integer limit) { + this(key, null, null, limit); + } + + @Override + public String getKey() { + return key; + } + + @Override + public Optional getStartTs() { + return startTs; + } + + @Override + public Optional getEndTs() { + return endTs; + } + + @Override + public Optional getLimit() { + return limit; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java new file mode 100644 index 00000000000..e0e0a4b002a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public abstract class BasicKvEntry implements KvEntry { + + private final String key; + + protected BasicKvEntry(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } + + @Override + public Optional getStrValue() { + return Optional.ofNullable(null); + } + + @Override + public Optional getLongValue() { + return Optional.ofNullable(null); + } + + @Override + public Optional getBooleanValue() { + return Optional.ofNullable(null); + } + + @Override + public Optional getDoubleValue() { + return Optional.ofNullable(null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BasicKvEntry)) return false; + BasicKvEntry that = (BasicKvEntry) o; + return Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hash(key); + } + + @Override + public String toString() { + return "BasicKvEntry{" + + "key='" + key + '\'' + + '}'; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java new file mode 100644 index 00000000000..b4442fa3a46 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java @@ -0,0 +1,97 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class BasicTsKvEntry implements TsKvEntry { + + private final long ts; + private final KvEntry kv; + + public BasicTsKvEntry(long ts, KvEntry kv) { + this.ts = ts; + this.kv = kv; + } + + @Override + public String getKey() { + return kv.getKey(); + } + + @Override + public DataType getDataType() { + return kv.getDataType(); + } + + @Override + public Optional getStrValue() { + return kv.getStrValue(); + } + + @Override + public Optional getLongValue() { + return kv.getLongValue(); + } + + @Override + public Optional getBooleanValue() { + return kv.getBooleanValue(); + } + + @Override + public Optional getDoubleValue() { + return kv.getDoubleValue(); + } + + @Override + public Object getValue() { + return kv.getValue(); + } + + @Override + public long getTs() { + return ts; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BasicTsKvEntry)) return false; + BasicTsKvEntry that = (BasicTsKvEntry) o; + return getTs() == that.getTs() && + Objects.equals(kv, that.kv); + } + + @Override + public int hashCode() { + return Objects.hash(getTs(), kv); + } + + @Override + public String toString() { + return "BasicTsKvEntry{" + + "ts=" + ts + + ", kv=" + kv + + '}'; + } + + @Override + public String getValueAsString() { + return kv.getValueAsString(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BooleanDataEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BooleanDataEntry.java new file mode 100644 index 00000000000..f13c723e8c2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BooleanDataEntry.java @@ -0,0 +1,69 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class BooleanDataEntry extends BasicKvEntry { + private final Boolean value; + + public BooleanDataEntry(String key, Boolean value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.BOOLEAN; + } + + @Override + public Optional getBooleanValue() { + return Optional.of(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof BooleanDataEntry)) return false; + if (!super.equals(o)) return false; + BooleanDataEntry that = (BooleanDataEntry) o; + return Objects.equals(value, that.value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "BooleanDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return Boolean.toString(value); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java new file mode 100644 index 00000000000..68f53584200 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +public enum DataType { + + STRING, LONG, BOOLEAN, DOUBLE; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DoubleDataEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DoubleDataEntry.java new file mode 100644 index 00000000000..e9cfe1cf2b0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DoubleDataEntry.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class DoubleDataEntry extends BasicKvEntry { + + private final Double value; + + public DoubleDataEntry(String key, Double value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.DOUBLE; + } + + @Override + public Optional getDoubleValue() { + return Optional.of(value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DoubleDataEntry)) return false; + if (!super.equals(o)) return false; + DoubleDataEntry that = (DoubleDataEntry) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "DoubleDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return Double.toString(value); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java new file mode 100644 index 00000000000..eeaa70c1bec --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.io.Serializable; +import java.util.Optional; + +/** + * Represents attribute or any other KV data entry + * + * @author ashvayka + */ +public interface KvEntry extends Serializable { + + String getKey(); + + DataType getDataType(); + + Optional getStrValue(); + + Optional getLongValue(); + + Optional getBooleanValue(); + + Optional getDoubleValue(); + + String getValueAsString(); + + Object getValue(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/LongDataEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/LongDataEntry.java new file mode 100644 index 00000000000..b72ee006780 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/LongDataEntry.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class LongDataEntry extends BasicKvEntry { + + private final Long value; + + public LongDataEntry(String key, Long value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.LONG; + } + + @Override + public Optional getLongValue() { + return Optional.of(value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LongDataEntry)) return false; + if (!super.equals(o)) return false; + LongDataEntry that = (LongDataEntry) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "LongDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return Long.toString(value); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java new file mode 100644 index 00000000000..2512d7064f3 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/StringDataEntry.java @@ -0,0 +1,72 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class StringDataEntry extends BasicKvEntry { + + private static final long serialVersionUID = 1L; + private final String value; + + public StringDataEntry(String key, String value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.STRING; + } + + @Override + public Optional getStrValue() { + return Optional.of(value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof StringDataEntry)) + return false; + if (!super.equals(o)) + return false; + StringDataEntry that = (StringDataEntry) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "StringDataEntry{" + "value='" + value + '\'' + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return value; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java new file mode 100644 index 00000000000..cf5e275f270 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +/** + * Represents time series KV data entry + * + * @author ashvayka + * + */ +public interface TsKvEntry extends KvEntry { + + long getTs(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java new file mode 100644 index 00000000000..f1e89e6e007 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.kv; + +import java.util.Optional; + +public interface TsKvQuery { + + String getKey(); + + Optional getStartTs(); + + Optional getEndTs(); + + Optional getLimit(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageLink.java new file mode 100644 index 00000000000..ae19f3915d1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/BasePageLink.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.page; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.UUID; + +@RequiredArgsConstructor +@AllArgsConstructor +public abstract class BasePageLink implements Serializable { + + private static final long serialVersionUID = -4189954843653250481L; + + @Getter protected final int limit; + + @Getter @Setter protected UUID idOffset; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java new file mode 100644 index 00000000000..692d472b5ab --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageDataIterable.java @@ -0,0 +1,78 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.page; + +import java.util.Iterator; +import java.util.List; + +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.UUIDBased; + +public class PageDataIterable> implements Iterable, Iterator { + + private final FetchFunction function; + private final int fetchSize; + + private List currentItems; + private int currentIdx; + private boolean hasNextPack; + private TextPageLink nextPackLink; + private boolean initialized; + + public PageDataIterable(FetchFunction function, int fetchSize) { + super(); + this.function = function; + this.fetchSize = fetchSize; + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public boolean hasNext() { + if(!initialized){ + fetch(new TextPageLink(fetchSize)); + initialized = true; + } + if(currentIdx == currentItems.size()){ + if(hasNextPack){ + fetch(nextPackLink); + } + } + return currentIdx != currentItems.size(); + } + + private void fetch(TextPageLink link) { + TextPageData pageData = function.fetch(link); + currentIdx = 0; + currentItems = pageData.getData(); + hasNextPack = pageData.hasNext(); + nextPackLink = pageData.getNextPageLink(); + } + + @Override + public T next() { + return currentItems.get(currentIdx++); + } + + public static interface FetchFunction> { + + TextPageData fetch(TextPageLink link); + + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java new file mode 100644 index 00000000000..cc9d22d0f67 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageData.java @@ -0,0 +1,71 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.page; + +import java.util.List; +import java.util.UUID; + +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.UUIDBased; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TextPageData> { + + private final List data; + private final TextPageLink nextPageLink; + private final boolean hasNext; + + public TextPageData(List data, TextPageLink pageLink) { + super(); + this.data = data; + int limit = pageLink.getLimit(); + if (data != null && data.size() == limit) { + int index = data.size()-1; + UUID idOffset = data.get(index).getId().getId(); + String textOffset = data.get(index).getSearchText(); + nextPageLink = new TextPageLink(limit, pageLink.getTextSearch(), idOffset, textOffset); + hasNext = true; + } else { + nextPageLink = null; + hasNext = false; + } + } + + @JsonCreator + public TextPageData(@JsonProperty("data") List data, + @JsonProperty("nextPageLink") TextPageLink nextPageLink, + @JsonProperty("hasNext") boolean hasNext) { + this.data = data; + this.nextPageLink = nextPageLink; + this.hasNext = hasNext; + } + + public List getData() { + return data; + } + + @JsonProperty("hasNext") + public boolean hasNext() { + return hasNext; + } + + public TextPageLink getNextPageLink() { + return nextPageLink; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java new file mode 100644 index 00000000000..d91b90f4c93 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/TextPageLink.java @@ -0,0 +1,80 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.page; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.ToString; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.UUID; + +@ToString +public class TextPageLink extends BasePageLink implements Serializable { + + private static final long serialVersionUID = -4189954843653250480L; + + @Getter private final String textSearch; + @Getter private final String textSearchBound; + @Getter private final String textOffset; + + public TextPageLink(int limit) { + this(limit, null, null, null); + } + + public TextPageLink(int limit, String textSearch) { + this(limit, textSearch, null, null); + } + + public TextPageLink(int limit, String textSearch, UUID idOffset, String textOffset) { + super(limit, idOffset); + this.textSearch = textSearch != null ? textSearch.toLowerCase() : null; + this.textSearchBound = nextSequence(this.textSearch); + this.textOffset = textOffset != null ? textOffset.toLowerCase() : null; + } + + @JsonCreator + public TextPageLink(@JsonProperty("limit") int limit, + @JsonProperty("textSearch") String textSearch, + @JsonProperty("textSearchBound") String textSearchBound, + @JsonProperty("textOffset") String textOffset, + @JsonProperty("idOffset") UUID idOffset) { + super(limit, idOffset); + this.textSearch = textSearch; + this.textSearchBound = textSearchBound; + this.textOffset = textOffset; + this.idOffset = idOffset; + } + + private static String nextSequence(String input) { + if (input != null && input.length() > 0) { + char[] chars = input.toCharArray(); + int i = chars.length - 1; + while (i >= 0 && ++chars[i--] == Character.MIN_VALUE) ; + if (i == -1 && (chars.length == 0 || chars[0] == Character.MIN_VALUE)) { + char buf[] = Arrays.copyOf(input.toCharArray(), input.length() + 1); + buf[buf.length - 1] = Character.MIN_VALUE; + return new String(buf); + } + return new String(chars); + } else { + return null; + } + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java new file mode 100644 index 00000000000..6ed927e9521 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageData.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.page; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.UUIDBased; + +import java.util.List; +import java.util.UUID; + +public class TimePageData> { + + private final List data; + private final TimePageLink nextPageLink; + private final boolean hasNext; + + public TimePageData(List data, TimePageLink pageLink) { + super(); + this.data = data; + int limit = pageLink.getLimit(); + if (data != null && data.size() == limit) { + int index = data.size() - 1; + UUID idOffset = data.get(index).getId().getId(); + nextPageLink = new TimePageLink(limit, pageLink.getStartTime(), pageLink.getEndTime(), pageLink.isAscOrder(), idOffset); + hasNext = true; + } else { + nextPageLink = null; + hasNext = false; + } + } + + @JsonCreator + public TimePageData(@JsonProperty("data") List data, + @JsonProperty("nextPageLink") TimePageLink nextPageLink, + @JsonProperty("hasNext") boolean hasNext) { + this.data = data; + this.nextPageLink = nextPageLink; + this.hasNext = hasNext; + } + + public List getData() { + return data; + } + + @JsonProperty("hasNext") + public boolean hasNext() { + return hasNext; + } + + public TimePageLink getNextPageLink() { + return nextPageLink; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java new file mode 100644 index 00000000000..5296a7f8321 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/TimePageLink.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.page; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.ToString; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.UUID; + +@ToString +public class TimePageLink extends BasePageLink implements Serializable { + + private static final long serialVersionUID = -4189954843653250480L; + + @Getter private final Long startTime; + @Getter private final Long endTime; + @Getter private final boolean ascOrder; + + public TimePageLink(int limit) { + this(limit, null, null, false, null); + } + + public TimePageLink(int limit, Long startTime) { + this(limit, startTime, null, false, null); + } + + public TimePageLink(int limit, Long startTime, Long endTime) { + this(limit, startTime, endTime, false, null); + } + + public TimePageLink(int limit, Long startTime, Long endTime, boolean ascOrder) { + this(limit, startTime, endTime, ascOrder, null); + } + + @JsonCreator + public TimePageLink(@JsonProperty("limit") int limit, + @JsonProperty("startTime") Long startTime, + @JsonProperty("endTime") Long endTime, + @JsonProperty("ascOrder") boolean ascOrder, + @JsonProperty("idOffset") UUID idOffset) { + super(limit, idOffset); + this.startTime = startTime; + this.endTime = endTime; + this.ascOrder = ascOrder; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java new file mode 100644 index 00000000000..dac6ef7b49f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java @@ -0,0 +1,87 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.plugin; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.*; +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.ComponentDescriptorId; + +/** + * @author Andrew Shvayka + */ +@ToString +public class ComponentDescriptor extends SearchTextBased { + + private static final long serialVersionUID = 1L; + + @Getter @Setter private ComponentType type; + @Getter @Setter private ComponentScope scope; + @Getter @Setter private String name; + @Getter @Setter private String clazz; + @Getter @Setter private JsonNode configurationDescriptor; + @Getter @Setter private String actions; + + public ComponentDescriptor() { + super(); + } + + public ComponentDescriptor(ComponentDescriptorId id) { + super(id); + } + + public ComponentDescriptor(ComponentDescriptor plugin) { + super(plugin); + this.type = plugin.getType(); + this.scope = plugin.getScope(); + this.name = plugin.getName(); + this.clazz = plugin.getClazz(); + this.configurationDescriptor = plugin.getConfigurationDescriptor(); + this.actions = plugin.getActions(); + } + + @Override + public String getSearchText() { + return name; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ComponentDescriptor that = (ComponentDescriptor) o; + + if (type != that.type) return false; + if (scope != that.scope) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + if (actions != null ? !actions.equals(that.actions) : that.actions != null) return false; + if (configurationDescriptor != null ? !configurationDescriptor.equals(that.configurationDescriptor) : that.configurationDescriptor != null) return false; + return clazz != null ? clazz.equals(that.clazz) : that.clazz == null; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + (scope != null ? scope.hashCode() : 0); + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (clazz != null ? clazz.hashCode() : 0); + result = 31 * result + (actions != null ? actions.hashCode() : 0); + return result; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java new file mode 100644 index 00000000000..4126b52390e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.plugin; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +public enum ComponentLifecycleEvent implements Serializable { + CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleState.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleState.java new file mode 100644 index 00000000000..0ee673635b8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleState.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.plugin; + +/** + * @author Andrew Shvayka + */ +public enum ComponentLifecycleState { + ACTIVE, SUSPENDED +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentScope.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentScope.java new file mode 100644 index 00000000000..332563b682d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentScope.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.plugin; + +/** + * @author Andrew Shvayka + */ +public enum ComponentScope { + SYSTEM, TENANT +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java new file mode 100644 index 00000000000..439a1d81fc7 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.plugin; + +/** + * @author Andrew Shvayka + */ +public enum ComponentType { + + FILTER, PROCESSOR, ACTION, PLUGIN + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java new file mode 100644 index 00000000000..7cad18fcb19 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/PluginMetaData.java @@ -0,0 +1,175 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.plugin; + +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; + +import com.fasterxml.jackson.databind.JsonNode; + +public class PluginMetaData extends SearchTextBased { + + private static final long serialVersionUID = 1L; + + private String apiToken; + private TenantId tenantId; + private String name; + private String clazz; + private boolean publicAccess; + private ComponentLifecycleState state; + private JsonNode configuration; + private JsonNode additionalInfo; + + public PluginMetaData() { + super(); + } + + public PluginMetaData(PluginId id) { + super(id); + } + + public PluginMetaData(PluginMetaData plugin) { + super(plugin); + this.apiToken = plugin.getApiToken(); + this.tenantId = plugin.getTenantId(); + this.name = plugin.getName(); + this.clazz = plugin.getClazz(); + this.publicAccess = plugin.isPublicAccess(); + this.state = plugin.getState(); + this.configuration = plugin.getConfiguration(); + this.additionalInfo = plugin.getAdditionalInfo(); + } + + @Override + public String getSearchText() { + return name; + } + + public String getApiToken() { + return apiToken; + } + + public void setApiToken(String apiToken) { + this.apiToken = apiToken; + } + + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getClazz() { + return clazz; + } + + public void setClazz(String clazz) { + this.clazz = clazz; + } + + public JsonNode getConfiguration() { + return configuration; + } + + public void setConfiguration(JsonNode configuration) { + this.configuration = configuration; + } + + public boolean isPublicAccess() { + return publicAccess; + } + + public void setPublicAccess(boolean publicAccess) { + this.publicAccess = publicAccess; + } + + public void setState(ComponentLifecycleState state) { + this.state = state; + } + + public ComponentLifecycleState getState() { + return state; + } + + public JsonNode getAdditionalInfo() { + return additionalInfo; + } + + public void setAdditionalInfo(JsonNode additionalInfo) { + this.additionalInfo = additionalInfo; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((apiToken == null) ? 0 : apiToken.hashCode()); + result = prime * result + ((clazz == null) ? 0 : clazz.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + PluginMetaData other = (PluginMetaData) obj; + if (apiToken == null) { + if (other.apiToken != null) + return false; + } else if (!apiToken.equals(other.apiToken)) + return false; + if (clazz == null) { + if (other.clazz != null) + return false; + } else if (!clazz.equals(other.clazz)) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (tenantId == null) { + if (other.tenantId != null) + return false; + } else if (!tenantId.equals(other.tenantId)) + return false; + return true; + } + + @Override + public String toString() { + return "PluginMetaData [apiToken=" + apiToken + ", tenantId=" + tenantId + ", name=" + name + ", clazz=" + clazz + ", publicAccess=" + publicAccess + + ", configuration=" + configuration + "]"; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java new file mode 100644 index 00000000000..3ed132dcb51 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleMetaData.java @@ -0,0 +1,69 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.rule; + +import lombok.Data; +import lombok.ToString; +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; + +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; + +@Data +public class RuleMetaData extends SearchTextBased { + + private static final long serialVersionUID = -5656679015122935465L; + + private TenantId tenantId; + private String name; + private ComponentLifecycleState state; + private int weight; + private String pluginToken; + private JsonNode filters; + private JsonNode processor; + private JsonNode action; + private JsonNode additionalInfo; + + public RuleMetaData() { + super(); + } + + public RuleMetaData(RuleId id) { + super(id); + } + + public RuleMetaData(RuleMetaData rule) { + super(rule); + this.tenantId = rule.getTenantId(); + this.name = rule.getName(); + this.state = rule.getState(); + this.weight = rule.getWeight(); + this.pluginToken = rule.getPluginToken(); + this.filters = rule.getFilters(); + this.processor = rule.getProcessor(); + this.action = rule.getAction(); + this.additionalInfo = rule.getAdditionalInfo(); + } + + @Override + public String getSearchText() { + return name; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleType.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleType.java new file mode 100644 index 00000000000..f5b070041eb --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleType.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.rule; + +/** + * Defines origin of the rule. + * + * @author ashvayka + * + */ +public enum RuleType { + + SYSTEM, USER; + +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/Scope.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/Scope.java new file mode 100644 index 00000000000..e8a9870fa21 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/Scope.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.rule; + +/** + * Defines scope of the rule execution in the actor system + * + * @author ashvayka + * + */ +public enum Scope { + + SYSTEM, TENANT, CUSTOMER, DEVICE, RULE; + +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/Authority.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/Authority.java new file mode 100644 index 00000000000..568d07b52a1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/Authority.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security; + +public enum Authority { + + SYS_ADMIN(0), + TENANT_ADMIN(1), + CUSTOMER_USER(2), + REFRESH_TOKEN(10); + + private int code; + + Authority(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static Authority parse(String value) { + Authority authority = null; + if (value != null && value.length() != 0) { + for (Authority current : Authority.values()) { + if (current.name().equalsIgnoreCase(value)) { + authority = current; + break; + } + } + } + return authority; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java new file mode 100644 index 00000000000..a64d3249090 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java @@ -0,0 +1,128 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security; + +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.DeviceCredentialsId; +import org.thingsboard.server.common.data.id.DeviceId; + +public class DeviceCredentials extends BaseData implements DeviceCredentialsFilter { + + private static final long serialVersionUID = -7869261127032877765L; + + private DeviceId deviceId; + private DeviceCredentialsType credentialsType; + private String credentialsId; + private String credentialsValue; + + public DeviceCredentials() { + super(); + } + + public DeviceCredentials(DeviceCredentialsId id) { + super(id); + } + + public DeviceCredentials(DeviceCredentials deviceCredentials) { + super(deviceCredentials); + this.deviceId = deviceCredentials.getDeviceId(); + this.credentialsType = deviceCredentials.getCredentialsType(); + this.credentialsId = deviceCredentials.getCredentialsId(); + this.credentialsValue = deviceCredentials.getCredentialsValue(); + } + + public DeviceId getDeviceId() { + return deviceId; + } + + public void setDeviceId(DeviceId deviceId) { + this.deviceId = deviceId; + } + + @Override + public DeviceCredentialsType getCredentialsType() { + return credentialsType; + } + + public void setCredentialsType(DeviceCredentialsType credentialsType) { + this.credentialsType = credentialsType; + } + + @Override + public String getCredentialsId() { + return credentialsId; + } + + public void setCredentialsId(String credentialsId) { + this.credentialsId = credentialsId; + } + + public String getCredentialsValue() { + return credentialsValue; + } + + public void setCredentialsValue(String credentialsValue) { + this.credentialsValue = credentialsValue; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((credentialsId == null) ? 0 : credentialsId.hashCode()); + result = prime * result + ((credentialsType == null) ? 0 : credentialsType.hashCode()); + result = prime * result + ((credentialsValue == null) ? 0 : credentialsValue.hashCode()); + result = prime * result + ((deviceId == null) ? 0 : deviceId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + DeviceCredentials other = (DeviceCredentials) obj; + if (credentialsId == null) { + if (other.credentialsId != null) + return false; + } else if (!credentialsId.equals(other.credentialsId)) + return false; + if (credentialsType != other.credentialsType) + return false; + if (credentialsValue == null) { + if (other.credentialsValue != null) + return false; + } else if (!credentialsValue.equals(other.credentialsValue)) + return false; + if (deviceId == null) { + if (other.deviceId != null) + return false; + } else if (!deviceId.equals(other.deviceId)) + return false; + return true; + } + + @Override + public String toString() { + return "DeviceCredentials [deviceId=" + deviceId + ", credentialsType=" + credentialsType + ", credentialsId=" + + credentialsId + ", credentialsValue=" + credentialsValue + ", createdTime=" + createdTime + ", id=" + + id + "]"; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsFilter.java new file mode 100644 index 00000000000..c4b4dccf052 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsFilter.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security; + +/** + * TODO: This is a temporary name. DeviceCredentialsId is resereved in dao layer + */ +public interface DeviceCredentialsFilter { + + String getCredentialsId(); + + DeviceCredentialsType getCredentialsType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java new file mode 100644 index 00000000000..3daa1e4e1bc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentialsType.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security; + +public enum DeviceCredentialsType { + + ACCESS_TOKEN + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java new file mode 100644 index 00000000000..8ce9f00484e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceTokenCredentials.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security; + +public class DeviceTokenCredentials implements DeviceCredentialsFilter { + + private final String token; + + public DeviceTokenCredentials(String token) { + super(); + this.token = token; + } + + @Override + public DeviceCredentialsType getCredentialsType() { + return DeviceCredentialsType.ACCESS_TOKEN; + } + + @Override + public String getCredentialsId() { + return token; + } + + @Override + public String toString() { + return "DeviceTokenCredentials [token=" + token + "]"; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java new file mode 100644 index 00000000000..42d90bb5b56 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java @@ -0,0 +1,156 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security; + +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.UserCredentialsId; +import org.thingsboard.server.common.data.id.UserId; + +public class UserCredentials extends BaseData { + + private static final long serialVersionUID = -2108436378880529163L; + + private UserId userId; + private boolean enabled; + private String password; + private String activateToken; + private String resetToken; + + public UserCredentials() { + super(); + } + + public UserCredentials(UserCredentialsId id) { + super(id); + } + + public UserCredentials(UserCredentials userCredentials) { + super(userCredentials); + this.userId = userCredentials.getUserId(); + this.password = userCredentials.getPassword(); + this.enabled = userCredentials.isEnabled(); + this.activateToken = userCredentials.getActivateToken(); + this.resetToken = userCredentials.getResetToken(); + } + + public UserId getUserId() { + return userId; + } + + public void setUserId(UserId userId) { + this.userId = userId; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getActivateToken() { + return activateToken; + } + + public void setActivateToken(String activateToken) { + this.activateToken = activateToken; + } + + public String getResetToken() { + return resetToken; + } + + public void setResetToken(String resetToken) { + this.resetToken = resetToken; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((activateToken == null) ? 0 : activateToken.hashCode()); + result = prime * result + (enabled ? 1231 : 1237); + result = prime * result + ((password == null) ? 0 : password.hashCode()); + result = prime * result + ((resetToken == null) ? 0 : resetToken.hashCode()); + result = prime * result + ((userId == null) ? 0 : userId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + UserCredentials other = (UserCredentials) obj; + if (activateToken == null) { + if (other.activateToken != null) + return false; + } else if (!activateToken.equals(other.activateToken)) + return false; + if (enabled != other.enabled) + return false; + if (password == null) { + if (other.password != null) + return false; + } else if (!password.equals(other.password)) + return false; + if (resetToken == null) { + if (other.resetToken != null) + return false; + } else if (!resetToken.equals(other.resetToken)) + return false; + if (userId == null) { + if (other.userId != null) + return false; + } else if (!userId.equals(other.userId)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("UserCredentials [userId="); + builder.append(userId); + builder.append(", enabled="); + builder.append(enabled); + builder.append(", password="); + builder.append(password); + builder.append(", activateToken="); + builder.append(activateToken); + builder.append(", resetToken="); + builder.append(resetToken); + builder.append(", createdTime="); + builder.append(createdTime); + builder.append(", id="); + builder.append(id); + builder.append("]"); + return builder.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java new file mode 100644 index 00000000000..2f2851c3389 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java @@ -0,0 +1,129 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.widget; + +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.WidgetTypeId; + +public class WidgetType extends BaseData { + + private static final long serialVersionUID = 8388684344603660756L; + + private TenantId tenantId; + private String bundleAlias; + private String alias; + private String name; + private JsonNode descriptor; + + public WidgetType() { + super(); + } + + public WidgetType(WidgetTypeId id) { + super(id); + } + + public WidgetType(WidgetType widgetType) { + super(widgetType); + this.tenantId = widgetType.getTenantId(); + this.bundleAlias = widgetType.getBundleAlias(); + this.alias = widgetType.getAlias(); + this.name = widgetType.getName(); + this.descriptor = widgetType.getDescriptor(); + } + + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public String getBundleAlias() { + return bundleAlias; + } + + public void setBundleAlias(String bundleAlias) { + this.bundleAlias = bundleAlias; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public JsonNode getDescriptor() { + return descriptor; + } + + public void setDescriptor(JsonNode descriptor) { + this.descriptor = descriptor; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0); + result = 31 * result + (bundleAlias != null ? bundleAlias.hashCode() : 0); + result = 31 * result + (alias != null ? alias.hashCode() : 0); + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (descriptor != null ? descriptor.hashCode() : 0); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + WidgetType that = (WidgetType) o; + + if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false; + if (bundleAlias != null ? !bundleAlias.equals(that.bundleAlias) : that.bundleAlias != null) return false; + if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + return descriptor != null ? descriptor.equals(that.descriptor) : that.descriptor == null; + + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("WidgetType{"); + sb.append("tenantId=").append(tenantId); + sb.append(", bundleAlias='").append(bundleAlias).append('\''); + sb.append(", alias='").append(alias).append('\''); + sb.append(", name='").append(name).append('\''); + sb.append(", descriptor=").append(descriptor); + sb.append('}'); + return sb.toString(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java new file mode 100644 index 00000000000..65a7f72b178 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -0,0 +1,121 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.widget; + +import org.thingsboard.server.common.data.SearchTextBased; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.WidgetsBundleId; + +import java.util.Arrays; + +public class WidgetsBundle extends SearchTextBased { + + private static final long serialVersionUID = -7627368878362410489L; + + private TenantId tenantId; + private String alias; + private String title; + private byte[] image; + + public WidgetsBundle() { + super(); + } + + public WidgetsBundle(WidgetsBundleId id) { + super(id); + } + + public WidgetsBundle(WidgetsBundle widgetsBundle) { + super(widgetsBundle); + this.tenantId = widgetsBundle.getTenantId(); + this.alias = widgetsBundle.getAlias(); + this.title = widgetsBundle.getTitle(); + this.image = widgetsBundle.getImage(); + } + + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public byte[] getImage() { + return image; + } + + public void setImage(byte[] image) { + this.image = image; + } + + @Override + public String getSearchText() { + return title; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0); + result = 31 * result + (alias != null ? alias.hashCode() : 0); + result = 31 * result + (title != null ? title.hashCode() : 0); + result = 31 * result + Arrays.hashCode(image); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + + WidgetsBundle that = (WidgetsBundle) o; + + if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false; + if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false; + if (title != null ? !title.equals(that.title) : that.title != null) return false; + return Arrays.equals(image, that.image); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("WidgetsBundle{"); + sb.append("tenantId=").append(tenantId); + sb.append(", alias='").append(alias).append('\''); + sb.append(", title='").append(title).append('\''); + sb.append(", image=").append(Arrays.toString(image)); + sb.append('}'); + return sb.toString(); + } + +} diff --git a/common/message/pom.xml b/common/message/pom.xml new file mode 100644 index 00000000000..be1492813af --- /dev/null +++ b/common/message/pom.xml @@ -0,0 +1,76 @@ + + + 4.0.0 + + org.thingsboard.server + 0.0.1-SNAPSHOT + common + + org.thingsboard.server.common + message + jar + + Thingsboard Server Common Messages + http://thingsboard.org + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard.server.common + data + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + + + + + + diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java new file mode 100644 index 00000000000..1a93c250b40 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg; + +import org.thingsboard.server.common.data.rule.Scope; +import org.thingsboard.server.common.data.rule.RuleType; +import org.thingsboard.server.common.msg.aware.RuleAwareMsg; + +/** + * Message that is used to deliver some data to the rule instance. + * For example: aggregated statistics or command decoded from http request. + * + * @author ashvayka + * + * @param - payload + */ +public interface RuleMsg extends RuleAwareMsg { + + Scope getRuleLevel(); + + RuleType getRuleType(); + + V getPayload(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/CustomerAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/CustomerAwareMsg.java new file mode 100644 index 00000000000..d3911bea9af --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/CustomerAwareMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.aware; + +import org.thingsboard.server.common.data.id.CustomerId; + +public interface CustomerAwareMsg { + + CustomerId getCustomerId(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/DeviceAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/DeviceAwareMsg.java new file mode 100644 index 00000000000..2d1c85bc3ff --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/DeviceAwareMsg.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.aware; + +import org.thingsboard.server.common.data.id.DeviceId; + +public interface DeviceAwareMsg { + + DeviceId getDeviceId(); +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/NodeAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/NodeAwareMsg.java new file mode 100644 index 00000000000..64ea6060089 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/NodeAwareMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.aware; + +import org.thingsboard.server.common.data.id.NodeId; + +public interface NodeAwareMsg { + + NodeId getNodeId(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/PluginAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/PluginAwareMsg.java new file mode 100644 index 00000000000..c0b4a26e0d9 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/PluginAwareMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.aware; + +import org.thingsboard.server.common.data.id.PluginId; + +public interface PluginAwareMsg { + + PluginId getPluginId(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleAwareMsg.java new file mode 100644 index 00000000000..43631d92ed8 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleAwareMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.aware; + +import org.thingsboard.server.common.data.id.RuleId; + +public interface RuleAwareMsg { + + RuleId getRuleId(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/SessionAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/SessionAwareMsg.java new file mode 100644 index 00000000000..c4b78560231 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/SessionAwareMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.aware; + +import org.thingsboard.server.common.data.id.SessionId; + +public interface SessionAwareMsg { + + SessionId getSessionId(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/TenantAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/TenantAwareMsg.java new file mode 100644 index 00000000000..5a4cc97c22f --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/TenantAwareMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.aware; + +import org.thingsboard.server.common.data.id.TenantId; + +public interface TenantAwareMsg { + + TenantId getTenantId(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java new file mode 100644 index 00000000000..dba41f77902 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.cluster; + +import lombok.Data; + +/** + * @author Andrew Shvayka + */ +@Data +public final class ClusterEventMsg { + + private final ServerAddress serverAddress; + private final boolean added; + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerAddress.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerAddress.java new file mode 100644 index 00000000000..d574bacb9f6 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerAddress.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.cluster; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +@Data +@EqualsAndHashCode +public class ServerAddress implements Comparable, Serializable { + + private final String host; + private final int port; + + @Override + public int compareTo(ServerAddress o) { + int result = this.host.compareTo(o.host); + if (result == 0) { + result = this.port - o.port; + } + return result; + } + + @Override + public String toString() { + return '[' + host + ':' + port + ']'; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ToAllNodesMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ToAllNodesMsg.java new file mode 100644 index 00000000000..9aae56b7c6c --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ToAllNodesMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.cluster; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +public interface ToAllNodesMsg extends Serializable { +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java new file mode 100644 index 00000000000..f7828735e2e --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +/** + * @author Andrew Shvayka + */ +public class AttributesSubscribeMsg implements FromDeviceMsg { + @Override + public MsgType getMsgType() { + return MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java new file mode 100644 index 00000000000..a38a4cbdc27 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +/** + * @author Andrew Shvayka + */ +public class AttributesUnsubscribeMsg implements FromDeviceMsg { + @Override + public MsgType getMsgType() { + return MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java new file mode 100644 index 00000000000..e3a92ca7066 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.ToString; +import org.thingsboard.server.common.msg.kv.AttributesKVMsg; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +@ToString +public class AttributesUpdateNotification implements ToDeviceMsg { + + private static final long serialVersionUID = 1L; + + private AttributesKVMsg data; + + public AttributesUpdateNotification(AttributesKVMsg data) { + this.data = data; + } + + @Override + public boolean isSuccess() { + return true; + } + + @Override + public MsgType getMsgType() { + return MsgType.ATTRIBUTES_UPDATE_NOTIFICATION; + } + + public AttributesKVMsg getData() { + return data; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java new file mode 100644 index 00000000000..82e43775e2e --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.session.MsgType; + +public class BasicCommandAckResponse extends BasicResponseMsg implements StatusCodeResponse { + + private static final long serialVersionUID = 1L; + + public static BasicCommandAckResponse onSuccess(MsgType requestMsgType, Integer requestId) { + return BasicCommandAckResponse.onSuccess(requestMsgType, requestId, 200); + } + + public static BasicCommandAckResponse onSuccess(MsgType requestMsgType, Integer requestId, Integer code) { + return new BasicCommandAckResponse(requestMsgType, requestId, true, null, code); + } + + public static BasicCommandAckResponse onError(MsgType requestMsgType, Integer requestId, Exception error) { + return new BasicCommandAckResponse(requestMsgType, requestId, false, error, null); + } + + private BasicCommandAckResponse(MsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { + super(requestMsgType, requestId, MsgType.TO_DEVICE_RPC_RESPONSE_ACK, success, error, code); + } + + @Override + public String toString() { + return "BasicStatusCodeResponse []"; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java new file mode 100644 index 00000000000..a8fdc86454c --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.ToString; +import org.thingsboard.server.common.msg.session.MsgType; + +import java.util.Set; + +@ToString +public class BasicGetAttributesRequest extends BasicRequest implements GetAttributesRequest { + + private static final long serialVersionUID = 1L; + + private final Set clientKeys; + private final Set sharedKeys; + + public BasicGetAttributesRequest(Integer requestId, Set clientKeys, Set sharedKeys) { + super(requestId); + this.clientKeys = clientKeys; + this.sharedKeys = sharedKeys; + } + + @Override + public MsgType getMsgType() { + return MsgType.GET_ATTRIBUTES_REQUEST; + } + + @Override + public Set getClientAttributeNames() { + return clientKeys; + } + + @Override + public Set getSharedAttributeNames() { + return sharedKeys; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java new file mode 100644 index 00000000000..ed786bc17e1 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.ToString; +import org.thingsboard.server.common.msg.kv.AttributesKVMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +@ToString +public class BasicGetAttributesResponse extends BasicResponseMsg implements GetAttributesResponse { + + private static final long serialVersionUID = 1L; + + public static BasicGetAttributesResponse onSuccess(MsgType requestMsgType, int requestId, AttributesKVMsg code) { + return new BasicGetAttributesResponse(requestMsgType, requestId, true, null, code); + } + + public static BasicGetAttributesResponse onError(MsgType requestMsgType, int requestId, Exception error) { + return new BasicGetAttributesResponse(requestMsgType, requestId, false, error, null); + } + + private BasicGetAttributesResponse(MsgType requestMsgType, int requestId, boolean success, Exception error, AttributesKVMsg code) { + super(requestMsgType, requestId, MsgType.GET_ATTRIBUTES_RESPONSE, success, error, code); + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java new file mode 100644 index 00000000000..26401d12658 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +public class BasicRequest implements Serializable { + + public static final Integer DEFAULT_REQUEST_ID = 0; + + private final Integer requestId; + + public BasicRequest(Integer requestId) { + this.requestId = requestId; + } + + public Integer getRequestId() { + return requestId; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java new file mode 100644 index 00000000000..24921926962 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java @@ -0,0 +1,79 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.io.Serializable; +import java.util.Optional; + +import org.thingsboard.server.common.msg.session.MsgType; + + +public class BasicResponseMsg implements ResponseMsg { + + private static final long serialVersionUID = 1L; + + private final MsgType requestMsgType; + private final Integer requestId; + private final MsgType msgType; + private final boolean success; + private final T data; + private final Exception error; + + protected BasicResponseMsg(MsgType requestMsgType, Integer requestId, MsgType msgType, boolean success, Exception error, T data) { + super(); + this.requestMsgType = requestMsgType; + this.requestId = requestId; + this.msgType = msgType; + this.success = success; + this.error = error; + this.data = data; + } + + @Override + public MsgType getRequestMsgType() { + return requestMsgType; + } + + @Override + public Integer getRequestId() { + return requestId; + } + + @Override + public boolean isSuccess() { + return success; + } + + @Override + public Optional getError() { + return Optional.ofNullable(error); + } + + @Override + public Optional getData() { + return Optional.ofNullable(data); + } + + @Override + public String toString() { + return "BasicResponseMsg [success=" + success + ", data=" + data + ", error=" + error + "]"; + } + + @Override + public MsgType getMsgType() { + return msgType; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java new file mode 100644 index 00000000000..1c2bf41e60c --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.ToString; +import org.thingsboard.server.common.msg.session.MsgType; + +@ToString +public class BasicStatusCodeResponse extends BasicResponseMsg implements StatusCodeResponse { + + private static final long serialVersionUID = 1L; + + public static BasicStatusCodeResponse onSuccess(MsgType requestMsgType, Integer requestId) { + return BasicStatusCodeResponse.onSuccess(requestMsgType, requestId, 0); + } + + public static BasicStatusCodeResponse onSuccess(MsgType requestMsgType, Integer requestId, Integer code) { + return new BasicStatusCodeResponse(requestMsgType, requestId, true, null, code); + } + + public static BasicStatusCodeResponse onError(MsgType requestMsgType, Integer requestId, Exception error) { + return new BasicStatusCodeResponse(requestMsgType, requestId, false, error, null); + } + + private BasicStatusCodeResponse(MsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { + super(requestMsgType, requestId, MsgType.STATUS_CODE_RESPONSE, success, error, code); + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java new file mode 100644 index 00000000000..d984e7ff82d --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.msg.session.MsgType; + +public class BasicTelemetryUploadRequest extends BasicRequest implements TelemetryUploadRequest { + + private static final long serialVersionUID = 1L; + + private final Map> data; + + public BasicTelemetryUploadRequest() { + this(DEFAULT_REQUEST_ID); + } + + public BasicTelemetryUploadRequest(Integer requestId) { + super(requestId); + this.data = new HashMap<>(); + } + + public void add(long ts, KvEntry entry) { + List tsEntries = data.get(ts); + if (tsEntries == null) { + tsEntries = new ArrayList<>(); + data.put(ts, tsEntries); + } + tsEntries.add(entry); + } + + @Override + public MsgType getMsgType() { + return MsgType.POST_TELEMETRY_REQUEST; + } + + @Override + public Map> getData() { + return data; + } + + @Override + public String toString() { + return "BasicTelemetryUploadRequest [data=" + data + "]"; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicToDeviceSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicToDeviceSessionActorMsg.java new file mode 100644 index 00000000000..538959d19c2 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicToDeviceSessionActorMsg.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +public class BasicToDeviceSessionActorMsg implements ToDeviceSessionActorMsg { + + private final ToDeviceMsg msg; + private final SessionId sessionId; + + public BasicToDeviceSessionActorMsg(ToDeviceMsg msg, SessionId sessionId) { + super(); + this.msg = msg; + this.sessionId = sessionId; + } + + @Override + public SessionId getSessionId() { + return sessionId; + } + + @Override + public ToDeviceMsg getMsg() { + return msg; + } + + @Override + public String toString() { + return "BasicToSessionResponseMsg [msg=" + msg + ", sessionId=" + sessionId + "]"; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicUpdateAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicUpdateAttributesRequest.java new file mode 100644 index 00000000000..c968f04c634 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicUpdateAttributesRequest.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.msg.session.MsgType; + +public class BasicUpdateAttributesRequest extends BasicRequest implements UpdateAttributesRequest { + + private static final long serialVersionUID = 1L; + + private final Set data; + + public BasicUpdateAttributesRequest() { + this(DEFAULT_REQUEST_ID); + } + + public BasicUpdateAttributesRequest(Integer requestId) { + super(requestId); + this.data = new LinkedHashSet<>(); + } + + public void add(AttributeKvEntry entry) { + this.data.add(entry); + } + + public void add(Collection entries) { + this.data.addAll(entries); + } + + @Override + public MsgType getMsgType() { + return MsgType.POST_ATTRIBUTES_REQUEST; + } + + @Override + public Set getAttributes() { + return data; + } + + @Override + public String toString() { + return "BasicUpdateAttributesRequest [data=" + data + "]"; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java new file mode 100644 index 00000000000..49bca539510 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.util.Set; + +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; + +public interface GetAttributesRequest extends FromDeviceRequestMsg { + + Set getClientAttributeNames(); + Set getSharedAttributeNames(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesResponse.java new file mode 100644 index 00000000000..ec3f074c8ff --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesResponse.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.kv.AttributesKVMsg; + +public interface GetAttributesResponse extends ResponseMsg { + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java new file mode 100644 index 00000000000..861eab85026 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.io.Serializable; +import java.util.Optional; + +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +public interface ResponseMsg extends ToDeviceMsg { + + MsgType getRequestMsgType(); + + Integer getRequestId(); + + Optional getError(); + + Optional getData(); +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java new file mode 100644 index 00000000000..b9ea578490e --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +/** + * @author Andrew Shvayka + */ +public class RpcSubscribeMsg implements FromDeviceMsg { + @Override + public MsgType getMsgType() { + return MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java new file mode 100644 index 00000000000..6934a956bdc --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +/** + * @author Andrew Shvayka + */ +public class RpcUnsubscribeMsg implements FromDeviceMsg { + @Override + public MsgType getMsgType() { + return MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java new file mode 100644 index 00000000000..1c235a0dd24 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +/** + * @author Andrew Shvayka + */ + +public enum RuleEngineError { + + NO_RULES, NO_ACTIVE_RULES, NO_FILTERS_MATCHED, NO_REQUEST_FROM_ACTIONS, NO_TWO_WAY_ACTIONS, NO_RESPONSE_FROM_ACTIONS, PLUGIN_TIMEOUT(true); + + private final boolean critical; + + RuleEngineError() { + this(false); + } + + RuleEngineError(boolean critical) { + this.critical = critical; + } + + public boolean isCritical() { + return critical; + } + + public int getPriority() { + return ordinal(); + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java new file mode 100644 index 00000000000..c9ea00ac3c2 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.Data; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +/** + * @author Andrew Shvayka + */ +@Data +public class RuleEngineErrorMsg implements ToDeviceMsg { + + private final MsgType inMsgType; + private final RuleEngineError error; + + @Override + public boolean isSuccess() { + return false; + } + + @Override + public MsgType getMsgType() { + return MsgType.RULE_ENGINE_ERROR; + } + + public String getErrorMsg() { + switch (error) { + case NO_RULES: + return "No rules configured!"; + case NO_ACTIVE_RULES: + return "No active rules!"; + case NO_FILTERS_MATCHED: + return "No rules that match current message!"; + case NO_REQUEST_FROM_ACTIONS: + return "Rule filters match, but no plugin message produced by rule action!"; + case NO_TWO_WAY_ACTIONS: + return "Rule filters match, but no rule with two-way action configured!"; + case NO_RESPONSE_FROM_ACTIONS: + return "Rule filters match, message processed by plugin, but no response produced by rule action!"; + case PLUGIN_TIMEOUT: + return "Timeout during processing of message by plugin!"; + default: + throw new RuntimeException("Error " + error + " is not supported!"); + } + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java new file mode 100644 index 00000000000..61bc094d52f --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +/** + * @author Andrew Shvayka + */ +public class SessionCloseMsg implements FromDeviceMsg { + @Override + public MsgType getMsgType() { + return MsgType.SESSION_CLOSE; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java new file mode 100644 index 00000000000..81daf899f9f --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +public interface StatusCodeResponse extends ResponseMsg{ + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java new file mode 100644 index 00000000000..0682d3ba705 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.util.List; +import java.util.Map; + +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; + +public interface TelemetryUploadRequest extends FromDeviceRequestMsg { + + Map> getData(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java new file mode 100644 index 00000000000..c376ea0ba36 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.Data; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToDeviceRpcRequestMsg implements ToDeviceMsg { + + private final int requestId; + private final String method; + private final String params; + + @Override + public MsgType getMsgType() { + return MsgType.TO_DEVICE_RPC_REQUEST; + } + + @Override + public boolean isSuccess() { + return true; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java new file mode 100644 index 00000000000..5ee764a0461 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.Data; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToDeviceRpcResponseMsg implements FromDeviceMsg { + + private final int requestId; + private final String data; + + @Override + public MsgType getMsgType() { + return MsgType.TO_DEVICE_RPC_RESPONSE; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceSessionActorMsg.java new file mode 100644 index 00000000000..97ab6d953d1 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceSessionActorMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +public interface ToDeviceSessionActorMsg extends SessionAwareMsg, Serializable { + + ToDeviceMsg getMsg(); +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java new file mode 100644 index 00000000000..a684681c5f0 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.Data; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToServerRpcRequestMsg implements FromDeviceMsg { + + private final int requestId; + private final String method; + private final String params; + + @Override + public MsgType getMsgType() { + return MsgType.TO_SERVER_RPC_REQUEST; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java new file mode 100644 index 00000000000..ccce825de67 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import lombok.Data; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToServerRpcResponseMsg implements ToDeviceMsg { + + private final int requestId; + private final String data; + + @Override + public MsgType getMsgType() { + return MsgType.TO_SERVER_RPC_RESPONSE; + } + + @Override + public boolean isSuccess() { + return true; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/UpdateAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/UpdateAttributesRequest.java new file mode 100644 index 00000000000..4f5c9362874 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/UpdateAttributesRequest.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.core; + +import java.util.Set; + +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; + +public interface UpdateAttributesRequest extends FromDeviceRequestMsg { + + Set getAttributes(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicToDeviceActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicToDeviceActorMsg.java new file mode 100644 index 00000000000..c5591119b0f --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicToDeviceActorMsg.java @@ -0,0 +1,101 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.device; + +import lombok.ToString; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.SessionType; +import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; + +import java.util.Optional; + +@ToString +public class BasicToDeviceActorMsg implements ToDeviceActorMsg { + + private static final long serialVersionUID = -1866795134993115408L; + + private final TenantId tenantId; + private final CustomerId customerId; + private final DeviceId deviceId; + private final SessionId sessionId; + private final SessionType sessionType; + private final ServerAddress serverAddress; + private final FromDeviceMsg msg; + + public BasicToDeviceActorMsg(ToDeviceActorMsg other, FromDeviceMsg msg) { + this(null, other.getTenantId(), other.getCustomerId(), other.getDeviceId(), other.getSessionId(), other.getSessionType(), msg); + } + + public BasicToDeviceActorMsg(ToDeviceActorSessionMsg msg, SessionType sessionType) { + this(null, msg.getTenantId(), msg.getCustomerId(), msg.getDeviceId(), msg.getSessionId(), sessionType, msg.getSessionMsg().getMsg()); + } + + private BasicToDeviceActorMsg(ServerAddress serverAddress, TenantId tenantId, CustomerId customerId, DeviceId deviceId, SessionId sessionId, SessionType sessionType, + FromDeviceMsg msg) { + super(); + this.serverAddress = serverAddress; + this.tenantId = tenantId; + this.customerId = customerId; + this.deviceId = deviceId; + this.sessionId = sessionId; + this.sessionType = sessionType; + this.msg = msg; + } + + @Override + public DeviceId getDeviceId() { + return deviceId; + } + + @Override + public CustomerId getCustomerId() { + return customerId; + } + + public TenantId getTenantId() { + return tenantId; + } + + @Override + public SessionId getSessionId() { + return sessionId; + } + + @Override + public SessionType getSessionType() { + return sessionType; + } + + @Override + public Optional getServerAddress() { + return Optional.ofNullable(serverAddress); + } + + @Override + public FromDeviceMsg getPayload() { + return msg; + } + + @Override + public ToDeviceActorMsg toOtherAddress(ServerAddress otherAddress) { + return new BasicToDeviceActorMsg(otherAddress, tenantId, customerId, deviceId, sessionId, sessionType, msg); + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/device/ToDeviceActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/device/ToDeviceActorMsg.java new file mode 100644 index 00000000000..c02ac0353eb --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/device/ToDeviceActorMsg.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.device; + +import java.io.Serializable; +import java.util.Optional; + +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.aware.CustomerAwareMsg; +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; +import org.thingsboard.server.common.msg.aware.TenantAwareMsg; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.session.FromDeviceMsg; +import org.thingsboard.server.common.msg.session.SessionType; + +public interface ToDeviceActorMsg extends DeviceAwareMsg, CustomerAwareMsg, TenantAwareMsg, Serializable { + + SessionId getSessionId(); + + SessionType getSessionType(); + + Optional getServerAddress(); + + FromDeviceMsg getPayload(); + + ToDeviceActorMsg toOtherAddress(ServerAddress otherAddress); +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/kv/AttributesKVMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/kv/AttributesKVMsg.java new file mode 100644 index 00000000000..73b8f00aaf7 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/kv/AttributesKVMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.kv; + +import java.io.Serializable; +import java.util.List; + +import org.thingsboard.server.common.data.kv.AttributeKey; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; + +public interface AttributesKVMsg extends Serializable { + + List getClientAttributes(); + List getSharedAttributes(); + List getDeletedAttributes(); +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java new file mode 100644 index 00000000000..32cdcaf314b --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.kv; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.data.kv.AttributeKey; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; + +import java.util.Collections; +import java.util.List; + +@Data +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class BasicAttributeKVMsg implements AttributesKVMsg { + + private static final long serialVersionUID = 1L; + + private final List clientAttributes; + private final List sharedAttributes; + private final List deletedAttributes; + + public static BasicAttributeKVMsg fromClient(List attributes) { + return new BasicAttributeKVMsg(attributes, Collections.emptyList(), Collections.emptyList()); + } + + public static BasicAttributeKVMsg fromShared(List attributes) { + return new BasicAttributeKVMsg(Collections.emptyList(), attributes, Collections.emptyList()); + } + + public static BasicAttributeKVMsg from(List client, List shared) { + return new BasicAttributeKVMsg(client, shared, Collections.emptyList()); + } + + public static AttributesKVMsg fromDeleted(List shared) { + return new BasicAttributeKVMsg(Collections.emptyList(), Collections.emptyList(), shared); + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java new file mode 100644 index 00000000000..fb56893529c --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleMsg.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.plugin; + +import lombok.Data; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.msg.aware.TenantAwareMsg; +import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@ToString +public class ComponentLifecycleMsg implements TenantAwareMsg, ToAllNodesMsg { + @Getter + private final TenantId tenantId; + private final PluginId pluginId; + private final RuleId ruleId; + @Getter + private final ComponentLifecycleEvent event; + + public static ComponentLifecycleMsg forPlugin(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent event) { + return new ComponentLifecycleMsg(tenantId, pluginId, null, event); + } + + public static ComponentLifecycleMsg forRule(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent event) { + return new ComponentLifecycleMsg(tenantId, null, ruleId, event); + } + + private ComponentLifecycleMsg(TenantId tenantId, PluginId pluginId, RuleId ruleId, ComponentLifecycleEvent event) { + this.tenantId = tenantId; + this.pluginId = pluginId; + this.ruleId = ruleId; + this.event = event; + } + + public Optional getPluginId() { + return Optional.ofNullable(pluginId); + } + + public Optional getRuleId() { + return Optional.ofNullable(ruleId); + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/AdaptorToSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/AdaptorToSessionActorMsg.java new file mode 100644 index 00000000000..a005f1cfe4c --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/AdaptorToSessionActorMsg.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +public interface AdaptorToSessionActorMsg extends SessionMsg { + + FromDeviceMsg getMsg(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java new file mode 100644 index 00000000000..11caf25d952 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +public class BasicAdaptorToSessionActorMsg extends BasicSessionMsg implements AdaptorToSessionActorMsg { + + private final FromDeviceMsg msg; + + public BasicAdaptorToSessionActorMsg(SessionContext ctx, FromDeviceMsg msg) { + super(ctx); + this.msg = msg; + } + + @Override + public FromDeviceMsg getMsg() { + return msg; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java new file mode 100644 index 00000000000..3b7d9fdf971 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import java.util.Optional; + +public class BasicSessionActorToAdaptorMsg extends BasicSessionMsg implements SessionActorToAdaptorMsg { + + private final ToDeviceMsg msg; + + public BasicSessionActorToAdaptorMsg(SessionContext ctx, ToDeviceMsg msg) { + super(ctx); + this.msg = msg; + } + + @Override + public ToDeviceMsg getMsg() { + return msg; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java new file mode 100644 index 00000000000..7bcc55e9916 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import org.thingsboard.server.common.data.id.SessionId; + +public class BasicSessionMsg implements SessionMsg { + + private final SessionContext ctx; + + public BasicSessionMsg(SessionContext ctx) { + super(); + this.ctx = ctx; + } + + @Override + public SessionId getSessionId() { + return ctx.getSessionId(); + } + + @Override + public SessionContext getSessionContext() { + return ctx; + } + + @Override + public String toString() { + return "BasicSessionMsg [ctx=" + ctx + "]"; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicToDeviceActorSessionMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicToDeviceActorSessionMsg.java new file mode 100644 index 00000000000..ced48432d75 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicToDeviceActorSessionMsg.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.data.id.TenantId; + +public class BasicToDeviceActorSessionMsg implements ToDeviceActorSessionMsg { + + private final TenantId tenantId; + private final CustomerId customerId; + private final DeviceId deviceId; + private final AdaptorToSessionActorMsg msg; + + public BasicToDeviceActorSessionMsg(Device device, AdaptorToSessionActorMsg msg) { + super(); + this.tenantId = device.getTenantId(); + this.customerId = device.getCustomerId(); + this.deviceId = device.getId(); + this.msg = msg; + } + + public BasicToDeviceActorSessionMsg(ToDeviceActorSessionMsg deviceMsg) { + this.tenantId = deviceMsg.getTenantId(); + this.customerId = deviceMsg.getCustomerId(); + this.deviceId = deviceMsg.getDeviceId(); + this.msg = deviceMsg.getSessionMsg(); + } + + @Override + public DeviceId getDeviceId() { + return deviceId; + } + + @Override + public CustomerId getCustomerId() { + return customerId; + } + + public TenantId getTenantId() { + return tenantId; + } + + @Override + public SessionId getSessionId() { + return msg.getSessionId(); + } + + @Override + public AdaptorToSessionActorMsg getSessionMsg() { + return msg; + } + + @Override + public String toString() { + return "BasicToDeviceActorSessionMsg [tenantId=" + tenantId + ", customerId=" + customerId + ", deviceId=" + deviceId + ", msg=" + msg + + "]"; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java new file mode 100644 index 00000000000..76da4055798 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +public enum FeatureType { + ATTRIBUTES, TELEMETRY, RPC +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java new file mode 100644 index 00000000000..b717fec751e --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import java.io.Serializable; + +public interface FromDeviceMsg extends Serializable { + + MsgType getMsgType(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceRequestMsg.java new file mode 100644 index 00000000000..77094626627 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceRequestMsg.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +/** + * @author Andrew Shvayka + */ +public interface FromDeviceRequestMsg extends FromDeviceMsg { + + Integer getRequestId(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/MsgType.java new file mode 100644 index 00000000000..1b914254626 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/MsgType.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +public enum MsgType { + GET_ATTRIBUTES_REQUEST(true), POST_ATTRIBUTES_REQUEST(true), GET_ATTRIBUTES_RESPONSE, + SUBSCRIBE_ATTRIBUTES_REQUEST, UNSUBSCRIBE_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATE_NOTIFICATION, + + POST_TELEMETRY_REQUEST(true), STATUS_CODE_RESPONSE, + + SUBSCRIBE_RPC_COMMANDS_REQUEST, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, + TO_DEVICE_RPC_REQUEST, TO_DEVICE_RPC_RESPONSE, TO_DEVICE_RPC_RESPONSE_ACK, + + TO_SERVER_RPC_REQUEST(true), TO_SERVER_RPC_RESPONSE, + + RULE_ENGINE_ERROR, + + SESSION_CLOSE; + + private final boolean requiresRulesProcessing; + + MsgType() { + this(false); + } + + MsgType(boolean requiresRulesProcessing) { + this.requiresRulesProcessing = requiresRulesProcessing; + } + + public boolean requiresRulesProcessing() { + return requiresRulesProcessing; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java new file mode 100644 index 00000000000..f85545e7336 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +public interface SessionActorToAdaptorMsg extends SessionMsg { + + ToDeviceMsg getMsg(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java new file mode 100644 index 00000000000..d437ddafc6f --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; +import org.thingsboard.server.common.msg.session.ex.SessionException; + +public interface SessionContext extends SessionAwareMsg { + + SessionType getSessionType(); + + void onMsg(SessionActorToAdaptorMsg msg) throws SessionException; + + void onMsg(SessionCtrlMsg msg) throws SessionException; + + void onError(SessionException e); + + boolean isClosed(); + + long getTimeout(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java new file mode 100644 index 00000000000..68c1d8db24a --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; + +public interface SessionCtrlMsg extends SessionAwareMsg { + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java new file mode 100644 index 00000000000..f4a51c5f7cd --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; + +public interface SessionMsg extends SessionAwareMsg { + + SessionContext getSessionContext(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionType.java new file mode 100644 index 00000000000..1e432f9115d --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionType.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +public enum SessionType { + + SYNC, ASYNC; + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceActorSessionMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceActorSessionMsg.java new file mode 100644 index 00000000000..aa3650e7e5e --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceActorSessionMsg.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import org.thingsboard.server.common.msg.aware.CustomerAwareMsg; +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; +import org.thingsboard.server.common.msg.aware.TenantAwareMsg; + +public interface ToDeviceActorSessionMsg extends DeviceAwareMsg, CustomerAwareMsg, TenantAwareMsg, SessionAwareMsg { + + AdaptorToSessionActorMsg getSessionMsg(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java new file mode 100644 index 00000000000..8c87f6037d0 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session; + +import java.io.Serializable; + +public interface ToDeviceMsg extends Serializable { + + boolean isSuccess(); + + MsgType getMsgType(); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java new file mode 100644 index 00000000000..d1885273cba --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session.ctrl; + +import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.session.SessionCtrlMsg; + +public class SessionCloseMsg implements SessionCtrlMsg { + + private final SessionId sessionId; + private final boolean timeout; + + public SessionCloseMsg(SessionId sessionId, boolean timeout) { + super(); + this.sessionId = sessionId; + this.timeout = timeout; + } + + @Override + public SessionId getSessionId() { + return sessionId; + } + + public boolean isTimeout() { + return timeout; + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/ProcessingTimeoutException.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/ProcessingTimeoutException.java new file mode 100644 index 00000000000..7323a2d34c4 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/ProcessingTimeoutException.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session.ex; + +public class ProcessingTimeoutException extends Exception { + + private static final long serialVersionUID = 1L; + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionAuthException.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionAuthException.java new file mode 100644 index 00000000000..9d22cca01b6 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionAuthException.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session.ex; + +public class SessionAuthException extends SessionException { + + private static final long serialVersionUID = 1L; + + public SessionAuthException(String msg) { + super(msg); + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionException.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionException.java new file mode 100644 index 00000000000..444c364790e --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ex/SessionException.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.session.ex; + +public class SessionException extends Exception { + + private static final long serialVersionUID = 1L; + + public SessionException(String msg){ + super(msg); + } + + public SessionException(Exception cause){ + super(cause); + } + + public SessionException(String msg, Exception cause){ + super(msg, cause); + } + + +} diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 00000000000..97ff8ab62b8 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + org.thingsboard + 0.0.1-SNAPSHOT + server + + org.thingsboard.server + common + pom + + Thingsboard Server Commons + http://thingsboard.org + + + UTF-8 + ${basedir}/.. + + + data + message + transport + + + diff --git a/common/transport/pom.xml b/common/transport/pom.xml new file mode 100644 index 00000000000..e57d3d790a2 --- /dev/null +++ b/common/transport/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + org.thingsboard.server + 0.0.1-SNAPSHOT + common + + org.thingsboard.server.common + transport + jar + + Thingsboard Server Common Transport components + http://thingsboard.org + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard.server.common + data + + + org.thingsboard.server.common + message + + + com.google.code.gson + gson + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java new file mode 100644 index 00000000000..a9760aa47e8 --- /dev/null +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport; + +import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; +import org.thingsboard.server.common.msg.aware.SessionAwareMsg; + +public interface SessionMsgProcessor { + + void process(SessionAwareMsg msg); + +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java new file mode 100644 index 00000000000..c2b1fe0d28e --- /dev/null +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport; + +import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; +import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; + +import java.util.Optional; + +public interface TransportAdaptor { + + AdaptorToSessionActorMsg convertToActorMsg(C ctx, MsgType type, T inbound) throws AdaptorException; + + Optional convertToAdaptorMsg(C ctx, SessionActorToAdaptorMsg msg) throws AdaptorException; + +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java new file mode 100644 index 00000000000..2c826ecca2c --- /dev/null +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.adaptor; + +public class AdaptorException extends Exception { + + private static final long serialVersionUID = 1L; + + public AdaptorException() { + super(); + } + + public AdaptorException(String cause) { + super(cause); + } + + public AdaptorException(Exception cause) { + super(cause); + } + +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java new file mode 100644 index 00000000000..27c705269a4 --- /dev/null +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -0,0 +1,199 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.adaptor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import com.google.gson.*; +import org.thingsboard.server.common.msg.core.*; + +import org.thingsboard.server.common.data.kv.*; +import org.thingsboard.server.common.msg.kv.AttributesKVMsg; + +public class JsonConverter { + + private static final Gson GSON = new Gson(); + + public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject) throws JsonSyntaxException { + return convertToTelemetry(jsonObject, BasicRequest.DEFAULT_REQUEST_ID); + } + + public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, int requestId) throws JsonSyntaxException { + BasicTelemetryUploadRequest request = new BasicTelemetryUploadRequest(requestId); + long systemTs = System.currentTimeMillis(); + if (jsonObject.isJsonObject()) { + parseObject(request, systemTs, jsonObject); + } else if (jsonObject.isJsonArray()) { + jsonObject.getAsJsonArray().forEach(je -> { + if (je.isJsonObject()) { + parseObject(request, systemTs, je.getAsJsonObject()); + } else { + throw new JsonSyntaxException("Can't parse value: " + je); + } + }); + } else { + throw new JsonSyntaxException("Can't parse value: " + jsonObject); + } + return request; + } + + public static ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { + JsonObject object = json.getAsJsonObject(); + return new ToServerRpcRequestMsg(requestId, object.get("method").getAsString(), GSON.toJson(object.get("params"))); + } + + private static void parseObject(BasicTelemetryUploadRequest request, long systemTs, JsonElement jsonObject) { + JsonObject jo = jsonObject.getAsJsonObject(); + if (jo.has("ts") && jo.has("values")) { + parseWithTs(request, jo); + } else { + parseWithoutTs(request, systemTs, jo); + } + } + + private static void parseWithoutTs(BasicTelemetryUploadRequest request, long systemTs, JsonObject jo) { + for (KvEntry entry : parseValues(jo)) { + request.add(systemTs, entry); + } + } + + private static void parseWithTs(BasicTelemetryUploadRequest request, JsonObject jo) { + long ts = jo.get("ts").getAsLong(); + JsonObject valuesObject = jo.get("values").getAsJsonObject(); + for (KvEntry entry : parseValues(valuesObject)) { + request.add(ts, entry); + } + } + + private static List parseValues(JsonObject valuesObject) { + List result = new ArrayList<>(); + for (Entry valueEntry : valuesObject.entrySet()) { + JsonElement element = valueEntry.getValue(); + if (element.isJsonPrimitive()) { + JsonPrimitive value = element.getAsJsonPrimitive(); + if (value.isString()) { + result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); + } else if (value.isBoolean()) { + result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean())); + } else if (value.isNumber()) { + if (value.getAsString().contains(".")) { + result.add(new DoubleDataEntry(valueEntry.getKey(), value.getAsDouble())); + } else { + result.add(new LongDataEntry(valueEntry.getKey(), value.getAsLong())); + } + } else { + throw new JsonSyntaxException("Can't parse value: " + value); + } + } else { + throw new JsonSyntaxException("Can't parse value: " + element); + } + } + return result; + } + + public static UpdateAttributesRequest convertToAttributes(JsonElement element) { + return convertToAttributes(element, BasicRequest.DEFAULT_REQUEST_ID); + } + + public static UpdateAttributesRequest convertToAttributes(JsonElement element, int requestId) { + if (element.isJsonObject()) { + BasicUpdateAttributesRequest request = new BasicUpdateAttributesRequest(requestId); + long ts = System.currentTimeMillis(); + request.add(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); + return request; + } else { + throw new JsonSyntaxException("Can't parse value: " + element); + } + } + + public static JsonObject toJson(AttributesKVMsg payload, boolean asMap) { + JsonObject result = new JsonObject(); + if (asMap) { + if (!payload.getClientAttributes().isEmpty()) { + JsonObject attrObject = new JsonObject(); + payload.getClientAttributes().forEach(addToObject(attrObject)); + result.add("client", attrObject); + } + if (!payload.getSharedAttributes().isEmpty()) { + JsonObject attrObject = new JsonObject(); + payload.getSharedAttributes().forEach(addToObject(attrObject)); + result.add("shared", attrObject); + } + } else { + payload.getClientAttributes().forEach(addToObject(result)); + payload.getSharedAttributes().forEach(addToObject(result)); + } + if (!payload.getDeletedAttributes().isEmpty()) { + JsonArray attrObject = new JsonArray(); + payload.getDeletedAttributes().forEach(addToObject(attrObject)); + result.add("deleted", attrObject); + } + return result; + } + + private static Consumer addToObject(JsonArray result) { + return key -> { + result.add(key.getAttributeKey()); + }; + } + + private static Consumer addToObject(JsonObject result) { + return de -> { + JsonPrimitive value; + switch (de.getDataType()) { + case BOOLEAN: + value = new JsonPrimitive(de.getBooleanValue().get()); + break; + case DOUBLE: + value = new JsonPrimitive(de.getDoubleValue().get()); + break; + case LONG: + value = new JsonPrimitive(de.getLongValue().get()); + break; + case STRING: + value = new JsonPrimitive(de.getStrValue().get()); + break; + default: + throw new IllegalArgumentException("Unsupported data type: " + de.getDataType()); + } + result.add(de.getKey(), value); + }; + } + + public static JsonObject toJson(ToDeviceRpcRequestMsg msg, boolean includeRequestId) { + JsonObject result = new JsonObject(); + if (includeRequestId) { + result.addProperty("id", msg.getRequestId()); + } + result.addProperty("method", msg.getMethod()); + result.add("params", new JsonParser().parse(msg.getParams())); + return result; + } + + public static JsonElement toJson(ToServerRpcResponseMsg msg) { + return new JsonParser().parse(msg.getData()); + } + + public static JsonElement toErrorJson(String errorMsg) { + JsonObject error = new JsonObject(); + error.addProperty("error", errorMsg); + return error; + } +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java new file mode 100644 index 00000000000..40d4be9199a --- /dev/null +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.auth; + +import org.thingsboard.server.common.data.id.DeviceId; + +public class DeviceAuthResult { + + private final boolean success; + private final DeviceId deviceId; + private final String errorMsg; + + public static DeviceAuthResult of(DeviceId deviceId) { + return new DeviceAuthResult(true, deviceId, null); + } + + public static DeviceAuthResult of(String errorMsg) { + return new DeviceAuthResult(false, null, errorMsg); + } + + private DeviceAuthResult(boolean success, DeviceId deviceId, String errorMsg) { + super(); + this.success = success; + this.deviceId = deviceId; + this.errorMsg = errorMsg; + } + + public boolean isSuccess() { + return success; + } + + public DeviceId getDeviceId() { + return deviceId; + } + + public String getErrorMsg() { + return errorMsg; + } + + @Override + public String toString() { + return "DeviceAuthResult [success=" + success + ", deviceId=" + deviceId + ", errorMsg=" + errorMsg + "]"; + } + +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java new file mode 100644 index 00000000000..67cdb51e16f --- /dev/null +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.auth; + +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; + +import java.util.Optional; + +public interface DeviceAuthService { + + DeviceAuthResult process(DeviceCredentialsFilter credentials); + + Optional findDeviceById(DeviceId deviceId); + +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java new file mode 100644 index 00000000000..b762ed9078f --- /dev/null +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.transport.session; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; +import org.thingsboard.server.common.data.security.DeviceTokenCredentials; +import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.common.transport.SessionMsgProcessor; +import org.thingsboard.server.common.transport.auth.DeviceAuthResult; +import org.thingsboard.server.common.transport.auth.DeviceAuthService; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public abstract class DeviceAwareSessionContext implements SessionContext { + + protected final DeviceAuthService authService; + protected final SessionMsgProcessor processor; + + protected volatile Device device; + + public DeviceAwareSessionContext(SessionMsgProcessor processor, DeviceAuthService authService) { + this.processor = processor; + this.authService = authService; + } + + public boolean login(DeviceCredentialsFilter credentials) { + DeviceAuthResult result = authService.process(credentials); + if (result.isSuccess()) { + Optional deviceOpt = authService.findDeviceById(result.getDeviceId()); + if (deviceOpt.isPresent()) { + device = deviceOpt.get(); + } + return true; + } else { + log.debug("Can't find device using credentials [{}] due to {}", credentials, result.getErrorMsg()); + return false; + } + } + + public Device getDevice() { + return device; + } +} diff --git a/extensions-api/pom.xml b/extensions-api/pom.xml new file mode 100644 index 00000000000..4af3de3372b --- /dev/null +++ b/extensions-api/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + org.thingsboard + 0.0.1-SNAPSHOT + server + + org.thingsboard.server + extensions-api + jar + + Thingsboard Server Extensions API + http://thingsboard.org + + + UTF-8 + ${basedir}/.. + + + + + org.thingsboard.server.common + data + + + org.thingsboard.server.common + message + + + org.springframework.boot + spring-boot-starter-web + provided + + + com.google.protobuf + protobuf-java + provided + + + com.google.guava + guava + provided + + + org.slf4j + slf4j-api + provided + + + org.slf4j + log4j-over-slf4j + provided + + + ch.qos.logback + logback-core + provided + + + ch.qos.logback + logback-classic + provided + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + + diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Action.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Action.java new file mode 100644 index 00000000000..6dd43270c0e --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Action.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016 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.extensions.api.component; + +import org.thingsboard.server.common.data.plugin.ComponentScope; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Action { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyJsonDescriptor.json"; + + Class configuration() default EmptyComponentConfiguration.class; + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/ConfigurableComponent.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/ConfigurableComponent.java new file mode 100644 index 00000000000..79f728f50e9 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/ConfigurableComponent.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 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.extensions.api.component; + +/** + * @author Andrew Shvayka + */ +public interface ConfigurableComponent { + + void init(T configuration); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/EmptyComponentConfiguration.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/EmptyComponentConfiguration.java new file mode 100644 index 00000000000..f99272e27df --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/EmptyComponentConfiguration.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016 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.extensions.api.component; + +/** + * @author Andrew Shvayka + */ +public class EmptyComponentConfiguration { +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Filter.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Filter.java new file mode 100644 index 00000000000..6c20ae97007 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Filter.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016 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.extensions.api.component; + +import org.thingsboard.server.common.data.plugin.ComponentScope; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Filter { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyJsonDescriptor.json"; + + Class configuration() default EmptyComponentConfiguration.class; + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Plugin.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Plugin.java new file mode 100644 index 00000000000..5c4c6323d33 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Plugin.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016 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.extensions.api.component; + +import org.thingsboard.server.common.data.plugin.ComponentScope; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Plugin { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyJsonDescriptor.json"; + + Class configuration() default EmptyComponentConfiguration.class; + + Class[] actions(); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Processor.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Processor.java new file mode 100644 index 00000000000..d6b1bce3d04 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/component/Processor.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016 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.extensions.api.component; + +import org.thingsboard.server.common.data.plugin.ComponentScope; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Andrew Shvayka + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Processor { + + String name(); + + ComponentScope scope() default ComponentScope.TENANT; + + String descriptor() default "EmptyJsonDescriptor.json"; + + Class configuration() default EmptyComponentConfiguration.class; + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configurable.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configurable.java new file mode 100644 index 00000000000..242beccc14e --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configurable.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 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.extensions.api.configuration; + +public interface Configurable { + + Class getConfigurationClass(); + + void validate(C configuration) throws ConfigurationValidationException; + + void configure(C configuration); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configuration.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configuration.java new file mode 100644 index 00000000000..3c2ba30bb24 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/Configuration.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016 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.extensions.api.configuration; + +import java.io.Serializable; + +public interface Configuration extends Serializable { + + // TODO: Figure out how to define and visualize configurations + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/ConfigurationValidationException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/ConfigurationValidationException.java new file mode 100644 index 00000000000..416668c5225 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/configuration/ConfigurationValidationException.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 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.extensions.api.configuration; + +public class ConfigurationValidationException extends Exception { + + private static final long serialVersionUID = 1L; + + public ConfigurationValidationException(String msg, Exception cause){ + super(msg, cause); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributes.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributes.java new file mode 100644 index 00000000000..4f2d7608893 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributes.java @@ -0,0 +1,68 @@ +/** + * Copyright © 2016 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.extensions.api.device; + +import org.thingsboard.server.common.data.kv.AttributeKvEntry; + +import java.util.*; + +/** + * @author Andrew Shvayka + */ +public class DeviceAttributes { + + private final Map clientSideAttributesMap; + private final Map serverPrivateAttributesMap; + private final Map serverPublicAttributesMap; + + public DeviceAttributes(List clientSideAttributes, List serverPrivateAttributes, List serverPublicAttributes) { + this.clientSideAttributesMap = mapAttributes(clientSideAttributes); + this.serverPrivateAttributesMap = mapAttributes(serverPrivateAttributes); + this.serverPublicAttributesMap = mapAttributes(serverPublicAttributes); + } + + private static Map mapAttributes(List attributes) { + Map result = new HashMap<>(); + for (AttributeKvEntry attribute : attributes) { + result.put(attribute.getKey(), attribute); + } + return result; + } + + public Collection getClientSideAttributes() { + return clientSideAttributesMap.values(); + } + + public Collection getServerSideAttributes() { + return serverPrivateAttributesMap.values(); + } + + public Collection getServerSidePublicAttributes() { + return serverPublicAttributesMap.values(); + } + + public Optional getClientSideAttribute(String attribute) { + return Optional.ofNullable(clientSideAttributesMap.get(attribute)); + } + + public Optional getServerPrivateAttribute(String attribute) { + return Optional.ofNullable(serverPrivateAttributesMap.get(attribute)); + } + + public Optional getServerPublicAttribute(String attribute) { + return Optional.ofNullable(serverPublicAttributesMap.get(attribute)); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java new file mode 100644 index 00000000000..ebeb58fca79 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016 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.extensions.api.device; + +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKey; + +import java.util.Set; + +/** + * @author Andrew Shvayka + */ +@ToString +public class DeviceAttributesEventNotificationMsg implements ToDeviceActorNotificationMsg { + + @Getter private final TenantId tenantId; + @Getter private final DeviceId deviceId; + @Getter private final Set keys; + @Getter private final boolean deleted; + + public static DeviceAttributesEventNotificationMsg onUpdate(TenantId tenantId, DeviceId deviceId, Set keys) { + return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keys, false); + } + + public static DeviceAttributesEventNotificationMsg onDelete(TenantId tenantId, DeviceId deviceId, Set keys) { + return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keys, true); + } + + private DeviceAttributesEventNotificationMsg(TenantId tenantId, DeviceId deviceId, Set keys, boolean deleted) { + this.tenantId = tenantId; + this.deviceId = deviceId; + this.keys = keys; + this.deleted = deleted; + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java new file mode 100644 index 00000000000..2236f149998 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.extensions.api.device; + +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; +import org.thingsboard.server.common.msg.aware.TenantAwareMsg; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +public interface ToDeviceActorNotificationMsg extends TenantAwareMsg, DeviceAwareMsg, Serializable { + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java new file mode 100644 index 00000000000..c20b91d2b1f --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java @@ -0,0 +1,88 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.extensions.api.plugins.handlers.*; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; + +/** + * @author Andrew Shvayka + */ +public abstract class AbstractPlugin implements Plugin { + + @Override + public void process(PluginContext ctx, PluginWebsocketMsg wsMsg) { + getWebsocketMsgHandler().process(ctx, wsMsg); + } + + @Override + public void process(PluginContext ctx, PluginRestMsg msg) { + getRestMsgHandler().process(ctx, msg); + } + + @Override + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) throws RuleException { + getRuleMsgHandler().process(ctx, tenantId, ruleId, msg); + } + + @Override + public void process(PluginContext ctx, RpcMsg msg) { + getRpcMsgHandler().process(ctx, msg); + } + + @Override + public void process(PluginContext ctx, FromDeviceRpcResponse msg) { + throw new IllegalStateException("Device RPC messages is not handled in current plugin!"); + } + + @Override + public void process(PluginContext ctx, TimeoutMsg msg) { + throw new IllegalStateException("Timeouts is not handled in current plugin!"); + } + + @Override + public void onServerAdded(PluginContext ctx, ServerAddress server) { + } + + @Override + public void onServerRemoved(PluginContext ctx, ServerAddress server) { + } + + protected RuleMsgHandler getRuleMsgHandler() { + return new DefaultRuleMsgHandler(); + } + + protected RestMsgHandler getRestMsgHandler() { + return new DefaultRestMsgHandler(); + } + + protected WebsocketMsgHandler getWebsocketMsgHandler() { + return new DefaultWebsocketMsgHandler(); + } + + protected RpcMsgHandler getRpcMsgHandler() { + return new DefaultRpcMsgHandler(); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java new file mode 100644 index 00000000000..15c12d43e25 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java @@ -0,0 +1,54 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.extensions.api.component.ConfigurableComponent; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; + +public interface Plugin extends ConfigurableComponent { + + void process(PluginContext ctx, PluginWebsocketMsg wsMsg); + + void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) throws RuleException; + + void process(PluginContext ctx, PluginRestMsg msg); + + void process(PluginContext ctx, RpcMsg msg); + + void process(PluginContext ctx, FromDeviceRpcResponse msg); + + void process(PluginContext ctx, TimeoutMsg msg); + + void onServerAdded(PluginContext ctx, ServerAddress server); + + void onServerRemoved(PluginContext ctx, ServerAddress server); + + void resume(PluginContext ctx); + + void suspend(PluginContext ctx); + + void stop(PluginContext ctx); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java new file mode 100644 index 00000000000..2d71f1af616 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; +import org.thingsboard.server.extensions.api.component.ConfigurableComponent; +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleContext; +import org.thingsboard.server.extensions.api.rules.RuleLifecycleComponent; +import org.thingsboard.server.extensions.api.rules.RuleProcessingMetaData; + +import java.util.Optional; + +public interface PluginAction extends ConfigurableComponent, RuleLifecycleComponent { + + Optional> convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd); + + Optional convert(PluginToRuleMsg response); + + boolean isOneWayAction(); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginApiCallSecurityContext.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginApiCallSecurityContext.java new file mode 100644 index 00000000000..ff025673b77 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginApiCallSecurityContext.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.io.Serializable; + +public final class PluginApiCallSecurityContext implements Serializable { + + private static final long serialVersionUID = 1L; + + private final TenantId pluginTenantId; + private final PluginId pluginId; + private final TenantId tenantId; + private final CustomerId customerId; + + public PluginApiCallSecurityContext(TenantId pluginTenantId, PluginId pluginId, TenantId tenantId, CustomerId customerId) { + super(); + this.pluginTenantId = pluginTenantId; + this.pluginId = pluginId; + this.tenantId = tenantId; + this.customerId = customerId; + } + + public TenantId getPluginTenantId(){ + return pluginTenantId; + } + + public PluginId getPluginId() { + return pluginId; + } + + public boolean isSystemAdmin() { + return tenantId == null || EntityId.NULL_UUID.equals(tenantId.getId()); + } + + public boolean isTenantAdmin() { + return !isSystemAdmin() && (customerId == null || EntityId.NULL_UUID.equals(customerId.getId())); + } + + public boolean isCustomerUser() { + return !isSystemAdmin() && !isTenantAdmin(); + } + + public TenantId getTenantId() { + return tenantId; + } + + public CustomerId getCustomerId() { + return customerId; + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginCallback.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginCallback.java new file mode 100644 index 00000000000..281a3af0033 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginCallback.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +/** + * @author Andrew Shvayka + */ +public interface PluginCallback { + + void onSuccess(PluginContext ctx, T value); + + void onFailure(PluginContext ctx, Exception e); +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java new file mode 100644 index 00000000000..13a956192c4 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginConstants.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +/** + * @author Andrew Shvayka + */ +public class PluginConstants { + public static final String PLUGIN_URL_PREFIX = "/api/plugins"; +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java new file mode 100644 index 00000000000..834ec3a1205 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.PluginId; +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.kv.TsKvQuery; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +public interface PluginContext { + + PluginId getPluginId(); + + void reply(PluginToRuleMsg msg); + + boolean checkAccess(DeviceId deviceId); + + Optional getSecurityCtx(); + + /* + Device RPC API + */ + + Optional resolve(DeviceId deviceId); + + void getDevice(DeviceId deviceId, PluginCallback pluginCallback); + + void sendRpcRequest(ToDeviceRpcRequest msg); + + void scheduleTimeoutMsg(TimeoutMsg timeoutMsg); + + + /* + Websocket API + */ + + void send(PluginWebsocketMsg wsMsg) throws IOException; + + void close(PluginWebsocketSessionRef sessionRef) throws IOException; + + /* + Plugin RPC API + */ + + void sendPluginRpcMsg(RpcMsg msg); + + /* + Timeseries API + */ + + + void saveTsData(DeviceId deviceId, TsKvEntry entry, PluginCallback callback); + + void saveTsData(DeviceId deviceId, List entry, PluginCallback callback); + + List loadTimeseries(DeviceId deviceId, TsKvQuery query); + + void loadLatestTimeseries(DeviceId deviceId, Collection keys, PluginCallback> callback); + + void loadLatestTimeseries(DeviceId deviceId, PluginCallback> callback); + + /* + Attributes API + */ + + void saveAttributes(DeviceId deviceId, String attributeType, List attributes, PluginCallback callback); + + Optional loadAttribute(DeviceId deviceId, String attributeType, String attributeKey); + + List loadAttributes(DeviceId deviceId, String attributeType, List attributeKeys); + + List loadAttributes(DeviceId deviceId, String attributeType); + + void removeAttributes(DeviceId deviceId, String scope, List attributeKeys); + + void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback> callback); +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginException.java new file mode 100644 index 00000000000..5f39ef94d7a --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginException.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +public class PluginException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public PluginException(String msg, Exception e) { + super(msg, e); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginInitializationException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginInitializationException.java new file mode 100644 index 00000000000..e4eb4328428 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginInitializationException.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.extensions.api.plugins; + +public class PluginInitializationException extends PluginException { + + private static final long serialVersionUID = 1L; + + public PluginInitializationException(String msg, Exception e) { + super(msg, e); + } + + public PluginInitializationException(String msg) { + super(msg, null); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRestMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRestMsgHandler.java new file mode 100644 index 00000000000..c9b27f74f21 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRestMsgHandler.java @@ -0,0 +1,72 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; + +import javax.servlet.ServletException; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public class DefaultRestMsgHandler implements RestMsgHandler { + + protected final ObjectMapper jsonMapper = new ObjectMapper(); + + @Override + public void process(PluginContext ctx, PluginRestMsg msg) { + try { + log.debug("[{}] Processing REST msg: {}", ctx.getPluginId(), msg); + HttpMethod method = msg.getRequest().getMethod(); + switch (method) { + case GET: + handleHttpGetRequest(ctx, msg); + break; + case POST: + handleHttpPostRequest(ctx, msg); + break; + case DELETE: + handleHttpDeleteRequest(ctx, msg); + break; + default: + msg.getResponseHolder().setErrorResult(new HttpRequestMethodNotSupportedException(method.name())); + } + log.debug("[{}] Processed REST msg.", ctx.getPluginId()); + } catch (Exception e) { + log.warn("[{}] Exception during REST msg processing: {}", ctx.getPluginId(), e.getMessage(), e); + msg.getResponseHolder().setErrorResult(e); + } + } + + protected void handleHttpGetRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException { + msg.getResponseHolder().setErrorResult(new HttpRequestMethodNotSupportedException(HttpMethod.GET.name())); + } + + protected void handleHttpPostRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException { + msg.getResponseHolder().setErrorResult(new HttpRequestMethodNotSupportedException(HttpMethod.POST.name())); + } + + protected void handleHttpDeleteRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException { + msg.getResponseHolder().setErrorResult(new HttpRequestMethodNotSupportedException(HttpMethod.DELETE.name())); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRpcMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRpcMsgHandler.java new file mode 100644 index 00000000000..c428cdb2b7d --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRpcMsgHandler.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; + +/** + * @author Andrew Shvayka + */ +public class DefaultRpcMsgHandler implements RpcMsgHandler { + + @Override + public void process(PluginContext ctx, RpcMsg msg) { + throw new RuntimeException("Not registered msg type: " + msg.getMsgClazz() + "!"); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java new file mode 100644 index 00000000000..6550f873650 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.msg.GetAttributesRequestRuleToPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.TelemetryUploadRequestRuleToPluginMsg; +import org.thingsboard.server.extensions.api.plugins.msg.UpdateAttributesRequestRuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public class DefaultRuleMsgHandler implements RuleMsgHandler { + + @Override + public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) throws RuleException { + if (msg instanceof TelemetryUploadRequestRuleToPluginMsg) { + handleTelemetryUploadRequest(ctx, tenantId, ruleId, (TelemetryUploadRequestRuleToPluginMsg) msg); + } else if (msg instanceof UpdateAttributesRequestRuleToPluginMsg) { + handleUpdateAttributesRequest(ctx, tenantId, ruleId, (UpdateAttributesRequestRuleToPluginMsg) msg); + } else if (msg instanceof GetAttributesRequestRuleToPluginMsg) { + handleGetAttributesRequest(ctx, tenantId, ruleId, (GetAttributesRequestRuleToPluginMsg) msg); + } + //TODO: handle subscriptions to attribute updates. + } + + protected void handleGetAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, GetAttributesRequestRuleToPluginMsg msg) { + msgTypeNotSupported(msg.getPayload().getMsgType()); + } + + protected void handleUpdateAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, UpdateAttributesRequestRuleToPluginMsg msg) { + msgTypeNotSupported(msg.getPayload().getMsgType()); + } + + protected void handleTelemetryUploadRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, TelemetryUploadRequestRuleToPluginMsg msg) { + msgTypeNotSupported(msg.getPayload().getMsgType()); + } + + private void msgTypeNotSupported(MsgType msgType) { + throw new RuntimeException("Not supported msg type: " + msgType + "!"); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultWebsocketMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultWebsocketMsgHandler.java new file mode 100644 index 00000000000..fab11bbc07f --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultWebsocketMsgHandler.java @@ -0,0 +1,103 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; +import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent; +import org.thingsboard.server.extensions.api.plugins.ws.WsSessionMetaData; +import org.thingsboard.server.extensions.api.plugins.ws.msg.*; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public class DefaultWebsocketMsgHandler implements WebsocketMsgHandler { + + protected final ObjectMapper jsonMapper = new ObjectMapper(); + + protected final Map wsSessionsMap = new HashMap<>(); + + @Override + public void process(PluginContext ctx, PluginWebsocketMsg wsMsg) { + PluginWebsocketSessionRef sessionRef = wsMsg.getSessionRef(); + if (log.isTraceEnabled()) { + log.trace("[{}] Processing: {}", sessionRef.getSessionId(), wsMsg); + } else { + log.debug("[{}] Processing: {}", sessionRef.getSessionId(), wsMsg.getClass().getSimpleName()); + } + if (wsMsg instanceof SessionEventPluginWebSocketMsg) { + handleWebSocketSessionEvent(ctx, sessionRef, (SessionEventPluginWebSocketMsg) wsMsg); + } else if (wsMsg instanceof TextPluginWebSocketMsg || wsMsg instanceof BinaryPluginWebSocketMsg) { + handleWebSocketMsg(ctx, sessionRef, wsMsg); + } else if (wsMsg instanceof PongPluginWebsocketMsg) { + handleWebSocketPongEvent(ctx, sessionRef); + } + } + + protected void handleWebSocketMsg(PluginContext ctx, PluginWebsocketSessionRef sessionRef, PluginWebsocketMsg wsMsg) { + throw new RuntimeException("Web-sockets are not supported by current plugin!"); + } + + protected void cleanupWebSocketSession(PluginContext ctx, String sessionId) { + + } + + protected void handleWebSocketSessionEvent(PluginContext ctx, PluginWebsocketSessionRef sessionRef, SessionEventPluginWebSocketMsg wsMsg) { + String sessionId = sessionRef.getSessionId(); + SessionEvent event = wsMsg.getPayload(); + log.debug("[{}] Processing: {}", sessionId, event); + switch (event.getEventType()) { + case ESTABLISHED: + wsSessionsMap.put(sessionId, new WsSessionMetaData(sessionRef)); + break; + case ERROR: + log.debug("[{}] Unknown websocket session error: {}. ", sessionId, event.getError().get()); + break; + case CLOSED: + wsSessionsMap.remove(sessionId); + cleanupWebSocketSession(ctx, sessionId); + break; + } + } + + protected void handleWebSocketPongEvent(PluginContext ctx, PluginWebsocketSessionRef sessionRef) { + String sessionId = sessionRef.getSessionId(); + WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); + if (sessionMD != null) { + log.debug("[{}] Updating session metadata: {}", sessionId, sessionRef); + sessionMD.setSessionRef(sessionRef); + sessionMD.setLastActivityTime(System.currentTimeMillis()); + } + } + + public void clear(PluginContext ctx) { + wsSessionsMap.values().stream().forEach(v -> { + try { + ctx.close(v.getSessionRef()); + } catch (IOException e) { + log.debug("[{}] Failed to close session: {}", v.getSessionRef().getSessionId(), e.getMessage(), e); + } + }); + wsSessionsMap.clear(); + } +} \ No newline at end of file diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RestMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RestMsgHandler.java new file mode 100644 index 00000000000..d91ba7ae201 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RestMsgHandler.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; + +/** + * @author Andrew Shvayka + */ +public interface RestMsgHandler { + + void process(PluginContext ctx, PluginRestMsg msg); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RpcMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RpcMsgHandler.java new file mode 100644 index 00000000000..42aeb81a1cd --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RpcMsgHandler.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; + +/** + * @author Andrew Shvayka + */ +public interface RpcMsgHandler { + + void process(PluginContext ctx, RpcMsg msg); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RuleMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RuleMsgHandler.java new file mode 100644 index 00000000000..c3565757b7c --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/RuleMsgHandler.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleException; + +/** + * @author Andrew Shvayka + */ +public interface RuleMsgHandler { + + void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) throws RuleException; + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/WebsocketMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/WebsocketMsgHandler.java new file mode 100644 index 00000000000..8526ec5f5f4 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/WebsocketMsgHandler.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.handlers; + +import org.thingsboard.server.extensions.api.plugins.PluginContext; +import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; + +/** + * @author Andrew Shvayka + */ +public interface WebsocketMsgHandler { + + void process(PluginContext ctx, PluginWebsocketMsg wsMsg); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractPluginToRuleMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractPluginToRuleMsg.java new file mode 100644 index 00000000000..398f5a303b6 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractPluginToRuleMsg.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.io.Serializable; +import java.util.UUID; + +public class AbstractPluginToRuleMsg implements PluginToRuleMsg { + + private static final long serialVersionUID = 1L; + + private final UUID uid; + private final TenantId tenantId; + private final RuleId ruleId; + private final T payload; + + public AbstractPluginToRuleMsg(UUID uid, TenantId tenantId, RuleId ruleId, T payload) { + super(); + this.uid = uid; + this.tenantId = tenantId; + this.ruleId = ruleId; + this.payload = payload; + } + + @Override + public UUID getUid() { + return uid; + } + + @Override + public TenantId getTenantId() { + return tenantId; + } + + @Override + public T getPayload() { + return payload; + } + + @Override + public RuleId getRuleId() { + return ruleId; + } + + + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractRuleToPluginMsg.java new file mode 100644 index 00000000000..ead9e9db98a --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/AbstractRuleToPluginMsg.java @@ -0,0 +1,72 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +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 java.io.Serializable; +import java.util.UUID; + +public abstract class AbstractRuleToPluginMsg implements RuleToPluginMsg { + + private static final long serialVersionUID = 1L; + + private final UUID uid; + private final TenantId tenantId; + private final CustomerId customerId; + private final DeviceId deviceId; + private final T payload; + + public AbstractRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, T payload) { + super(); + this.uid = UUID.randomUUID(); + this.tenantId = tenantId; + this.customerId = customerId; + this.deviceId = deviceId; + this.payload = payload; + } + + @Override + public UUID getUid() { + return uid; + } + + public TenantId getTenantId() { + return tenantId; + } + + public CustomerId getCustomerId() { + return customerId; + } + + @Override + public DeviceId getDeviceId() { + return deviceId; + } + + public T getPayload() { + return payload; + } + + @Override + public String toString() { + return "AbstractRuleToPluginMsg [uid=" + uid + ", tenantId=" + tenantId + ", customerId=" + customerId + + ", deviceId=" + deviceId + ", payload=" + payload + "]"; + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/FromDeviceRpcResponse.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/FromDeviceRpcResponse.java new file mode 100644 index 00000000000..f9e18834fbd --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/FromDeviceRpcResponse.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@RequiredArgsConstructor +@ToString +public class FromDeviceRpcResponse { + @Getter + private final UUID id; + private final String response; + private final RpcError error; + + public Optional getResponse() { + return Optional.ofNullable(response); + } + + public Optional getError() { + return Optional.ofNullable(error); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetAttributesRequestRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetAttributesRequestRuleToPluginMsg.java new file mode 100644 index 00000000000..4c821f69f37 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetAttributesRequestRuleToPluginMsg.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +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.msg.core.GetAttributesRequest; + +/** + * @author Andrew Shvayka + */ +public class GetAttributesRequestRuleToPluginMsg extends AbstractRuleToPluginMsg { + + private static final long serialVersionUID = 1L; + + public GetAttributesRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, GetAttributesRequest payload) { + super(tenantId, customerId, deviceId, payload); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetRequestRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetRequestRuleToPluginMsg.java new file mode 100644 index 00000000000..c824a3f1442 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/GetRequestRuleToPluginMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; + +public class GetRequestRuleToPluginMsg extends AbstractRuleToPluginMsg{ + + private static final long serialVersionUID = 1L; + + public GetRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, + String[] payload) { + super(tenantId, customerId, deviceId, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/PluginToRuleMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/PluginToRuleMsg.java new file mode 100644 index 00000000000..a6bd6e5ae81 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/PluginToRuleMsg.java @@ -0,0 +1,64 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg; + +import java.io.Serializable; +import java.util.UUID; + +/** + * The basic interface for messages that are sent from particular plugin to rule + * instance + * + * @author ashvayka + * @see RuleToPluginMsg + * + */ +public interface PluginToRuleMsg extends ToRuleActorMsg, Serializable { + + /** + * Returns the unique identifier of the message + * + * @return unique identifier of the message. + */ + UUID getUid(); + + /** + * Returns the unique identifier of the tenant that owns the rule + * + * @return unique identifier of the tenant that owns the rule. + * + */ + TenantId getTenantId(); + + /** + * Returns the unique identifier of the rule + * + * @return unique identifier of the rule. + */ + RuleId getRuleId(); + + /** + * Returns the serializable message payload. + * + * @return the serializable message payload. + */ + V getPayload(); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ResponsePluginToRuleMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ResponsePluginToRuleMsg.java new file mode 100644 index 00000000000..6768042963f --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ResponsePluginToRuleMsg.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; + +import java.util.UUID; + +public class ResponsePluginToRuleMsg extends AbstractPluginToRuleMsg{ + + private static final long serialVersionUID = 1L; + + public ResponsePluginToRuleMsg(UUID uid, TenantId tenantId, RuleId ruleId, ToDeviceMsg payload) { + super(uid, tenantId, ruleId, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcError.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcError.java new file mode 100644 index 00000000000..1f3768e8a2c --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcError.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +/** + * @author Andrew Shvayka + */ +public enum RpcError { + NOT_FOUND, FORBIDDEN, NO_ACTIVE_CONNECTION, TIMEOUT, INTERNAL; +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcRequestRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcRequestRuleToPluginMsg.java new file mode 100644 index 00000000000..8826979fc5b --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcRequestRuleToPluginMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +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.msg.core.ToServerRpcRequestMsg; + +public class RpcRequestRuleToPluginMsg extends AbstractRuleToPluginMsg { + + private static final long serialVersionUID = 1L; + + public RpcRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, ToServerRpcRequestMsg payload) { + super(tenantId, customerId, deviceId, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcResponsePluginToRuleMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcResponsePluginToRuleMsg.java new file mode 100644 index 00000000000..6ddd56a6af6 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RpcResponsePluginToRuleMsg.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; + +import java.util.UUID; + +public class RpcResponsePluginToRuleMsg extends AbstractPluginToRuleMsg { + + private static final long serialVersionUID = 1L; + + public RpcResponsePluginToRuleMsg(UUID uid, TenantId tenantId, RuleId ruleId, ToServerRpcResponseMsg payload) { + super(uid, tenantId, ruleId, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RuleToPluginMsg.java new file mode 100644 index 00000000000..548bf8ec0b9 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/RuleToPluginMsg.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; + +import java.io.Serializable; +import java.util.UUID; + +/** + * The basic interface for messages that are sent from particular rule to plugin + * instance + * + * @author ashvayka + * + */ +public interface RuleToPluginMsg extends Serializable { + + /** + * Returns the unique identifier of the message + * + * @return unique identifier of the message. + */ + UUID getUid(); + + + /** + * Returns the unique identifier of the device that send the message + * + * @return unique identifier of the device. + */ + DeviceId getDeviceId(); + + /** + * Returns the unique identifier of the customer that owns the device + * + * @return unique identifier of the device. + */ + CustomerId getCustomerId(); + + /** + * Returns the serializable message payload. + * + * @return the serializable message payload. + */ + V getPayload(); +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TelemetryUploadRequestRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TelemetryUploadRequestRuleToPluginMsg.java new file mode 100644 index 00000000000..c72cdb3f164 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TelemetryUploadRequestRuleToPluginMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +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.msg.core.TelemetryUploadRequest; + +public class TelemetryUploadRequestRuleToPluginMsg extends AbstractRuleToPluginMsg { + + private static final long serialVersionUID = 1L; + + public TelemetryUploadRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, TelemetryUploadRequest payload) { + super(tenantId, customerId, deviceId, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutIntMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutIntMsg.java new file mode 100644 index 00000000000..6d96ae7609c --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutIntMsg.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +/** + * @author Andrew Shvayka + */ +public final class TimeoutIntMsg extends TimeoutMsg { + + public TimeoutIntMsg(Integer id, long timeout) { + super(id, timeout); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutMsg.java new file mode 100644 index 00000000000..8932c334665 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutMsg.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import lombok.Data; + +/** + * @author Andrew Shvayka + */ +@Data +public class TimeoutMsg { + private final T id; + private final long timeout; +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutUUIDMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutUUIDMsg.java new file mode 100644 index 00000000000..47b00a24f18 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutUUIDMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +public final class TimeoutUUIDMsg extends TimeoutMsg { + + public TimeoutUUIDMsg(UUID id, long timeout) { + super(id, timeout); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java new file mode 100644 index 00000000000..3fdc076ede0 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.io.Serializable; +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToDeviceRpcRequest implements Serializable { + private final UUID id; + private final TenantId tenantId; + private final DeviceId deviceId; + private final boolean oneway; + private final long expirationTime; + private final ToDeviceRpcRequestBody body; +} + diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java new file mode 100644 index 00000000000..c37e5762df5 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToDeviceRpcRequestBody implements Serializable { + private final String method; + private final String params; +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java new file mode 100644 index 00000000000..c19c9dc29cb --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@ToString +@RequiredArgsConstructor +public class ToDeviceRpcRequestPluginMsg implements ToDeviceActorNotificationMsg { + + private final ServerAddress serverAddress; + @Getter + private final PluginId pluginId; + @Getter + private final TenantId pluginTenantId; + @Getter + private final ToDeviceRpcRequest msg; + + public ToDeviceRpcRequestPluginMsg(PluginId pluginId, TenantId pluginTenantId, ToDeviceRpcRequest msg) { + this(null, pluginId, pluginTenantId, msg); + } + + public Optional getServerAddress() { + return Optional.ofNullable(serverAddress); + } + + @Override + public DeviceId getDeviceId() { + return msg.getDeviceId(); + } + + @Override + public TenantId getTenantId() { + return msg.getTenantId(); + } +} + diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginActorMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginActorMsg.java new file mode 100644 index 00000000000..22a4fafd2aa --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginActorMsg.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.aware.PluginAwareMsg; + +public interface ToPluginActorMsg extends PluginAwareMsg { + + TenantId getPluginTenantId(); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginRpcResponseDeviceMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginRpcResponseDeviceMsg.java new file mode 100644 index 00000000000..f761850a701 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToPluginRpcResponseDeviceMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +import lombok.Data; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToPluginRpcResponseDeviceMsg implements ToPluginActorMsg { + private final PluginId pluginId; + private final TenantId pluginTenantId; + private final FromDeviceRpcResponse response; +} + diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java new file mode 100644 index 00000000000..a427d65713f --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.msg; + +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.msg.core.UpdateAttributesRequest; + +public class UpdateAttributesRequestRuleToPluginMsg extends AbstractRuleToPluginMsg { + + private static final long serialVersionUID = 1L; + + public UpdateAttributesRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, UpdateAttributesRequest payload) { + super(tenantId, customerId, deviceId, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/BasicPluginRestMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/BasicPluginRestMsg.java new file mode 100644 index 00000000000..3a176386e71 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/BasicPluginRestMsg.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.rest; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; + +@SuppressWarnings("rawtypes") +public class BasicPluginRestMsg implements PluginRestMsg { + + private final PluginApiCallSecurityContext securityCtx; + private final RestRequest request; + private final DeferredResult responseHolder; + + public BasicPluginRestMsg(PluginApiCallSecurityContext securityCtx, RestRequest request, + DeferredResult responseHolder) { + super(); + this.securityCtx = securityCtx; + this.request = request; + this.responseHolder = responseHolder; + } + + @Override + public PluginApiCallSecurityContext getSecurityCtx() { + return securityCtx; + } + + @Override + public RestRequest getRequest() { + return request; + } + + @Override + public DeferredResult getResponseHolder() { + return responseHolder; + } + + @Override + public PluginId getPluginId() { + return securityCtx.getPluginId(); + } + + @Override + public TenantId getPluginTenantId() { + return securityCtx.getPluginTenantId(); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/PluginRestMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/PluginRestMsg.java new file mode 100644 index 00000000000..b2ba689e495 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/PluginRestMsg.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.rest; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; + +@SuppressWarnings("rawtypes") +public interface PluginRestMsg extends ToPluginActorMsg { + + RestRequest getRequest(); + + DeferredResult getResponseHolder(); + + PluginApiCallSecurityContext getSecurityCtx(); + + @Override + default PluginId getPluginId() { + return getSecurityCtx().getPluginId(); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/RestRequest.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/RestRequest.java new file mode 100644 index 00000000000..c9aaefd8e54 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rest/RestRequest.java @@ -0,0 +1,87 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.rest; + +import lombok.Data; +import org.springframework.http.HttpMethod; +import org.springframework.http.RequestEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.thingsboard.server.extensions.api.plugins.PluginConstants; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.Charset; +import java.util.Optional; +import java.util.function.Function; + +@Data +public class RestRequest { + private static final Charset UTF8 = Charset.forName("UTF-8"); + private final RequestEntity requestEntity; + private final HttpServletRequest request; + + public HttpMethod getMethod() { + return requestEntity.getMethod(); + } + + public String getRequestBody() { + return new String(requestEntity.getBody(), UTF8); + } + + public String[] getPathParams() { + String requestUrl = request.getRequestURL().toString(); + int index = requestUrl.indexOf(PluginConstants.PLUGIN_URL_PREFIX); + String[] pathParams = requestUrl.substring(index + PluginConstants.PLUGIN_URL_PREFIX.length()).split("/"); + String[] result = new String[pathParams.length - 2]; + System.arraycopy(pathParams, 2, result, 0, result.length); + return result; + } + + public String getParameter(String paramName) throws ServletException { + return getParameter(paramName, null); + } + + public String getParameter(String paramName, String defaultValue) throws ServletException { + String paramValue = request.getParameter(paramName); + if (StringUtils.isEmpty(paramValue)) { + if (defaultValue == null) { + throw new MissingServletRequestParameterException(paramName, "String"); + } else { + return defaultValue; + } + } else { + return paramValue; + } + } + + public Optional getLongParamValue(String paramName) { + return getParamValue(paramName, s -> Long.valueOf(s)); + } + + public Optional getIntParamValue(String paramName) { + return getParamValue(paramName, s -> Integer.valueOf(s)); + } + + public Optional getParamValue(String paramName, Function function) { + String paramValue = request.getParameter(paramName); + if (paramValue != null) { + return Optional.of(function.apply(paramValue)); + } else { + return Optional.empty(); + } + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/PluginRpcMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/PluginRpcMsg.java new file mode 100644 index 00000000000..7aaaf01b63f --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/PluginRpcMsg.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.rpc; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; + +@ToString +@RequiredArgsConstructor +public class PluginRpcMsg implements ToPluginActorMsg { + + private final TenantId tenantId; + private final PluginId pluginId; + @Getter + private final RpcMsg rpcMsg; + + @Override + public TenantId getPluginTenantId() { + return tenantId; + } + + @Override + public PluginId getPluginId() { + return pluginId; + } + + + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/RpcMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/RpcMsg.java new file mode 100644 index 00000000000..71a8534b4b0 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/rpc/RpcMsg.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.rpc; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.msg.cluster.ServerAddress; + +/** + * @author Andrew Shvayka + */ +@ToString +@RequiredArgsConstructor +public class RpcMsg { + @Getter + private final ServerAddress serverAddress; + @Getter + private final int msgClazz; + @Getter + private final byte[] msgData; +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/BasicPluginWebsocketSessionRef.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/BasicPluginWebsocketSessionRef.java new file mode 100644 index 00000000000..2f4c582e173 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/BasicPluginWebsocketSessionRef.java @@ -0,0 +1,111 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws; + +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.Map; + +public class BasicPluginWebsocketSessionRef implements PluginWebsocketSessionRef { + + private static final long serialVersionUID = 1L; + + private final String sessionId; + private final PluginApiCallSecurityContext securityCtx; + private final URI uri; + private final Map attributes; + private final InetSocketAddress localAddress; + private final InetSocketAddress remoteAddress; + + public BasicPluginWebsocketSessionRef(String sessionId, PluginApiCallSecurityContext securityCtx, URI uri, Map attributes, + InetSocketAddress localAddress, InetSocketAddress remoteAddress) { + super(); + this.sessionId = sessionId; + this.securityCtx = securityCtx; + this.uri = uri; + this.attributes = attributes; + this.localAddress = localAddress; + this.remoteAddress = remoteAddress; + } + + public String getSessionId() { + return sessionId; + } + + public TenantId getPluginTenantId() { + return securityCtx.getPluginTenantId(); + } + + public PluginId getPluginId() { + return securityCtx.getPluginId(); + } + + public URI getUri() { + return uri; + } + + public Map getAttributes() { + return attributes; + } + + public InetSocketAddress getLocalAddress() { + return localAddress; + } + + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BasicPluginWebsocketSessionRef other = (BasicPluginWebsocketSessionRef) obj; + if (sessionId == null) { + if (other.sessionId != null) + return false; + } else if (!sessionId.equals(other.sessionId)) + return false; + return true; + } + + @Override + public String toString() { + return "BasicPluginWebsocketSessionRef [sessionId=" + sessionId + ", pluginId=" + getPluginId() + "]"; + } + + @Override + public PluginApiCallSecurityContext getSecurityCtx() { + return securityCtx; + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/PluginWebsocketSessionRef.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/PluginWebsocketSessionRef.java new file mode 100644 index 00000000000..7079c8283e7 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/PluginWebsocketSessionRef.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws; + +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; + +import java.io.Serializable; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.Map; + +public interface PluginWebsocketSessionRef extends Serializable { + + String getSessionId(); + + TenantId getPluginTenantId(); + + PluginId getPluginId(); + + URI getUri(); + + Map getAttributes(); + + InetSocketAddress getLocalAddress(); + + InetSocketAddress getRemoteAddress(); + + PluginApiCallSecurityContext getSecurityCtx(); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/SessionEvent.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/SessionEvent.java new file mode 100644 index 00000000000..66d3f271450 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/SessionEvent.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws; + +import lombok.Getter; +import lombok.ToString; + +import java.util.Optional; + +@ToString +public class SessionEvent { + + public enum SessionEventType { + ESTABLISHED, CLOSED, ERROR + }; + + @Getter + private final SessionEventType eventType; + @Getter + private final Optional error; + + private SessionEvent(SessionEventType eventType, Throwable error) { + super(); + this.eventType = eventType; + this.error = Optional.ofNullable(error); + } + + public static SessionEvent onEstablished() { + return new SessionEvent(SessionEventType.ESTABLISHED, null); + } + + public static SessionEvent onClosed() { + return new SessionEvent(SessionEventType.CLOSED, null); + } + + public static SessionEvent onError(Throwable t) { + return new SessionEvent(SessionEventType.ERROR, t); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/WsSessionMetaData.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/WsSessionMetaData.java new file mode 100644 index 00000000000..0ee8830537e --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/WsSessionMetaData.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws; + +public class WsSessionMetaData { + + private PluginWebsocketSessionRef sessionRef; + private long lastActivityTime; + + public WsSessionMetaData(PluginWebsocketSessionRef sessionRef) { + super(); + this.sessionRef = sessionRef; + this.lastActivityTime = System.currentTimeMillis(); + } + + public PluginWebsocketSessionRef getSessionRef() { + return sessionRef; + } + + public void setSessionRef(PluginWebsocketSessionRef sessionRef) { + this.sessionRef = sessionRef; + } + + public long getLastActivityTime() { + return lastActivityTime; + } + + public void setLastActivityTime(long lastActivityTime) { + this.lastActivityTime = lastActivityTime; + } + + @Override + public String toString() { + return "WsSessionMetaData [sessionRef=" + sessionRef + ", lastActivityTime=" + lastActivityTime + "]"; + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/AbstractPluginWebSocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/AbstractPluginWebSocketMsg.java new file mode 100644 index 00000000000..ef70edc2a10 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/AbstractPluginWebSocketMsg.java @@ -0,0 +1,64 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.common.data.id.PluginId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; + +public abstract class AbstractPluginWebSocketMsg implements PluginWebsocketMsg { + + private static final long serialVersionUID = 1L; + + private final PluginWebsocketSessionRef sessionRef; + private final T payload; + + AbstractPluginWebSocketMsg(PluginWebsocketSessionRef sessionRef, T payload) { + this.sessionRef = sessionRef; + this.payload = payload; + } + + public PluginWebsocketSessionRef getSessionRef() { + return sessionRef; + } + + + @Override + public TenantId getPluginTenantId(){ + return sessionRef.getPluginTenantId(); + } + + @Override + public PluginId getPluginId() { + return sessionRef.getPluginId(); + } + + @Override + public PluginApiCallSecurityContext getSecurityCtx() { + return sessionRef.getSecurityCtx(); + } + + public T getPayload() { + return payload; + } + + @Override + public String toString() { + return "AbstractPluginWebSocketMsg [sessionRef=" + sessionRef + ", payload=" + payload + "]"; + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/BinaryPluginWebSocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/BinaryPluginWebSocketMsg.java new file mode 100644 index 00000000000..8afe0ef038b --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/BinaryPluginWebSocketMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; + +import java.nio.ByteBuffer; + +public class BinaryPluginWebSocketMsg extends AbstractPluginWebSocketMsg { + + private static final long serialVersionUID = 1L; + + public BinaryPluginWebSocketMsg(PluginWebsocketSessionRef sessionRef, ByteBuffer payload) { + super(sessionRef, payload); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/EmptyPluginWebsocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/EmptyPluginWebsocketMsg.java new file mode 100644 index 00000000000..a02c997d2dc --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/EmptyPluginWebsocketMsg.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; + +import java.nio.ByteBuffer; + +public class EmptyPluginWebsocketMsg extends AbstractPluginWebSocketMsg { + + private static final long serialVersionUID = 1L; + private static ByteBuffer EMPTY = ByteBuffer.wrap(new byte[0]); + + protected EmptyPluginWebsocketMsg(PluginWebsocketSessionRef sessionRef) { + super(sessionRef, EMPTY); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PingPluginWebsocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PingPluginWebsocketMsg.java new file mode 100644 index 00000000000..7d6d6824613 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PingPluginWebsocketMsg.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; + +public class PingPluginWebsocketMsg extends EmptyPluginWebsocketMsg { + + private static final long serialVersionUID = 1L; + + public PingPluginWebsocketMsg(PluginWebsocketSessionRef sessionRef) { + super(sessionRef); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PluginWebsocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PluginWebsocketMsg.java new file mode 100644 index 00000000000..8cc4232be26 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PluginWebsocketMsg.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; +import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; + +import java.io.Serializable; + +public interface PluginWebsocketMsg extends ToPluginActorMsg, Serializable { + + PluginWebsocketSessionRef getSessionRef(); + + T getPayload(); + + PluginApiCallSecurityContext getSecurityCtx(); +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PongPluginWebsocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PongPluginWebsocketMsg.java new file mode 100644 index 00000000000..ab5a2383d2e --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/PongPluginWebsocketMsg.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; + +public class PongPluginWebsocketMsg extends EmptyPluginWebsocketMsg { + + private static final long serialVersionUID = 1L; + + public PongPluginWebsocketMsg(PluginWebsocketSessionRef sessionRef) { + super(sessionRef); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/SessionEventPluginWebSocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/SessionEventPluginWebSocketMsg.java new file mode 100644 index 00000000000..a36549834d6 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/SessionEventPluginWebSocketMsg.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; +import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent; + +public class SessionEventPluginWebSocketMsg extends AbstractPluginWebSocketMsg { + + private static final long serialVersionUID = 1L; + + public SessionEventPluginWebSocketMsg(PluginWebsocketSessionRef sessionRef, SessionEvent payload) { + super(sessionRef, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/TextPluginWebSocketMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/TextPluginWebSocketMsg.java new file mode 100644 index 00000000000..d144b6ca469 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/ws/msg/TextPluginWebSocketMsg.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.extensions.api.plugins.ws.msg; + +import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; + +public class TextPluginWebSocketMsg extends AbstractPluginWebSocketMsg { + + private static final long serialVersionUID = 1L; + + public TextPluginWebSocketMsg(PluginWebsocketSessionRef sessionRef, String payload) { + super(sessionRef, payload); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleContext.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleContext.java new file mode 100644 index 00000000000..337028f57a2 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleContext.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.extensions.api.device.DeviceAttributes; + +import java.util.Optional; + +public interface RuleContext { + + RuleId getRuleId(); + + DeviceAttributes getDeviceAttributes(); + + Event save(Event event); + + Optional saveIfNotExists(Event event); + + Optional findEvent(String eventType, String eventUid); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleException.java new file mode 100644 index 00000000000..7cc3bd46eab --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleException.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +public class RuleException extends Exception { + + private static final long serialVersionUID = 1L; + + public RuleException(String msg) { + super(msg); + } + + public RuleException(String msg, Exception e) { + super(msg, e); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java new file mode 100644 index 00000000000..0f39f9268a6 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.component.ConfigurableComponent; + +/** + * @author Andrew Shvayka + */ +public interface RuleFilter extends ConfigurableComponent, RuleLifecycleComponent { + + boolean filter(RuleContext ctx, ToDeviceActorMsg msg); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleInitializationException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleInitializationException.java new file mode 100644 index 00000000000..8cf19e5df8d --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleInitializationException.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +public class RuleInitializationException extends RuleException { + + private static final long serialVersionUID = 1L; + + public RuleInitializationException(String msg, Exception e) { + super(msg, e); + } + + public RuleInitializationException(String msg) { + super(msg, null); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleLifecycleComponent.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleLifecycleComponent.java new file mode 100644 index 00000000000..4eb38d2d419 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleLifecycleComponent.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +/** + * @author Andrew Shvayka + */ +public interface RuleLifecycleComponent { + + void resume(); + + void suspend(); + + void stop(); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessingMetaData.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessingMetaData.java new file mode 100644 index 00000000000..8f528a9a31c --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessingMetaData.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class RuleProcessingMetaData { + + private final Map md; + + public RuleProcessingMetaData() { + super(); + this.md = new HashMap<>(); + } + + public void put(String key, T value) { + md.put(key, value); + } + + public Optional get(String key) { + return Optional.ofNullable((T) md.get(key)); + } + + public Map getValues() { + return Collections.unmodifiableMap(md); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java new file mode 100644 index 00000000000..179204f8b2e --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.extensions.api.component.ConfigurableComponent; + +/** + * @author Andrew Shvayka + */ +public interface RuleProcessor extends ConfigurableComponent, RuleLifecycleComponent { + + RuleProcessingMetaData process(RuleContext ctx, ToDeviceActorMsg msg) throws RuleException; +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/SimpleRuleLifecycleComponent.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/SimpleRuleLifecycleComponent.java new file mode 100644 index 00000000000..7da902d4f80 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/SimpleRuleLifecycleComponent.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public abstract class SimpleRuleLifecycleComponent implements RuleLifecycleComponent { + + @Override + public void resume() { + log.debug("Resume method was called, but no impl provided!"); + } + + @Override + public void suspend() { + log.debug("Suspend method was called, but no impl provided!"); + } + + @Override + public void stop() { + log.debug("Stop method was called, but no impl provided!"); + } + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/ToRuleActorMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/ToRuleActorMsg.java new file mode 100644 index 00000000000..57ac998c4db --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/ToRuleActorMsg.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016 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.extensions.api.rules; + +import org.thingsboard.server.common.data.id.RuleId; +import org.thingsboard.server.common.msg.aware.TenantAwareMsg; + +public interface ToRuleActorMsg extends TenantAwareMsg { + + RuleId getRuleId(); + +} diff --git a/extensions-api/src/main/resources/EmptyJsonDescriptor.json b/extensions-api/src/main/resources/EmptyJsonDescriptor.json new file mode 100644 index 00000000000..03bf1454b8b --- /dev/null +++ b/extensions-api/src/main/resources/EmptyJsonDescriptor.json @@ -0,0 +1,12 @@ +{ + "schema": { + "description": "Empty Schema", + "type": "object", + "additionalProperties": false, + "properties": { + } + }, + "form": [ + "*" + ] +} \ No newline at end of file diff --git a/extensions-core/pom.xml b/extensions-core/pom.xml new file mode 100644 index 00000000000..b2e2d183c0b --- /dev/null +++ b/extensions-core/pom.xml @@ -0,0 +1,131 @@ + + + 4.0.0 + + org.thingsboard + 0.0.1-SNAPSHOT + server + + org.thingsboard.server + extensions-core + jar + + Thingsboard Server Core Extensions + http://thingsboard.org + + + UTF-8 + ${basedir}/.. + + + + + org.thingsboard.server + extensions-api + provided + + + com.google.code.gson + gson + + + org.apache.velocity + velocity + provided + + + org.apache.velocity + velocity-tools + provided + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework + spring-context-support + provided + + + javax.mail + mail + + + com.google.protobuf + protobuf-java + provided + + + com.google.guava + guava + provided + + + org.slf4j + slf4j-api + provided + + + org.slf4j + log4j-over-slf4j + provided + + + ch.qos.logback + logback-core + provided + + + ch.qos.logback + logback-classic + provided + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + org.codehaus.mojo + build-helper-maven-plugin + + + + + diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java new file mode 100644 index 00000000000..33b13330275 --- /dev/null +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java @@ -0,0 +1,109 @@ +/** + * Copyright © 2016 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.extensions.core.action.mail; + +import lombok.extern.slf4j.Slf4j; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.runtime.parser.ParseException; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.ToDeviceMsg; +import org.thingsboard.server.extensions.api.component.Action; +import org.thingsboard.server.extensions.api.plugins.PluginAction; +import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; +import org.thingsboard.server.extensions.api.rules.RuleContext; +import org.thingsboard.server.extensions.api.rules.RuleProcessingMetaData; +import org.thingsboard.server.extensions.api.rules.SimpleRuleLifecycleComponent; +import org.thingsboard.server.extensions.core.utils.VelocityUtils; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@Action(name = "Send Mail Action", descriptor = "SendMailActionDescriptor.json", configuration = SendMailActionConfiguration.class) +@Slf4j +public class SendMailAction extends SimpleRuleLifecycleComponent implements PluginAction { + + private SendMailActionConfiguration configuration; + private Optional