Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature/html-emails-copy

pull/4429/head
AndrewVolosytnykhThingsboard 6 years ago
parent
commit
a29991a653
  1. 18
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  2. 2
      application/src/main/java/org/thingsboard/server/config/WebConfig.java
  3. 2
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 37
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  5. 8
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  6. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  8. 10
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  9. 202
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  10. 12
      application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  12. 2
      application/src/main/resources/thingsboard.yml
  13. 15
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  14. 33
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java
  15. 442
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  16. 65
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  17. 43
      application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java
  18. 56
      application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java
  19. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  20. 50
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  21. 4
      common/data/pom.xml
  22. 27
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  23. 45
      common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java
  24. 23
      common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java
  25. 23
      common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java
  26. 36
      common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java
  27. 40
      common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java
  28. 20
      common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java
  29. 37
      common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java
  30. 36
      common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java
  31. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  32. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java
  33. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mQueuedRequest.java
  34. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  35. 131
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java
  36. 84
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  37. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java
  38. 265
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java
  39. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  40. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  41. 23
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  42. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java
  43. 4
      dao/pom.xml
  44. 7
      dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
  45. 3
      dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java
  46. 122
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  47. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java
  48. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java
  49. 171
      dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java
  50. 106
      dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java
  51. 4
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  52. 151
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  53. 6
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  54. 2
      pom.xml
  55. 2
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java
  56. 8
      ui-ngx/src/app/core/http/event.service.ts
  57. 30
      ui-ngx/src/app/core/http/resource.service.ts
  58. 20
      ui-ngx/src/app/core/services/time.service.ts
  59. 6
      ui-ngx/src/app/core/utils.ts
  60. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  61. 5
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss
  62. 29
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  63. 10
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html
  64. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts
  65. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  66. 2
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html
  67. 73
      ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html
  68. 35
      ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.scss
  69. 109
      ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts
  70. 122
      ui-ngx/src/app/modules/home/components/event/event-table-config.ts
  71. 13
      ui-ngx/src/app/modules/home/components/event/event-table.component.ts
  72. 36
      ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts
  73. 8
      ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts
  74. 18
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts
  75. 84
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts
  76. 29
      ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts
  77. 16
      ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts
  78. 33
      ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts
  79. 31
      ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts
  80. 2
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  81. 25
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts
  82. 3
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html
  83. 8
      ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss
  84. 69
      ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html
  85. 23
      ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.scss
  86. 227
      ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.ts
  87. 25
      ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts
  88. 15
      ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts
  89. 6
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  90. 3
      ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw
  91. 6
      ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts
  92. 11
      ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts
  93. 58
      ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts
  94. 71
      ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts
  95. 24
      ui-ngx/src/app/modules/home/pages/edge/edge.component.html
  96. 4
      ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts
  97. 9
      ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts
  98. 4
      ui-ngx/src/app/modules/home/pages/profile/profile.component.html
  99. 9
      ui-ngx/src/app/modules/home/pages/profile/profile.component.ts
  100. 53
      ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts

18
application/src/main/data/json/system/widget_bundles/input_widgets.json

@ -455,6 +455,24 @@
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Photo camera input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Photo camera input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
} }
},
{
"alias": "update_json_attribute",
"name": "Update JSON attribute",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADhCAMAAACZWwyuAAAB71BMVEX////w8PDt7e3z8/Pg4ODAwMDc3Nz19fUZhJQ4lKK9vb17e3v9/f2mpqbj4+P4+Pi+vr58fHzT09P7+/uTxcz2+/ucy9GpqakCAgK93OHNzc0eh5b09PSw1dusrKwvkZ+ampqysrIzMzPV1dXCwsKurq4iiZnZ2dnX19fIyMgrj50ABsiKiopmZmbo8/XQ5+ro6OjPz89JnqqCgoLX6u2cnJwmjJv39/ff39+12N7ExMQ2NjaioqKYmJjs7OzD4OTS0tLKysrh8PLq6uq6uroCCMjDw8OOjo6hzdO0tLQuj56UlJS4uvDZ7O7l5eVvsrw0k6EchZWEhIRwcHDU6ezG4eaGv8d1tb+3t7dCmqeIiIj4+/3j8fO62+AKD8tWpbGgoKBqamouLi7u7u47lqTP0PWr0tgXHM1/u8N6uMFqsLpfqrVNoKxFRUXU1faYyM92dnY7Ozvz+Pry8vK5ubm4uLh+fn5sbGzGx/OipOtzduG42t8uM9KNw8pTo690dHT6+vrt9viOkefb29um0NYfHx/p6vu9vvGqrO2YmulXW9uhztRnrrgmJiYQEBDt7fzk5frf4PjL4+eMj+a/3eJpbN9MUNk7P9SQxMuNwslfX19TU1OcnurI4uY9QdYyN9ImK9BVVVVPT097fuNYWFjSJwPyAAALvElEQVR42uzbTWviQBjA8ad5qgNONI05FCSIshUNKERCSFlM2WIxSKCH5FI8RfoFCrvVYwv7xTfG4susxqgpqJkfHoLM6c9MTCYROI7jOC4doiIRoqrEkhQRuIOJEsEVROI1Dw2pIkPlMQ8PyWMez8ItLOD2QnArAlxyIsEYhK/x5CSMJQGX0Cfu8AmXTCEqbkWUPQaBiDtd8hJXMJ6SbNByeWd3iROMRxIPAhEx09MSd1ATDwIJMdPTEndJNIid4D/kkLk4yMbFZTop2fXdonSZktb2XuFKpwRnBzeoGoeklNZS1tjj+BVu2hDKfYhxKXUXTtnXHIqgXd+esk/pNSK+GrQcjZsbuotBVrKUFmzSGs++b1djZqVV1m01D6crqlSpVOldpYKFSmzKWUCN0nI0bq5bWAwiuHquNNnj+JOlSDWA144OJW86KUcp38oAtQZIPZ/mICQEBvWGcLpw7i6acu0iCkXDtQ1hcos4MtdTOt4VYs2h5dk42y4azj3ejhYpVUyEwEZ1F+BpBJKviQVfWUkZuOTZz0e5ba8FJ4xNWZcLPw0De97vezpYTzmUK1iiL18p5T+aEaymxGRU2OhhTMAbgFISATvCMqU0LgEUo4U/KOreKV9LsSlHAeKtgY+09STr6ylvzABfRuWvlC5i4DCz8oiU4Az6Uwug25blzsMyZb8zDpkQkgj04YSxKSf16FwpTOquzZwrnx5lvT3sLlP21lKSoxY4aM4wANCn10DmKemvaFZ2HuBMMCnDT5QSe205x6R8ufICWqowKQ/92WFJ/lsTID+9vhp29FlKpye1/AbUXELqTTgDbMq60S2PDMRHKutsSqzTGrIpD74YYvUohBr+x1D+O0tZMqaO0wASfPjmWdwjbfgFd0wDUZjY+F/Ke6ptSRl/iZ6Nm3Dc4sq7Se/GMRtbQ7hZvjrR+XZGKimrhoZ7pOSbbKntV/Kt39R20fkDiZSf7Yi4i3rBkzLVJ46g4M7mHH+l4Bjpv+hiXfTyTr9lVk+U38Diqzs1n1l8XeibiJaKLP7O74FEiYdMT/RfE0RVJZICe8txSSRKmU9KEIRm8V0QnitNIWvST6m93whhy3/sm/lTEmEYx7/SOLPbdFgjCDGEWm0FBUVEGRRF0WEmlCgxeFSaeKHpNF1ajWW3V4cddh9/aLs078PqCMOCxjrs55dn3mff9/3hM7x7fGfYrSs1ll9leUWFrmrK+ERXahSk0uc9srTKJ7c31+hKjQJUHnnorc2g0lijK9eVGgWovHF1fUaV5zSVyg54RUaVNzWVearcK5FWWWPcqanMU2VjU1NTI6kcNjbpNJX5HvDGq43pA7510/Q+TaVylcyl/F65z1iuqVSi8vgpX23VqaUfO5c1lUpUrq+V2LXUh6PxnPbhuExxhnHrfi3OKFylbseV8ic/tz/WlRoroFK8V9ZMGad0pUZuKstyBSWMpnJ1qfS0tXmk6hhFHmRfR/sXnQJUVswPJy8dy0Vlvd9fL1WDFXmRfR3tv5hn39oT9+JU5TyyNwBQi8rOz3vWVnurc1HZ5XR2rZhK2n8x68buNbwNPqIq43lCVSrLpB/k1aZcVD5wODoAXOQk9ND3hD39DrzhzR39QgiIhs22Xj1YH4EQL5hjQIQ3C5OO9DrbWaCSZ9dpPu2PjdeuHZK7BPD+JdU0g4nrQVWplPizJxeVAaAVgLvF5na7gT6+rnlgAJZ6p6ujWfCD72uesEWpDxP3qvlrGzA+6hwNh2gdU8mus/m0PzYYjYexkO/3qBLxsbtQnUpf8vDCRi4HNcBFADcXsPDoDcF1FnwlYOFaWV9UpQdjiGfrSCW7TvPTVB/FQl4knlIlfr+H6lRWDZ8uU66yjjOLcM2Syj64DCmV41wd65PK1sl6m9WWUSXNz8zd9jtUiaftg6pTuXX4VFkeKk2c84KIXlTZE2IqRzgT65PKrgF/pEem0jpEKuX7ZDPZQDXNC3swGLSnnkNqUbnfW6PwFb1bgEgr1w0JUSXAVJq4AOszVSNcHTBho3VwRUkl7bOQg9WQMdj+nOo/vnwBMDMoEnwxA0AtKm/MbxE5qUBlhGt5Z9AjKtw3WSwylTFLd7gfrM9Uge/wO3ts6XWfhFf+PlJJ84nFj52XH66LxKli1m5/DQl2wM9MX1aFyl//8koFKvHRY3Y5sO5+2Gxtkau0CpMjYH1SdaFDMNf3p9fpo8LF+hBdp/nEopehH3aJ11QRHxuLL1D5y3hbFSqXAvnAV6JIbJuu1lQuC0eStRs1lctC1aU16njsaCFb8VRW5pCxvWnBauW/5ZXZDvKQJ0K6ednYMarGXHIFVFZ99ib36BTklRlVRjmTXCWNDdaccsmgXaIBSlGPSt9BMa/0KcgrM6rUOyFTSWNSmT2XxB2RucQsFKMalRLztxTklbxVEKJ6FktQXhngOK4OIiOTgjXGszHllA5p1Mo1L5FLErPtd6EcFansXDt8TkFeyff7J4RPTCXllXG3+2JKXX/YYhFVsjHllLFewCLEs+WSc2MoOoWoXFtbu6kzg8pM74+9LlJJeSWQUhfg/Kk+G9MBb+GB3g5kySWfioFF0SlE5b4TV4YvKVQ5ZCWVLK9k6tq4kaVVOjk3YkPIwtuECv4gV+C90uc9pkzlWRvljiyvZOpMmVSiq2WciyALH+ZQfApUuV65SsodKRkCPN2p/NGS6rMx5ZQYCk+Es+SSmAk+Q/EpQOX5mgpf8mGZYpUsd5SpfDBgMugR8hikPhtTTolxwdqDzLkkntlHUHzyV9l5I+lNHmhUrpLljjKVzpg55kC8x2a2hsDGlFMCvVwEmXNJPEpABayOOMPggvpZBSrHnRaPCepnBVRuOFFefmIDlg3TX/buXqVhKIzD+FuonEEJJQ4eKogEOrjFQRRSisEiuIl3YPYOvQId7Kg34OSdGgd1SaD5aPmH8zwQsv8g5Jx3OOf08c0GUP+UUezMXDzA8/3lKHNnZS630Oqf8sAK//5QvkKrG+VVsqygXEQvGyibUc5WlZTl44+gbEJ5u5pm1ZT3/hjKBpSLr5NRDeWFf4WyyR78eVRHeecnUG5PebOOaynt0K8ttNpTzpMsy5LkvIry+uNpZqHVnjK6LMvm4yrKqXd84A2X6PzBd0mZux/KERvHfsYZE884o5ch2/LscxOe5C4orfDFxMJrAFP0oQQllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBuWdK2qLwDj8lIiIiIiIKLpdG45ZFqTP6y407heV/aTfK1Oi3qBtlgNdmfLdrxypyw0AAhifJ2AeDZtcohUAEgZGFrdJVjIsgk2DY5urt9xFu9wn2xWMFLklzV3gb45u/GHD7YSML5s3wwUASSqHcbkIplNtLKIVyewll7onaZR7oB/yfJ/UYZfvLmbMXyscpy2CKxplOKB+mtBwRNRcflLJJvhh/Anybir4nBV9sMeJKyonnZaYO/cm4GX1+JC6xr02dPgAlpaFpFMTq7onUsRkx0WEd5RD42ucP3YWJeMJQI4YTJj7H2tz3TzkBlPR0oQEgkUK6AEzz2mPnxhx6LPsW0RA2XHYc8ebKwXPcP2UPgPTynAk9KV2pTHtcRZkxZ8Ma9dkZvqJmO3KLjnPFTimP1EEm/PyP8tMfSp8p7/R9FWWyy9BMrTnp1lwRA53zNx70UrtTSmjiMhIdXykvdADQpDCT2katoiTWiC1Xli0OmbJyZkQ8m4XR7/XYASQ7JNLwSglxfh4qUl+bETUNsIqyM66YAyfPdaz5hpiYuzzDWLHeKyUcYhEH9Zcy/wzZRApebDUOa2873dWZ2iJG54oQENHVuGRrE+Ju30q5g28qoRTK7SWUQrm9hFIot5dQCuX2kvUrWQrcXrKqKgvUkiRJkiRJkiS9328dj4CaN1dagAAAAABJRU5ErkJggg==",
"description": "Simple form to input new JSON value for pre-defined attribute/timeseries key.",
"descriptor": {
"type": "latest",
"sizeX": 7.5,
"sizeY": 3,
"resources": [],
"templateHtml": "<tb-json-input-widget \n [ctx]=\"ctx\">\n</tb-json-input-widget>",
"templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.jsonInputWidget.onDataUpdated();\n}\n\nself.onResize = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AdvancedSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"widgetMode\": {\n \"title\": \"Widget mode\",\n \"type\": \"string\",\n \"default\": \"ATTRIBUTE\"\n },\n \"attributeScope\": {\n \"title\": \"Attribute scope\",\n \"type\": \"string\",\n \"default\": \"SERVER_SCOPE\"\n },\n \"showLabel\":{\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"attributeRequired\": {\n \"title\": \"Value required\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showResultMessage\": {\n \"title\": \"Show result message\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"widgetMode\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"ATTRIBUTE\",\n \"label\": \"Update attribute\"\n },\n {\n \"value\": \"TIME_SERIES\",\n \"label\": \"Update timeseries\"\n }\n ]\n },\n {\n \"key\": \"attributeScope\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"condition\": \"model.widgetMode === 'ATTRIBUTE'\",\n \"items\": [\n {\n \"value\": \"SERVER_SCOPE\",\n \"label\": \"Server attribute\"\n },\n {\n \"value\": \"SHARED_SCOPE\",\n \"label\": \"Shared attribute\"\n }\n ]\n },\n \"showLabel\",\n {\n \"key\": \"labelValue\",\n \"condition\": \"model.showLabel\"\n },\n \"attributeRequired\",\n \"showResultMessage\"\n ]\n}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"attributeScope\":\"SERVER_SCOPE\",\"showLabel\":true,\"attributeRequired\":true,\"showResultMessage\":true},\"title\":\"Update JSON attribute\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
}
} }
] ]
} }

2
application/src/main/java/org/thingsboard/server/config/WebConfig.java

@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
@Controller @Controller
public class WebConfig { public class WebConfig {
@RequestMapping(value = "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**") @RequestMapping(value = {"/assets", "/assets/", "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**"})
public String redirect() { public String redirect() {
return "forward:/index.html"; return "forward:/index.html";
} }

2
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -110,7 +110,6 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -130,6 +129,7 @@ import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository; import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;

37
application/src/main/java/org/thingsboard/server/controller/EventController.java

@ -18,12 +18,14 @@ package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
@ -101,4 +104,38 @@ public class EventController extends BaseController {
} }
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST)
@ResponseBody
public PageData<Event> getEvents(
@PathVariable("entityType") String strEntityType,
@PathVariable("entityId") String strEntityId,
@RequestParam("tenantId") String strTenantId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestBody EventFilter eventFilter,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ);
if(sortProperty != null && sortProperty.equals("createdTime") && eventFilter.hasFilterForJsonBody()) {
sortProperty = ModelConstants.CREATED_TIME_PROPERTY;
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(eventService.findEventsByFilter(tenantId, entityId, eventFilter, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
} }

8
application/src/main/java/org/thingsboard/server/controller/TbResourceController.java

@ -36,8 +36,8 @@ import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
@ -52,12 +52,6 @@ public class TbResourceController extends BaseController {
public static final String RESOURCE_ID = "resourceId"; public static final String RESOURCE_ID = "resourceId";
private final TbResourceService resourceService;
public TbResourceController(TbResourceService resourceService) {
this.resourceService = resourceService;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET) @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET)
@ResponseBody @ResponseBody

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java

@ -40,8 +40,6 @@ public class DeviceProfileMsgConstructor {
.setName(deviceProfile.getName()) .setName(deviceProfile.getName())
.setDefault(deviceProfile.isDefault()) .setDefault(deviceProfile.isDefault())
.setType(deviceProfile.getType().name()) .setType(deviceProfile.getType().name())
.setTransportType(deviceProfile.getTransportType().name())
.setProvisionType(deviceProfile.getProvisionType().name())
.setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile.getProfileData()))); .setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile.getProfileData())));
// TODO: voba - should this be always null at the moment?? // TODO: voba - should this be always null at the moment??
// if (deviceProfile.getDefaultRuleChainId() != null) { // if (deviceProfile.getDefaultRuleChainId() != null) {
@ -54,6 +52,12 @@ public class DeviceProfileMsgConstructor {
if (deviceProfile.getDescription() != null) { if (deviceProfile.getDescription() != null) {
builder.setDescription(deviceProfile.getDescription()); builder.setDescription(deviceProfile.getDescription());
} }
if (deviceProfile.getTransportType() != null) {
builder.setTransportType(deviceProfile.getTransportType().name());
}
if (deviceProfile.getProvisionType() != null) {
builder.setProvisionType(deviceProfile.getProvisionType().name());
}
if (deviceProfile.getProvisionDeviceKey() != null) { if (deviceProfile.getProvisionDeviceKey() != null) {
builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey()); builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey());
} }

3
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -58,11 +58,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;

10
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.resource.TbResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
@ -97,7 +97,7 @@ public class InstallScripts {
private OAuth2ConfigTemplateService oAuth2TemplateService; private OAuth2ConfigTemplateService oAuth2TemplateService;
@Autowired @Autowired
private TbResourceService resourceService; private ResourceService resourceService;
private Path getTenantRuleChainsDir() { private Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
@ -259,18 +259,12 @@ public class InstallScripts {
try { try {
createDefaultRuleChains(tenantId); createDefaultRuleChains(tenantId);
createDefaultRuleChain(tenantId, "Thermostat"); createDefaultRuleChain(tenantId, "Thermostat");
loadEdgeDemoRuleChains(tenantId);
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to load dashboard from json", e); log.error("Unable to load dashboard from json", e);
throw new RuntimeException("Unable to load dashboard from json", e); throw new RuntimeException("Unable to load dashboard from json", e);
} }
} }
private void loadEdgeDemoRuleChains(TenantId tenantId) throws Exception {
Path edgeDemoRuleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, EDGE_MANAGEMENT, RULE_CHAINS_DIR);
loadRuleChainsFromPath(tenantId, edgeDemoRuleChainsDir);
}
public void createOAuth2Templates() throws Exception { public void createOAuth2Templates() throws Exception {
Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR); Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) { try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) {

202
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -0,0 +1,202 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mInstance;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.ResourceService;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j
@Service
public class DefaultTbResourceService implements TbResourceService {
private final ResourceService resourceService;
private final DDFFileParser ddfFileParser;
public DefaultTbResourceService(ResourceService resourceService) {
this.resourceService = resourceService;
this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
}
@Override
public TbResource saveResource(TbResource resource) throws ThingsboardException {
log.trace("Executing saveResource [{}]", resource);
if (StringUtils.isEmpty(resource.getData())) {
throw new DataValidationException("Resource data should be specified!");
}
if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
try {
List<ObjectModel> objectModels =
ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
if (!objectModels.isEmpty()) {
ObjectModel objectModel = objectModels.get(0);
String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion();
String name = objectModel.name;
resource.setResourceKey(resourceKey);
if (resource.getId() == null) {
resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion());
}
resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name);
} else {
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
}
} catch (InvalidDDFFileException | IOException e) {
throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL);
}
if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource) == null) {
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
}
} else {
resource.setResourceKey(resource.getFileName());
}
return resourceService.saveResource(resource);
}
@Override
public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) {
return resourceService.getResource(tenantId, resourceType, resourceId);
}
@Override
public TbResource findResourceById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceById(tenantId, resourceId);
}
@Override
public TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceInfoById(tenantId, resourceId);
}
@Override
public PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) {
return resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink);
}
@Override
public PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) {
return resourceService.findTenantResourcesByTenantId(tenantId, pageLink);
}
@Override
public List<LwM2mObject> findLwM2mObject(TenantId tenantId, String sortOrder, String sortProperty, String[] objectIds) {
log.trace("Executing findByTenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL,
objectIds);
return resources.stream()
.map(this::toLwM2mObject)
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
}
@Override
public List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) {
log.trace("Executing findByTenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink);
return resourcePageData.getData().stream()
.map(this::toLwM2mObject)
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
}
@Override
public void deleteResource(TenantId tenantId, TbResourceId resourceId) {
resourceService.deleteResource(tenantId, resourceId);
}
@Override
public void deleteResourcesByTenantId(TenantId tenantId) {
resourceService.deleteResourcesByTenantId(tenantId);
}
private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
Comparator<LwM2mObject> comparator;
if ("name".equals(sortProperty)) {
comparator = Comparator.comparing(LwM2mObject::getName);
} else {
comparator = Comparator.comparingLong(LwM2mObject::getId);
}
return "DESC".equals(sortOrder) ? comparator.reversed() : comparator;
}
private LwM2mObject toLwM2mObject(TbResource resource) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
List<ObjectModel> objectModels =
ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
if (objectModels.size() == 0) {
return null;
} else {
ObjectModel obj = objectModels.get(0);
LwM2mObject lwM2mObject = new LwM2mObject();
lwM2mObject.setId(obj.id);
lwM2mObject.setKeyId(resource.getResourceKey());
lwM2mObject.setName(obj.name);
lwM2mObject.setMultiple(obj.multiple);
lwM2mObject.setMandatory(obj.mandatory);
LwM2mInstance instance = new LwM2mInstance();
instance.setId(0);
List<LwM2mResourceObserve> resources = new ArrayList<>();
obj.resources.forEach((k, v) -> {
if (!v.operations.isExecutable()) {
LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false);
resources.add(lwM2MResourceObserve);
}
});
instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
lwM2mObject.setInstances(new LwM2mInstance[]{instance});
return lwM2mObject;
}
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e);
return null;
}
}
}

12
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java → application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java

@ -13,24 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.resource; package org.thingsboard.server.service.resource;
import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import java.io.IOException;
import java.util.List; import java.util.List;
public interface TbResourceService { public interface TbResourceService {
TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException;
TbResource saveResource(TbResource resource) throws ThingsboardException;
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
@ -55,4 +54,5 @@ public interface TbResourceService {
void deleteResource(TenantId tenantId, TbResourceId resourceId); void deleteResource(TenantId tenantId, TbResourceId resourceId);
void deleteResourcesByTenantId(TenantId tenantId); void deleteResourcesByTenantId(TenantId tenantId);
} }

6
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -56,7 +56,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
@ -108,7 +108,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbClusterService tbClusterService; private final TbClusterService tbClusterService;
private final DataDecodingEncodingService dataDecodingEncodingService; private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService; private final DeviceProvisionService deviceProvisionService;
private final TbResourceService resourceService; private final ResourceService resourceService;
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>(); private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
@ -117,7 +117,7 @@ public class DefaultTransportApiService implements TransportApiService {
RelationService relationService, DeviceCredentialsService deviceCredentialsService, RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
DeviceProvisionService deviceProvisionService, TbResourceService resourceService) { DeviceProvisionService deviceProvisionService, ResourceService resourceService) {
this.deviceProfileCache = deviceProfileCache; this.deviceProfileCache = deviceProfileCache;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
this.apiUsageStateService = apiUsageStateService; this.apiUsageStateService = apiUsageStateService;

2
application/src/main/resources/thingsboard.yml

@ -516,7 +516,7 @@ js:
# Built-in JVM JavaScript environment properties # Built-in JVM JavaScript environment properties
local: local:
# Use Sandboxed (secured) JVM JavaScript environment # Use Sandboxed (secured) JVM JavaScript environment
use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:false}" use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:true}"
# Specify thread pool size for JavaScript sandbox resource monitor # Specify thread pool size for JavaScript sandbox resource monitor
monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}" monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
# Maximum CPU time in milliseconds allowed for script execution # Maximum CPU time in milliseconds allowed for script execution

15
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.AssetUpdateMsg; import org.thingsboard.server.gen.edge.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg;
@ -672,28 +673,32 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class); edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
edgeImitator.expectMessageAmount(7); edgeImitator.expectMessageAmount(7);
edgeImitator.connect(); edgeImitator.connect();
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size()); Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size());
Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent());
edgeImitator.getDownlinkMsgs().clear(); edgeImitator.getDownlinkMsgs().clear();
edgeImitator.expectMessageAmount(4); edgeImitator.expectMessageAmount(7);
doPost("/api/edge/sync/" + edge.getId()); doPost("/api/edge/sync/" + edge.getId());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
Assert.assertEquals(4, edgeImitator.getDownlinkMsgs().size()); Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size());
Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent());
edgeImitator.allowIgnoredTypes(); edgeImitator.allowIgnoredTypes();
edgeImitator.disconnect(); try {
edgeImitator.disconnect();
} catch (Exception ignored) {}
doDelete("/api/device/" + savedDevice.getId().getId().toString()) doDelete("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());

33
application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java

@ -42,7 +42,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class BaseEdgeEventControllerTest extends AbstractControllerTest { public class BaseEdgeEventControllerTest extends AbstractControllerTest {
private Tenant savedTenant; private Tenant savedTenant;
private TenantId tenantId;
private User tenantAdmin; private User tenantAdmin;
@Before @Before
@ -52,7 +51,6 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class); savedTenant = doPost("/api/tenant", tenant, Tenant.class);
tenantId = savedTenant.getId();
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
tenantAdmin = new User(); tenantAdmin = new User();
@ -63,6 +61,10 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
// sleep 1 seconds to avoid CREDENTIALS updated message for the user
// user credentials is going to be stored and updated event pushed to edge notification service
// while service will be processing this event edge could be already added and additional message will be pushed
Thread.sleep(1000);
} }
@After @After
@ -75,7 +77,6 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
@Test @Test
public void testGetEdgeEvents() throws Exception { public void testGetEdgeEvents() throws Exception {
Thread.sleep(500);
Edge edge = constructEdge("TestEdge", "default"); Edge edge = constructEdge("TestEdge", "default");
edge = doPost("/api/edge", edge, Edge.class); edge = doPost("/api/edge", edge, Edge.class);
@ -83,29 +84,31 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/edge/" + edge.getId().toString() + "/device/" + savedDevice.getId().toString(), Device.class); doPost("/api/edge/" + edge.getId().toString() + "/device/" + savedDevice.getId().toString(), Device.class);
Thread.sleep(500);
Asset asset = constructAsset("TestAsset", "default"); Asset asset = constructAsset("TestAsset", "default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class); Asset savedAsset = doPost("/api/asset", asset, Asset.class);
doPost("/api/edge/" + edge.getId().toString() + "/asset/" + savedAsset.getId().toString(), Asset.class); doPost("/api/edge/" + edge.getId().toString() + "/asset/" + savedAsset.getId().toString(), Asset.class);
Thread.sleep(500);
EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE); EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE);
doPost("/api/relation", relation); doPost("/api/relation", relation);
Thread.sleep(500);
List<EdgeEvent> edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?", // wait while edge event for the relation entity persisted to DB
new TypeReference<PageData<EdgeEvent>>() { Thread.sleep(100);
}, new TimePageLink(4)).getData(); List<EdgeEvent> edgeEvents;
int attempt = 1;
Assert.assertFalse(edgeEvents.isEmpty()); do {
edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?",
new TypeReference<PageData<EdgeEvent>>() {}, new TimePageLink(4)).getData();
attempt++;
Thread.sleep(100);
} while (edgeEvents.size() != 4 && attempt < 5);
Assert.assertEquals(4, edgeEvents.size()); Assert.assertEquals(4, edgeEvents.size());
Assert.assertEquals(EdgeEventType.RULE_CHAIN, edgeEvents.get(0).getType()); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType())));
Assert.assertEquals(EdgeEventType.DEVICE, edgeEvents.get(1).getType()); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType())));
Assert.assertEquals(EdgeEventType.ASSET, edgeEvents.get(2).getType()); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.ASSET.equals(ee.getType())));
Assert.assertEquals(EdgeEventType.RELATION, edgeEvents.get(3).getType()); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType())));
} }
private Device constructDevice(String name, String type) { private Device constructDevice(String name, String type) {

442
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -32,10 +32,12 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
@ -45,6 +47,15 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -57,6 +68,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
@ -71,7 +85,6 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.AssetUpdateMsg; import org.thingsboard.server.gen.edge.AssetUpdateMsg;
@ -81,6 +94,7 @@ import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.EdgeConfiguration; import org.thingsboard.server.gen.edge.EdgeConfiguration;
@ -106,6 +120,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -114,6 +129,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@Slf4j @Slf4j
abstract public class BaseEdgeTest extends AbstractControllerTest { abstract public class BaseEdgeTest extends AbstractControllerTest {
private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat";
private Tenant savedTenant; private Tenant savedTenant;
private TenantId tenantId; private TenantId tenantId;
private User tenantAdmin; private User tenantAdmin;
@ -145,17 +162,25 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
// sleep 1 seconds to avoid CREDENTIALS updated message for the user
// user credentials is going to be stored and updated event pushed to edge notification service
// while service will be processing this event edge could be already added and additional message will be pushed
Thread.sleep(1000);
installation(); installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
// should be less, but events from SyncEdgeService stack with events from controller. will be fixed in next releases // TODO: voba - should be less, but events from SyncEdgeService stack with events from controller. will be fixed in next releases
edgeImitator.expectMessageAmount(7); // so ideally sync process should check current edge queue and add only missing entities to the edge queue
edgeImitator.expectMessageAmount(10);
edgeImitator.connect(); edgeImitator.connect();
} }
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
edgeImitator.disconnect(); try {
edgeImitator.disconnect();
} catch (Exception ignored) {}
loginSysAdmin(); loginSysAdmin();
@ -163,23 +188,63 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void test() throws Exception { public void test() throws Exception {
testReceivedInitialData(); testReceivedInitialData();
int expectedDownlinkSize = 10;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testDevices(); testDevices();
expectedDownlinkSize = expectedDownlinkSize + 4;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testAssets(); testAssets();
expectedDownlinkSize = expectedDownlinkSize + 4;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testRuleChains(); testRuleChains();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testDashboards(); testDashboards();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testRelations(); testRelations();
expectedDownlinkSize = expectedDownlinkSize + 2;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testAlarms(); testAlarms();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testEntityView(); testEntityView();
expectedDownlinkSize = expectedDownlinkSize + 2;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testCustomer(); testCustomer();
expectedDownlinkSize = expectedDownlinkSize + 2;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testWidgetsBundleAndWidgetType(); testWidgetsBundleAndWidgetType();
expectedDownlinkSize = expectedDownlinkSize + 4;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testTimeseries(); testTimeseries();
expectedDownlinkSize = expectedDownlinkSize + 1;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testAttributes(); testAttributes();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testSendMessagesToCloud(); testSendMessagesToCloud();
expectedDownlinkSize = expectedDownlinkSize + 9;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testRpcCall(); testRpcCall();
expectedDownlinkSize = expectedDownlinkSize + 1;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
} }
private Device findDeviceByName(String deviceName) throws Exception { private Device findDeviceByName(String deviceName) throws Exception {
@ -204,10 +269,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
return asset; return asset;
} }
private Device saveDevice(String deviceName) throws Exception { private Device saveDevice(String deviceName, String type) throws Exception {
Device device = new Device(); Device device = new Device();
device.setName(deviceName); device.setName(deviceName);
device.setType("test"); device.setType(type);
return doPost("/api/device", device, Device.class); return doPost("/api/device", device, Device.class);
} }
@ -233,7 +298,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent); edgeEventService.saveAsync(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceRpcCallMsg); Assert.assertTrue(latestMessage instanceof DeviceRpcCallMsg);
@ -243,7 +308,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testReceivedInitialData() throws Exception { private void testReceivedInitialData() throws Exception {
log.info("Checking received data"); log.info("Checking received data");
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
EdgeConfiguration configuration = edgeImitator.getConfiguration(); EdgeConfiguration configuration = edgeImitator.getConfiguration();
Assert.assertNotNull(configuration); Assert.assertNotNull(configuration);
@ -253,9 +318,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UserId userId = edgeImitator.getUserId(); UserId userId = edgeImitator.getUserId();
Assert.assertNotNull(userId); Assert.assertNotNull(userId);
Optional<DeviceUpdateMsg> optionalMsg1 = edgeImitator.findMessageByType(DeviceUpdateMsg.class); Optional<DeviceUpdateMsg> deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class);
Assert.assertTrue(optionalMsg1.isPresent()); Assert.assertTrue(deviceUpdateMsgOpt.isPresent());
DeviceUpdateMsg deviceUpdateMsg = optionalMsg1.get(); DeviceUpdateMsg deviceUpdateMsg = deviceUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()); UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB());
Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class); Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class);
@ -264,9 +329,25 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData(); new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeDevices.contains(device)); Assert.assertTrue(edgeDevices.contains(device));
Optional<AssetUpdateMsg> optionalMsg2 = edgeImitator.findMessageByType(AssetUpdateMsg.class); List<DeviceProfileUpdateMsg> deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class);
Assert.assertTrue(optionalMsg2.isPresent()); Assert.assertEquals(3, deviceProfileUpdateMsgList.size());
AssetUpdateMsg assetUpdateMsg = optionalMsg2.get(); Optional<DeviceProfileUpdateMsg> deviceProfileUpdateMsgOpt =
deviceProfileUpdateMsgList.stream().filter(dfum -> CUSTOM_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny();
Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent());
DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
UUID deviceProfileUUID = new UUID(deviceProfileUpdateMsg.getIdMSB(), deviceProfileUpdateMsg.getIdLSB());
DeviceProfile deviceProfile = doGet("/api/deviceProfile/" + deviceProfileUUID.toString(), DeviceProfile.class);
Assert.assertNotNull(deviceProfile);
Assert.assertNotNull(deviceProfile.getProfileData());
Assert.assertNotNull(deviceProfile.getProfileData().getAlarms());
Assert.assertNotNull(deviceProfile.getProfileData().getAlarms().get(0).getClearRule());
testAutoGeneratedCodeByProtobuf(deviceProfileUpdateMsg);
Optional<AssetUpdateMsg> assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class);
Assert.assertTrue(assetUpdateMsgOpt.isPresent());
AssetUpdateMsg assetUpdateMsg = assetUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType());
UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB()); UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB());
Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class); Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class);
@ -277,9 +358,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(assetUpdateMsg); testAutoGeneratedCodeByProtobuf(assetUpdateMsg);
Optional<RuleChainUpdateMsg> optionalMsg3 = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(optionalMsg3.isPresent()); Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
RuleChainUpdateMsg ruleChainUpdateMsg = optionalMsg3.get(); RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB()); UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB());
RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class); RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class);
@ -296,13 +377,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testDevices() throws Exception { private void testDevices() throws Exception {
log.info("Testing devices"); log.info("Testing devices");
Device savedDevice = saveDevice("Edge Device 2"); // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Device savedDevice = saveDevice("Edge Device 2", "Default");
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getId().getId().toString(), Device.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
@ -312,11 +392,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName()); Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType()); Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
// 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getId().getId().toString(), Device.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
@ -324,11 +404,34 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString()) doDelete("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); // we should not get any message because device is not assigned to edge any more
Assert.assertFalse(edgeImitator.waitForMessages(1));
// 4
edgeImitator.expectMessageAmount(1);
savedDevice = saveDevice("Edge Device 3", "Default");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
// 5
edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isOk());
// in this case we should get messages because device was assigned to edge
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
@ -342,13 +445,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testAssets() throws Exception { private void testAssets() throws Exception {
log.info("Testing assets"); log.info("Testing assets");
Asset savedAsset = saveAsset("Edge Asset 2");
// 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Asset savedAsset = saveAsset("Edge Asset 2");
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) latestMessage; AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) latestMessage;
@ -358,11 +461,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName()); Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName());
Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType()); Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType());
// 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
assetUpdateMsg = (AssetUpdateMsg) latestMessage; assetUpdateMsg = (AssetUpdateMsg) latestMessage;
@ -370,11 +473,32 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits());
Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString()) doDelete("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertFalse(edgeImitator.waitForMessages(1));
// 4
edgeImitator.expectMessageAmount(1);
savedAsset = saveAsset("Edge Asset 3");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
assetUpdateMsg = (AssetUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType());
Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits());
Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits());
Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName());
Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType());
// 5
edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
assetUpdateMsg = (AssetUpdateMsg) latestMessage; assetUpdateMsg = (AssetUpdateMsg) latestMessage;
@ -387,21 +511,21 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testRuleChains() throws Exception { private void testRuleChains() throws Exception {
log.info("Testing RuleChains"); log.info("Testing RuleChains");
// 1
edgeImitator.expectMessageAmount(1);
RuleChain ruleChain = new RuleChain(); RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge Test Rule Chain"); ruleChain.setName("Edge Test Rule Chain");
ruleChain.setType(RuleChainType.EDGE); ruleChain.setType(RuleChainType.EDGE);
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
createRuleChainMetadata(savedRuleChain); createRuleChainMetadata(savedRuleChain);
// sleep 1 seconds to avoid ENTITY_UPDATED_RPC_MESSAGE for the rule chain
// Wait before rule chain metadata saved to database before rule chain is assigned to edge // rule chain metadata is going to be stored and updated event pushed to edge notification service
// while service will be processing this event assignment rule chain to edge will be completed if bad timing
Thread.sleep(1000); Thread.sleep(1000);
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getId().getId().toString()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); + "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg); Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg);
RuleChainUpdateMsg ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage; RuleChainUpdateMsg ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage;
@ -410,13 +534,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getName(), savedRuleChain.getName()); Assert.assertEquals(ruleChainUpdateMsg.getName(), savedRuleChain.getName());
// 2
testRuleChainMetadataRequestMsg(savedRuleChain.getId()); testRuleChainMetadataRequestMsg(savedRuleChain.getId());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); + "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg); Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg);
ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage; ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage;
@ -424,17 +549,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits()); Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
// 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString()) doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertFalse(edgeImitator.waitForMessages(1));
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg);
ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
log.info("RuleChains tested successfully"); log.info("RuleChains tested successfully");
} }
@ -452,8 +571,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg);
@ -502,15 +621,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testDashboards() throws Exception { private void testDashboards() throws Exception {
log.info("Testing Dashboards"); log.info("Testing Dashboards");
// 1
edgeImitator.expectMessageAmount(1);
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTitle("Edge Test Dashboard"); dashboard.setTitle("Edge Test Dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
DashboardUpdateMsg dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; DashboardUpdateMsg dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
@ -518,25 +637,24 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits()); Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits()); Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName()); Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName());
testAutoGeneratedCodeByProtobuf(dashboardUpdateMsg); testAutoGeneratedCodeByProtobuf(dashboardUpdateMsg);
// 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
savedDashboard.setTitle("Updated Edge Test Dashboard"); savedDashboard.setTitle("Updated Edge Test Dashboard");
doPost("/api/dashboard", savedDashboard, Dashboard.class); doPost("/api/dashboard", savedDashboard, Dashboard.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType());
Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName()); Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
@ -544,17 +662,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits()); Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits()); Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits());
// 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString()) doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertFalse(edgeImitator.waitForMessages(1));
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType());
Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits());
log.info("Dashboards tested successfully"); log.info("Dashboards tested successfully");
} }
@ -562,19 +674,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testRelations() throws Exception { private void testRelations() throws Exception {
log.info("Testing Relations"); log.info("Testing Relations");
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
Asset asset = findAssetByName("Edge Asset 1"); Asset asset = findAssetByName("Edge Asset 1");
EntityRelation relation = new EntityRelation(); EntityRelation relation = new EntityRelation();
relation.setType("test"); relation.setType("test");
relation.setFrom(device.getId()); relation.setFrom(device.getId());
relation.setTo(asset.getId()); relation.setTo(asset.getId());
relation.setTypeGroup(RelationTypeGroup.COMMON); relation.setTypeGroup(RelationTypeGroup.COMMON);
edgeImitator.expectMessageAmount(1);
doPost("/api/relation", relation); doPost("/api/relation", relation);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage; RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage;
@ -588,6 +698,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name());
Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name());
// 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/relation?" + doDelete("/api/relation?" +
"fromId=" + relation.getFrom().getId().toString() + "fromId=" + relation.getFrom().getId().toString() +
@ -597,8 +708,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
"&toId=" + relation.getTo().getId().toString() + "&toId=" + relation.getTo().getId().toString() +
"&toType=" + relation.getTo().getEntityType().name()) "&toType=" + relation.getTo().getEntityType().name())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
relationUpdateMsg = (RelationUpdateMsg) latestMessage; relationUpdateMsg = (RelationUpdateMsg) latestMessage;
@ -617,18 +727,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testAlarms() throws Exception { private void testAlarms() throws Exception {
log.info("Testing Alarms"); log.info("Testing Alarms");
Device device = findDeviceByName("Edge Device 1");
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
Alarm alarm = new Alarm(); Alarm alarm = new Alarm();
alarm.setOriginator(device.getId()); alarm.setOriginator(device.getId());
alarm.setStatus(AlarmStatus.ACTIVE_UNACK); alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
alarm.setType("alarm"); alarm.setType("alarm");
alarm.setSeverity(AlarmSeverity.CRITICAL); alarm.setSeverity(AlarmSeverity.CRITICAL);
edgeImitator.expectMessageAmount(1);
Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class); Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
@ -639,10 +748,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getStatus(), savedAlarm.getStatus().name()); Assert.assertEquals(alarmUpdateMsg.getStatus(), savedAlarm.getStatus().name());
Assert.assertEquals(alarmUpdateMsg.getSeverity(), savedAlarm.getSeverity().name()); Assert.assertEquals(alarmUpdateMsg.getSeverity(), savedAlarm.getSeverity().name());
// 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/ack"); doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/ack");
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
@ -652,10 +761,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName());
Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.ACTIVE_ACK.name()); Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.ACTIVE_ACK.name());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/clear"); doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/clear");
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
@ -665,26 +774,29 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName());
Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name()); Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name());
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/alarm/" + savedAlarm.getId().getId().toString()) doDelete("/api/alarm/" + savedAlarm.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Alarms tested successfully"); log.info("Alarms tested successfully");
} }
private void testEntityView() throws Exception { private void testEntityView() throws Exception {
log.info("Testing EntityView"); log.info("Testing EntityView");
Device device = findDeviceByName("Edge Device 1");
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
EntityView entityView = new EntityView(); EntityView entityView = new EntityView();
entityView.setName("Edge EntityView 1"); entityView.setName("Edge EntityView 1");
entityView.setType("test"); entityView.setType("test");
entityView.setEntityId(device.getId()); entityView.setEntityId(device.getId());
EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class); EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class);
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getId().getId().toString()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage;
@ -697,11 +809,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(entityViewUpdateMsg.getEntityIdLSB(), device.getUuidId().getLeastSignificantBits()); Assert.assertEquals(entityViewUpdateMsg.getEntityIdLSB(), device.getUuidId().getLeastSignificantBits());
Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name()); Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name());
// 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage;
@ -709,17 +821,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits()); Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits());
Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits()); Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/entityView/" + savedEntityView.getId().getId().toString()) doDelete("/api/entityView/" + savedEntityView.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertFalse(edgeImitator.waitForMessages(1));
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType());
Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits());
Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits());
log.info("EntityView tested successfully"); log.info("EntityView tested successfully");
} }
@ -727,15 +833,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testCustomer() throws Exception { private void testCustomer() throws Exception {
log.info("Testing Customer"); log.info("Testing Customer");
// 1
edgeImitator.expectMessageAmount(1);
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("Edge Customer 1"); customer.setTitle("Edge Customer 1");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
edgeImitator.expectMessageAmount(1);
doPost("/api/customer/" + savedCustomer.getId().getId().toString() doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class); + "/edge/" + edge.getId().getId().toString(), Edge.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) latestMessage; CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) latestMessage;
@ -743,13 +848,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits());
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
Assert.assertEquals(customerUpdateMsg.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(customerUpdateMsg.getTitle(), savedCustomer.getTitle());
testAutoGeneratedCodeByProtobuf(customerUpdateMsg); testAutoGeneratedCodeByProtobuf(customerUpdateMsg);
// 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/edge/" + edge.getId().getId().toString(), Edge.class); doDelete("/api/customer/edge/" + edge.getId().getId().toString(), Edge.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
customerUpdateMsg = (CustomerUpdateMsg) latestMessage; customerUpdateMsg = (CustomerUpdateMsg) latestMessage;
@ -757,17 +861,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits());
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertFalse(edgeImitator.waitForMessages(1));
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
customerUpdateMsg = (CustomerUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, customerUpdateMsg.getMsgType());
Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits());
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
log.info("Customer tested successfully"); log.info("Customer tested successfully");
} }
@ -775,13 +873,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testWidgetsBundleAndWidgetType() throws Exception { private void testWidgetsBundleAndWidgetType() throws Exception {
log.info("Testing WidgetsBundle and WidgetType"); log.info("Testing WidgetsBundle and WidgetType");
// 1
edgeImitator.expectMessageAmount(1);
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("Test Widget Bundle"); widgetsBundle.setTitle("Test Widget Bundle");
edgeImitator.expectMessageAmount(1);
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg);
WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage;
@ -790,20 +887,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits()); Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits());
Assert.assertEquals(widgetsBundleUpdateMsg.getAlias(), savedWidgetsBundle.getAlias()); Assert.assertEquals(widgetsBundleUpdateMsg.getAlias(), savedWidgetsBundle.getAlias());
Assert.assertEquals(widgetsBundleUpdateMsg.getTitle(), savedWidgetsBundle.getTitle()); Assert.assertEquals(widgetsBundleUpdateMsg.getTitle(), savedWidgetsBundle.getTitle());
testAutoGeneratedCodeByProtobuf(widgetsBundleUpdateMsg); testAutoGeneratedCodeByProtobuf(widgetsBundleUpdateMsg);
// 2
edgeImitator.expectMessageAmount(1);
WidgetType widgetType = new WidgetType(); WidgetType widgetType = new WidgetType();
widgetType.setName("Test Widget Type"); widgetType.setName("Test Widget Type");
widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
ObjectNode descriptor = mapper.createObjectNode(); ObjectNode descriptor = mapper.createObjectNode();
descriptor.put("key", "value"); descriptor.put("key", "value");
widgetType.setDescriptor(descriptor); widgetType.setDescriptor(descriptor);
edgeImitator.expectMessageAmount(1);
WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg);
WidgetTypeUpdateMsg widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; WidgetTypeUpdateMsg widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage;
@ -814,11 +909,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(widgetTypeUpdateMsg.getName(), savedWidgetType.getName()); Assert.assertEquals(widgetTypeUpdateMsg.getName(), savedWidgetType.getName());
Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor()); Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString()) doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg);
widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage;
@ -826,11 +921,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(widgetTypeUpdateMsg.getIdMSB(), savedWidgetType.getUuidId().getMostSignificantBits()); Assert.assertEquals(widgetTypeUpdateMsg.getIdMSB(), savedWidgetType.getUuidId().getMostSignificantBits());
Assert.assertEquals(widgetTypeUpdateMsg.getIdLSB(), savedWidgetType.getUuidId().getLeastSignificantBits()); Assert.assertEquals(widgetTypeUpdateMsg.getIdLSB(), savedWidgetType.getUuidId().getLeastSignificantBits());
// 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString()) doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg);
widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage;
@ -843,15 +938,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testTimeseries() throws Exception { private void testTimeseries() throws Exception {
log.info("Testing timeseries"); log.info("Testing timeseries");
Device device = findDeviceByName("Edge Device 1");
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}";
JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); JsonNode timeseriesEntityData = mapper.readTree(timeseriesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent);
edgeEventService.saveAsync(edgeEvent1);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto); Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -890,7 +985,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent); edgeEventService.saveAsync(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto); Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -916,7 +1011,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent); edgeEventService.saveAsync(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto); Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -941,7 +1036,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1); edgeEventService.saveAsync(edgeEvent1);
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto); Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -996,8 +1091,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
@ -1013,7 +1108,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void sendDeviceWithNameThatAlreadyExistsOnCloud() throws Exception { private void sendDeviceWithNameThatAlreadyExistsOnCloud() throws Exception {
String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15); String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15);
Device deviceOnCloud = saveDevice(deviceOnCloudName); Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default");
UUID uuid = Uuids.timeBased(); UUID uuid = Uuids.timeBased();
@ -1033,8 +1128,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
@ -1063,7 +1158,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doPost("/api/relation", relation); doPost("/api/relation", relation);
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
RelationRequestMsg.Builder relationRequestMsgBuilder = RelationRequestMsg.newBuilder(); RelationRequestMsg.Builder relationRequestMsgBuilder = RelationRequestMsg.newBuilder();
@ -1078,8 +1173,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
@ -1115,7 +1210,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
List<AlarmInfo> alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?", List<AlarmInfo> alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?",
@ -1158,7 +1253,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
EntityRelation relation = doGet("/api/relation?" + EntityRelation relation = doGet("/api/relation?" +
"&fromId=" + device2.getId().getId().toString() + "&fromId=" + device2.getId().getId().toString() +
@ -1212,7 +1307,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder2); testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder2);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
// Wait before device attributes saved to database before requesting them from controller // Wait before device attributes saved to database before requesting them from controller
Thread.sleep(1000); Thread.sleep(1000);
@ -1243,8 +1338,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());;
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg);
@ -1270,8 +1365,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg);
@ -1299,8 +1394,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg);
@ -1327,7 +1422,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
} }
private void sendDeviceRpcResponse() throws Exception { private void sendDeviceRpcResponse() throws Exception {
@ -1352,7 +1447,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
} }
private void sendAttributesRequest() throws Exception { private void sendAttributesRequest() throws Exception {
@ -1383,8 +1478,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
edgeImitator.waitForMessages(); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto); Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -1416,7 +1511,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
edgeImitator.waitForResponses(); Assert.assertTrue(edgeImitator.waitForResponses());
device = doGet("/api/device/" + device.getId().getId().toString(), Device.class); device = doGet("/api/device/" + device.getId().getId().toString(), Device.class);
Assert.assertNotNull(device); Assert.assertNotNull(device);
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?", List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?",
@ -1428,7 +1523,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void installation() throws Exception { private void installation() throws Exception {
edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class);
Device savedDevice = saveDevice("Edge Device 1"); DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, null);
extendDeviceProfileData(deviceProfile);
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getId().getId().toString(), Device.class);
@ -1437,6 +1536,35 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
} }
private void extendDeviceProfileData(DeviceProfile deviceProfile) {
DeviceProfileData profileData = deviceProfile.getProfileData();
List<DeviceProfileAlarm> alarms = new ArrayList<>();
DeviceProfileAlarm deviceProfileAlarm = new DeviceProfileAlarm();
deviceProfileAlarm.setAlarmType("High Temperature");
AlarmRule alarmRule = new AlarmRule();
alarmRule.setAlarmDetails("Alarm Details");
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setSpec(new SimpleAlarmConditionSpec());
List<AlarmConditionFilter> condition = new ArrayList<>();
AlarmConditionFilter alarmConditionFilter = new AlarmConditionFilter();
alarmConditionFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
predicate.setValue(new FilterPredicateValue<>(55.0));
alarmConditionFilter.setPredicate(predicate);
alarmConditionFilter.setValueType(EntityKeyValueType.NUMERIC);
condition.add(alarmConditionFilter);
alarmCondition.setCondition(condition);
alarmRule.setCondition(alarmCondition);
deviceProfileAlarm.setClearRule(alarmRule);
TreeMap<AlarmSeverity, AlarmRule> createRules = new TreeMap<>();
createRules.put(AlarmSeverity.CRITICAL, alarmRule);
deviceProfileAlarm.setCreateRules(createRules);
alarms.add(deviceProfileAlarm);
profileData.setAlarms(alarms);
profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123"));
}
private EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) { private EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) {
EdgeEvent edgeEvent = new EdgeEvent(); EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setEdgeId(edgeId); edgeEvent.setEdgeId(edgeId);

65
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -32,6 +32,7 @@ import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.DownlinkMsg; import org.thingsboard.server.gen.edge.DownlinkMsg;
@ -58,6 +59,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@Slf4j @Slf4j
public class EdgeImitator { public class EdgeImitator {
@ -154,88 +156,93 @@ public class EdgeImitator {
private ListenableFuture<List<Void>> processDownlinkMsg(DownlinkMsg downlinkMsg) { private ListenableFuture<List<Void>> processDownlinkMsg(DownlinkMsg downlinkMsg) {
List<ListenableFuture<Void>> result = new ArrayList<>(); List<ListenableFuture<Void>> result = new ArrayList<>();
if (downlinkMsg.getDeviceUpdateMsgList() != null && !downlinkMsg.getDeviceUpdateMsgList().isEmpty()) { if (downlinkMsg.getDeviceUpdateMsgCount() > 0) {
for (DeviceUpdateMsg deviceUpdateMsg: downlinkMsg.getDeviceUpdateMsgList()) { for (DeviceUpdateMsg deviceUpdateMsg: downlinkMsg.getDeviceUpdateMsgList()) {
result.add(saveDownlinkMsg(deviceUpdateMsg)); result.add(saveDownlinkMsg(deviceUpdateMsg));
} }
} }
if (downlinkMsg.getDeviceCredentialsUpdateMsgList() != null && !downlinkMsg.getDeviceCredentialsUpdateMsgList().isEmpty()) { if (downlinkMsg.getDeviceProfileUpdateMsgCount() > 0) {
for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : downlinkMsg.getDeviceProfileUpdateMsgList()) {
result.add(saveDownlinkMsg(deviceProfileUpdateMsg));
}
}
if (downlinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) {
for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg: downlinkMsg.getDeviceCredentialsUpdateMsgList()) { for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg: downlinkMsg.getDeviceCredentialsUpdateMsgList()) {
result.add(saveDownlinkMsg(deviceCredentialsUpdateMsg)); result.add(saveDownlinkMsg(deviceCredentialsUpdateMsg));
} }
} }
if (downlinkMsg.getAssetUpdateMsgList() != null && !downlinkMsg.getAssetUpdateMsgList().isEmpty()) { if (downlinkMsg.getAssetUpdateMsgCount() > 0) {
for (AssetUpdateMsg assetUpdateMsg: downlinkMsg.getAssetUpdateMsgList()) { for (AssetUpdateMsg assetUpdateMsg: downlinkMsg.getAssetUpdateMsgList()) {
result.add(saveDownlinkMsg(assetUpdateMsg)); result.add(saveDownlinkMsg(assetUpdateMsg));
} }
} }
if (downlinkMsg.getRuleChainUpdateMsgList() != null && !downlinkMsg.getRuleChainUpdateMsgList().isEmpty()) { if (downlinkMsg.getRuleChainUpdateMsgCount() > 0) {
for (RuleChainUpdateMsg ruleChainUpdateMsg: downlinkMsg.getRuleChainUpdateMsgList()) { for (RuleChainUpdateMsg ruleChainUpdateMsg: downlinkMsg.getRuleChainUpdateMsgList()) {
result.add(saveDownlinkMsg(ruleChainUpdateMsg)); result.add(saveDownlinkMsg(ruleChainUpdateMsg));
} }
} }
if (downlinkMsg.getRuleChainMetadataUpdateMsgList() != null && !downlinkMsg.getRuleChainMetadataUpdateMsgList().isEmpty()) { if (downlinkMsg.getRuleChainMetadataUpdateMsgCount() > 0) {
for (RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg: downlinkMsg.getRuleChainMetadataUpdateMsgList()) { for (RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg: downlinkMsg.getRuleChainMetadataUpdateMsgList()) {
result.add(saveDownlinkMsg(ruleChainMetadataUpdateMsg)); result.add(saveDownlinkMsg(ruleChainMetadataUpdateMsg));
} }
} }
if (downlinkMsg.getDashboardUpdateMsgList() != null && !downlinkMsg.getDashboardUpdateMsgList().isEmpty()) { if (downlinkMsg.getDashboardUpdateMsgCount() > 0) {
for (DashboardUpdateMsg dashboardUpdateMsg: downlinkMsg.getDashboardUpdateMsgList()) { for (DashboardUpdateMsg dashboardUpdateMsg: downlinkMsg.getDashboardUpdateMsgList()) {
result.add(saveDownlinkMsg(dashboardUpdateMsg)); result.add(saveDownlinkMsg(dashboardUpdateMsg));
} }
} }
if (downlinkMsg.getRelationUpdateMsgList() != null && !downlinkMsg.getRelationUpdateMsgList().isEmpty()) { if (downlinkMsg.getRelationUpdateMsgCount() > 0) {
for (RelationUpdateMsg relationUpdateMsg: downlinkMsg.getRelationUpdateMsgList()) { for (RelationUpdateMsg relationUpdateMsg: downlinkMsg.getRelationUpdateMsgList()) {
result.add(saveDownlinkMsg(relationUpdateMsg)); result.add(saveDownlinkMsg(relationUpdateMsg));
} }
} }
if (downlinkMsg.getAlarmUpdateMsgList() != null && !downlinkMsg.getAlarmUpdateMsgList().isEmpty()) { if (downlinkMsg.getAlarmUpdateMsgCount() > 0) {
for (AlarmUpdateMsg alarmUpdateMsg: downlinkMsg.getAlarmUpdateMsgList()) { for (AlarmUpdateMsg alarmUpdateMsg: downlinkMsg.getAlarmUpdateMsgList()) {
result.add(saveDownlinkMsg(alarmUpdateMsg)); result.add(saveDownlinkMsg(alarmUpdateMsg));
} }
} }
if (downlinkMsg.getEntityDataList() != null && !downlinkMsg.getEntityDataList().isEmpty()) { if (downlinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData: downlinkMsg.getEntityDataList()) { for (EntityDataProto entityData: downlinkMsg.getEntityDataList()) {
result.add(saveDownlinkMsg(entityData)); result.add(saveDownlinkMsg(entityData));
} }
} }
if (downlinkMsg.getEntityViewUpdateMsgList() != null && !downlinkMsg.getEntityViewUpdateMsgList().isEmpty()) { if (downlinkMsg.getEntityViewUpdateMsgCount() > 0) {
for (EntityViewUpdateMsg entityViewUpdateMsg: downlinkMsg.getEntityViewUpdateMsgList()) { for (EntityViewUpdateMsg entityViewUpdateMsg: downlinkMsg.getEntityViewUpdateMsgList()) {
result.add(saveDownlinkMsg(entityViewUpdateMsg)); result.add(saveDownlinkMsg(entityViewUpdateMsg));
} }
} }
if (downlinkMsg.getCustomerUpdateMsgList() != null && !downlinkMsg.getCustomerUpdateMsgList().isEmpty()) { if (downlinkMsg.getCustomerUpdateMsgCount() > 0) {
for (CustomerUpdateMsg customerUpdateMsg: downlinkMsg.getCustomerUpdateMsgList()) { for (CustomerUpdateMsg customerUpdateMsg: downlinkMsg.getCustomerUpdateMsgList()) {
result.add(saveDownlinkMsg(customerUpdateMsg)); result.add(saveDownlinkMsg(customerUpdateMsg));
} }
} }
if (downlinkMsg.getWidgetsBundleUpdateMsgList() != null && !downlinkMsg.getWidgetsBundleUpdateMsgList().isEmpty()) { if (downlinkMsg.getWidgetsBundleUpdateMsgCount() > 0) {
for (WidgetsBundleUpdateMsg widgetsBundleUpdateMsg: downlinkMsg.getWidgetsBundleUpdateMsgList()) { for (WidgetsBundleUpdateMsg widgetsBundleUpdateMsg: downlinkMsg.getWidgetsBundleUpdateMsgList()) {
result.add(saveDownlinkMsg(widgetsBundleUpdateMsg)); result.add(saveDownlinkMsg(widgetsBundleUpdateMsg));
} }
} }
if (downlinkMsg.getWidgetTypeUpdateMsgList() != null && !downlinkMsg.getWidgetTypeUpdateMsgList().isEmpty()) { if (downlinkMsg.getWidgetTypeUpdateMsgCount() > 0) {
for (WidgetTypeUpdateMsg widgetTypeUpdateMsg: downlinkMsg.getWidgetTypeUpdateMsgList()) { for (WidgetTypeUpdateMsg widgetTypeUpdateMsg: downlinkMsg.getWidgetTypeUpdateMsgList()) {
result.add(saveDownlinkMsg(widgetTypeUpdateMsg)); result.add(saveDownlinkMsg(widgetTypeUpdateMsg));
} }
} }
if (downlinkMsg.getUserUpdateMsgList() != null && !downlinkMsg.getUserUpdateMsgList().isEmpty()) { if (downlinkMsg.getUserUpdateMsgCount() > 0) {
for (UserUpdateMsg userUpdateMsg: downlinkMsg.getUserUpdateMsgList()) { for (UserUpdateMsg userUpdateMsg: downlinkMsg.getUserUpdateMsgList()) {
onUserUpdate(userUpdateMsg); onUserUpdate(userUpdateMsg);
result.add(saveDownlinkMsg(userUpdateMsg)); result.add(saveDownlinkMsg(userUpdateMsg));
} }
} }
if (downlinkMsg.getUserCredentialsUpdateMsgList() != null && !downlinkMsg.getUserCredentialsUpdateMsgList().isEmpty()) { if (downlinkMsg.getUserCredentialsUpdateMsgCount() > 0) {
for (UserCredentialsUpdateMsg userCredentialsUpdateMsg: downlinkMsg.getUserCredentialsUpdateMsgList()) { for (UserCredentialsUpdateMsg userCredentialsUpdateMsg: downlinkMsg.getUserCredentialsUpdateMsgList()) {
result.add(saveDownlinkMsg(userCredentialsUpdateMsg)); result.add(saveDownlinkMsg(userCredentialsUpdateMsg));
} }
} }
if (downlinkMsg.getDeviceRpcCallMsgList() != null && !downlinkMsg.getDeviceRpcCallMsgList().isEmpty()) { if (downlinkMsg.getDeviceRpcCallMsgCount() > 0) {
for (DeviceRpcCallMsg deviceRpcCallMsg: downlinkMsg.getDeviceRpcCallMsgList()) { for (DeviceRpcCallMsg deviceRpcCallMsg: downlinkMsg.getDeviceRpcCallMsgList()) {
result.add(saveDownlinkMsg(deviceRpcCallMsg)); result.add(saveDownlinkMsg(deviceRpcCallMsg));
} }
} }
if (downlinkMsg.getDeviceCredentialsRequestMsgList() != null && !downlinkMsg.getDeviceCredentialsRequestMsgList().isEmpty()) { if (downlinkMsg.getDeviceCredentialsRequestMsgCount() > 0) {
for (DeviceCredentialsRequestMsg deviceCredentialsRequestMsg: downlinkMsg.getDeviceCredentialsRequestMsgList()) { for (DeviceCredentialsRequestMsg deviceCredentialsRequestMsg: downlinkMsg.getDeviceCredentialsRequestMsgList()) {
result.add(saveDownlinkMsg(deviceCredentialsRequestMsg)); result.add(saveDownlinkMsg(deviceCredentialsRequestMsg));
} }
@ -256,15 +263,21 @@ public class EdgeImitator {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
public void waitForMessages() throws InterruptedException { public boolean waitForMessages() throws InterruptedException {
messagesLatch.await(5, TimeUnit.SECONDS); return waitForMessages(5);
}
public boolean waitForMessages(int timeout) throws InterruptedException {
return messagesLatch.await(timeout, TimeUnit.SECONDS);
} }
public void expectMessageAmount(int messageAmount) { public void expectMessageAmount(int messageAmount) {
messagesLatch = new CountDownLatch(messageAmount); messagesLatch = new CountDownLatch(messageAmount);
} }
public void waitForResponses() throws InterruptedException { responsesLatch.await(5, TimeUnit.SECONDS); } public boolean waitForResponses() throws InterruptedException {
return responsesLatch.await(5, TimeUnit.SECONDS);
}
public void expectResponsesAmount(int messageAmount) { public void expectResponsesAmount(int messageAmount) {
responsesLatch = new CountDownLatch(messageAmount); responsesLatch = new CountDownLatch(messageAmount);
@ -282,6 +295,18 @@ public class EdgeImitator {
return result; return result;
} }
@SuppressWarnings("unchecked")
public <T extends AbstractMessage> List<T> findAllMessagesByType(Class<T> tClass) {
List<T> result;
try {
lock.lock();
result = (List<T>) downlinkMsgs.stream().filter(downlinkMsg -> downlinkMsg.getClass().isAssignableFrom(tClass)).collect(Collectors.toList());
} finally {
lock.unlock();
}
return result;
}
public AbstractMessage getLatestMessage() { public AbstractMessage getLatestMessage() {
return downlinkMsgs.get(downlinkMsgs.size() - 1); return downlinkMsgs.get(downlinkMsgs.size() - 1);
} }

43
application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.service.resource.*Test",
})
public class ServiceSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
InMemoryStorage.getInstance().cleanup();
}
}

56
dao/src/test/java/org/thingsboard/server/dao/service/BaseTbResourceServiceTest.java → application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java

@ -13,28 +13,37 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.service; package org.thingsboard.server.service.resource;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant; 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.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.AbstractServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public abstract class BaseTbResourceServiceTest extends AbstractServiceTest { import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class BaseTbResourceServiceTest extends AbstractControllerTest {
private static final String LWM2M_TEST_MODEL = "<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd\">\n" + private static final String LWM2M_TEST_MODEL = "<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd\">\n" +
"<Object ObjectType=\"MODefinition\">\n" + "<Object ObjectType=\"MODefinition\">\n" +
@ -67,18 +76,38 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
private TenantId tenantId; private TenantId tenantId;
@Autowired
private TbResourceService resourceService;
private Tenant savedTenant;
private User tenantAdmin;
@Before @Before
public void before() { public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant); savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId(); tenantId = savedTenant.getId();
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 @After
public void after() { public void afterTest() throws Exception {
tenantService.deleteTenant(tenantId); loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
@ -239,9 +268,10 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
@Test @Test
public void testFindTenantResourcesByTenantId() throws Exception { public void testFindTenantResourcesByTenantId() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("Test tenant"); tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant); tenant = doPost("/api/tenant", tenant, Tenant.class);
TenantId tenantId = tenant.getId(); TenantId tenantId = tenant.getId();
@ -279,14 +309,17 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty()); Assert.assertTrue(pageData.getData().isEmpty());
tenantService.deleteTenant(tenantId); doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testFindAllTenantResourcesByTenantId() throws Exception { public void testFindAllTenantResourcesByTenantId() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("Test tenant"); tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant); tenant = doPost("/api/tenant", tenant, Tenant.class);
TenantId tenantId = tenant.getId(); TenantId tenantId = tenant.getId();
@ -344,7 +377,8 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty()); Assert.assertTrue(pageData.getData().isEmpty());
tenantService.deleteTenant(tenantId); doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
} }
} }

3
common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.event;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -41,6 +42,8 @@ public interface EventService {
List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit); List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
PageData<Event> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
void removeEvents(TenantId tenantId, EntityId entityId); void removeEvents(TenantId tenantId, EntityId entityId);
} }

50
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.List;
public interface ResourceService {
TbResource saveResource(TbResource resource);
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
List<TbResource> findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType lwm2mModel, String[] objectIds);
PageData<TbResource> findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType lwm2mModel, PageLink pageLink);
void deleteResource(TenantId tenantId, TbResourceId resourceId);
void deleteResourcesByTenantId(TenantId tenantId);
}

4
common/data/pom.xml

@ -87,10 +87,6 @@
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>protobuf-dynamic</artifactId> <artifactId>protobuf-dynamic</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

27
common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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 StringUtils {
public static boolean isEmpty(String source) {
return source == null || source.isEmpty();
}
public static boolean isNotEmpty(String source) {
return source != null && !source.isEmpty();
}
}

45
common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public abstract class DebugEvent implements EventFilter {
private String msgDirectionType;
private String server;
private String dataSearch;
private String metadataSearch;
private String entityName;
private String relationType;
private String entityId;
private String msgType;
private boolean isError;
private String error;
public void setIsError(boolean isError) {
this.isError = isError;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(msgDirectionType) || !StringUtils.isEmpty(server) || !StringUtils.isEmpty(dataSearch) || !StringUtils.isEmpty(metadataSearch)
|| !StringUtils.isEmpty(entityName) || !StringUtils.isEmpty(relationType) || !StringUtils.isEmpty(entityId) || !StringUtils.isEmpty(msgType) || !StringUtils.isEmpty(error) || isError;
}
}

23
common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
public class DebugRuleChainEventFilter extends DebugEvent {
@Override
public EventType getEventType() {
return EventType.DEBUG_RULE_CHAIN;
}
}

23
common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
public class DebugRuleNodeEventFilter extends DebugEvent {
@Override
public EventType getEventType() {
return EventType.DEBUG_RULE_NODE;
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public class ErrorEventFilter implements EventFilter {
private String server;
private String method;
private String error;
@Override
public EventType getEventType() {
return EventType.ERROR;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(method) || !StringUtils.isEmpty(error);
}
}

40
common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "eventType")
@JsonSubTypes({
@JsonSubTypes.Type(value = DebugRuleNodeEventFilter.class, name = "DEBUG_RULE_NODE"),
@JsonSubTypes.Type(value = DebugRuleChainEventFilter.class, name = "DEBUG_RULE_CHAIN"),
@JsonSubTypes.Type(value = ErrorEventFilter.class, name = "ERROR"),
@JsonSubTypes.Type(value = LifeCycleEventFilter.class, name = "LC_EVENT"),
@JsonSubTypes.Type(value = StatisticsEventFilter.class, name = "STATS")
})
public interface EventFilter {
@JsonIgnore
EventType getEventType();
boolean hasFilterForJsonBody();
}

20
common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
public enum EventType {
ERROR, LC_EVENT, STATS, DEBUG_RULE_NODE, DEBUG_RULE_CHAIN
}

37
common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public class LifeCycleEventFilter implements EventFilter {
private String server;
private String event;
private String status;
private String error;
@Override
public EventType getEventType() {
return EventType.LC_EVENT;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(event) || !StringUtils.isEmpty(status) || !StringUtils.isEmpty(error);
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public class StatisticsEventFilter implements EventFilter {
private String server;
private Integer messagesProcessed;
private Integer errorsOccurred;
@Override
public EventType getEventType() {
return EventType.STATS;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(server) || (messagesProcessed != null && messagesProcessed > 0) || (errorsOccurred != null && errorsOccurred > 0);
}
}

7
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java

@ -40,7 +40,7 @@ import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -70,8 +70,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override @Override
public List<SecurityInfo> getAllByEndpoint(String endPoint) { public List<SecurityInfo> getAllByEndpoint(String endPoint) {
String endPointKey = endPoint; ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, TypeServer.BOOTSTRAP);
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPointKey, TypeServer.BOOTSTRAP);
if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
/** add value to store from BootstrapJson */ /** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store); this.setBootstrapConfigScurityInfo(store);
@ -87,7 +86,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
} catch (InvalidConfigurationException e) { } catch (InvalidConfigurationException e) {
log.error("", e); log.error("", e);
} }
return store.getSecurityInfo() == null ? null : Arrays.asList(store.getSecurityInfo()); return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo());
} }
} }
return null; return null;

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java

@ -23,5 +23,5 @@ public class LwM2MBootstrapServers {
private Integer lifetime = 300; private Integer lifetime = 300;
private Integer defaultMinPeriod = 1; private Integer defaultMinPeriod = 1;
private boolean notifIfDisabled = true; private boolean notifIfDisabled = true;
private String binding = "U"; private String binding = "UQ";
} }

5
ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mQueuedRequest.java

@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.transport.lwm2m.server;
:host { public interface LwM2mQueuedRequest {
min-width: 364px; void send();
} }

11
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
@ -74,12 +75,12 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
@Override @Override
public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) {
log.info("[{}] toDeviceRpcRequest", toDeviceRequest); this.service.onToDeviceRpcRequest(toDeviceRequest);
} }
@Override @Override
public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) {
log.info("[{}] toServerRpcResponse", toServerResponse); this.service.onToServerRpcResponse(toServerResponse);
} }
@Override @Override
@ -87,13 +88,15 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
log.info("[{}] operationComplete", future); log.info("[{}] operationComplete", future);
} }
public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) { @Override
public void onResourceUpdate(@NotNull Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
if (ResourceType.LWM2M_MODEL.name().equals(resourceUpdateMsgOpt.get().getResourceType())) { if (ResourceType.LWM2M_MODEL.name().equals(resourceUpdateMsgOpt.get().getResourceType())) {
this.service.onResourceUpdate(resourceUpdateMsgOpt); this.service.onResourceUpdate(resourceUpdateMsgOpt);
} }
} }
public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) { @Override
public void onResourceDelete(@NotNull Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
if (ResourceType.LWM2M_MODEL.name().equals(resourceDeleteMsgOpt.get().getResourceType())) { if (ResourceType.LWM2M_MODEL.name().equals(resourceDeleteMsgOpt.get().getResourceType())) {
this.service.onResourceDelete(resourceDeleteMsgOpt); this.service.onResourceDelete(resourceDeleteMsgOpt);
} }

131
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java

@ -22,15 +22,14 @@ import com.google.gson.JsonSyntaxException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.core.attributes.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
@ -44,11 +43,19 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION;
import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
@ -56,8 +63,19 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
public class LwM2mTransportHandler { public class LwM2mTransportHandler {
public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
public static final String BOOTSTRAP = "bootstrap";
public static final String SERVERS = "servers";
public static final String LWM2M_SERVER = "lwm2mServer";
public static final String BOOTSTRAP_SERVER = "bootstrapServer";
public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
public static final String ATTRIBUTE = "attribute"; public static final String ATTRIBUTE = "attribute";
public static final String TELEMETRY = "telemetry"; public static final String TELEMETRY = "telemetry";
public static final String KEY_NAME = "keyName";
public static final String OBSERVE = "observe";
public static final String ATTRIBUTE_LWM2M = "attributeLwm2m";
private static final String REQUEST = "/request"; private static final String REQUEST = "/request";
private static final String RESPONSE = "/response"; private static final String RESPONSE = "/response";
private static final String ATTRIBUTES = "/" + ATTRIBUTE; private static final String ATTRIBUTES = "/" + ATTRIBUTE;
@ -70,14 +88,6 @@ public class LwM2mTransportHandler {
public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES; public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES;
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
public static final String KEY_NAME = "keyName";
public static final String OBSERVE = "observe";
public static final String BOOTSTRAP = "bootstrap";
public static final String SERVERS = "servers";
public static final String LWM2M_SERVER = "lwm2mServer";
public static final String BOOTSTRAP_SERVER = "bootstrapServer";
public static final String LOG_LW2M_TELEMETRY = "logLwm2m"; public static final String LOG_LW2M_TELEMETRY = "logLwm2m";
public static final String LOG_LW2M_INFO = "info"; public static final String LOG_LW2M_INFO = "info";
@ -144,19 +154,19 @@ public class LwM2mTransportHandler {
throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type); throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type);
} }
} }
//
public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) { // public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) {
if (content instanceof LwM2mObject) { // if (content instanceof LwM2mObject) {
return (LwM2mObject) content; // return (LwM2mObject) content;
} else if (content instanceof LwM2mObjectInstance) { // } else if (content instanceof LwM2mObjectInstance) {
return (LwM2mObjectInstance) content; // return (LwM2mObjectInstance) content;
} else if (content instanceof LwM2mSingleResource) { // } else if (content instanceof LwM2mSingleResource) {
return (LwM2mSingleResource) content; // return (LwM2mSingleResource) content;
} else if (content instanceof LwM2mMultipleResource) { // } else if (content instanceof LwM2mMultipleResource) {
return (LwM2mMultipleResource) content; // return (LwM2mMultipleResource) content;
} // }
return null; // return null;
} // }
public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) { public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) {
LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile(); LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile();
@ -166,6 +176,7 @@ public class LwM2mTransportHandler {
lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray()); lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray());
lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray()); lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray());
lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).getAsJsonArray()); lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).getAsJsonArray());
lwM2MClientProfile.setPostAttributeLwm2mProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).getAsJsonObject());
return lwM2MClientProfile; return lwM2MClientProfile;
} }
@ -184,6 +195,9 @@ public class LwM2mTransportHandler {
* "attribute":["/2/0/1","/3/0/9"], * "attribute":["/2/0/1","/3/0/9"],
* "telemetry":["/1/0/1","/2/0/1","/6/0/1"], * "telemetry":["/1/0/1","/2/0/1","/6/0/1"],
* "observe":["/2/0","/2/0/0","/4/0/2"]} * "observe":["/2/0","/2/0/0","/4/0/2"]}
* "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"},
* "/3_1.0/0": {"gt": 17},
* "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
*/ */
public static LwM2mClientProfile getLwM2MClientProfileFromThingsboard(DeviceProfile deviceProfile) { public static LwM2mClientProfile getLwM2MClientProfileFromThingsboard(DeviceProfile deviceProfile) {
if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
@ -192,7 +206,7 @@ public class LwM2mTransportHandler {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
String profileStr = mapper.writeValueAsString(profile); String profileStr = mapper.writeValueAsString(profile);
JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null;
return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
} catch (IOException e) { } catch (IOException e) {
log.error("", e); log.error("", e);
} }
@ -240,7 +254,10 @@ public class LwM2mTransportHandler {
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonArray() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonArray() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE) && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE) &&
!objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonNull() && !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonNull() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray()); objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE_LWM2M) &&
!objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonNull() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonObject());
} }
private static boolean getValidateBootstrapProfileFromThingsboard(JsonObject objectMsg) { private static boolean getValidateBootstrapProfileFromThingsboard(JsonObject objectMsg) {
@ -345,14 +362,6 @@ public class LwM2mTransportHandler {
} }
} }
public static Integer validateObjectIdFromKey(String key) {
try {
return Integer.parseInt(key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[0]);
} catch (Exception e) {
return null;
}
}
public static String validateObjectVerFromKey(String key) { public static String validateObjectVerFromKey(String key) {
try { try {
return (key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[1]); return (key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[1]);
@ -360,4 +369,54 @@ public class LwM2mTransportHandler {
return ObjectModel.DEFAULT_VERSION; return ObjectModel.DEFAULT_VERSION;
} }
} }
/**
* As example:
* a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
* to the Server with a minimum interval of 1sec;
* this value is set at theResource level.
* b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
* to the Server with a minimum value (pmin) given by the default one
* (resource 2 of Object Server ID=1),
* or with another value if this Attribute has been set at another level
* (Object or Object Instance: see section5.1.1).
* c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
* will be notified to the Server with a minimum interval of 10 sec;
* this value is set at the Object Instance level.
* d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 20 and new value is 35 due to step condition
* b.old value is 45 and new value is 50 due to gt condition
* c.old value is 50 and new value is 40 due to both gt and step conditions
* d.old value is 35 and new value is 20 due to step conditione)
* Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 17 and new value is 24 due to lt condition
* b.old value is 75 and new value is 90 due to both gt and step conditions
* String uriQueries = "pmin=10&pmax=60";
* AttributeSet attributes = AttributeSet.parse(uriQueries);
* WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
* Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
* Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
* Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
* Attribute [] attrs = {gt, st};
*/
public static DownlinkRequest createWriteAttributeRequest(String target, Object params) {
AttributeSet attrSet = new AttributeSet(createWriteAttributes(params));
return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null;
}
private static Attribute[] createWriteAttributes(Object params) {
List attributeLists = new ArrayList<Attribute>();
ObjectMapper oMapper = new ObjectMapper();
Map<String, Object> map = oMapper.convertValue(params, ConcurrentHashMap.class);
map.forEach((k, v) -> {
if (!v.toString().isEmpty() || (v.toString().isEmpty() && OBJECT_VERSION.equals(k))) {
attributeLists.add(new Attribute(k,
(DIMENSION.equals(k) || MINIMUM_PERIOD.equals(k) || MAXIMUM_PERIOD.equals(k)) ?
((Double) v).longValue() : v));
}
});
return (Attribute[]) attributeLists.toArray(Attribute[]::new);
}
} }

84
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java

@ -16,9 +16,8 @@
package org.thingsboard.server.transport.lwm2m.server; package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.coap.Response;
import org.eclipse.leshan.core.attributes.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
@ -32,8 +31,8 @@ import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.exception.ClientSleepingException;
import org.eclipse.leshan.core.response.CancelObservationResponse; import org.eclipse.leshan.core.response.CancelObservationResponse;
import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
@ -58,8 +57,6 @@ import java.util.Date;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.isSuccess;
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE;
@ -74,6 +71,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest;
@Slf4j @Slf4j
@Service @Service
@ -122,7 +120,7 @@ public class LwM2mTransportRequest {
DownlinkRequest request = null; DownlinkRequest request = null;
ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null; ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null;
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
ResourceModel resource = lwM2MClient.getResourceModel(targetIdVer); ResourceModel resource = null;
timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT;
switch (typeOper) { switch (typeOper) {
case GET_TYPE_OPER_READ: case GET_TYPE_OPER_READ:
@ -144,6 +142,7 @@ public class LwM2mTransportRequest {
request = new CancelObservationRequest(observation); request = new CancelObservationRequest(observation);
break; break;
case POST_TYPE_OPER_EXECUTE: case POST_TYPE_OPER_EXECUTE:
resource = lwM2MClient.getResourceModel(targetIdVer);
if (params != null && resource != null && !resource.multiple) { if (params != null && resource != null && !resource.multiple) {
request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds)); request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds));
} else { } else {
@ -152,6 +151,7 @@ public class LwM2mTransportRequest {
break; break;
case POST_TYPE_OPER_WRITE_REPLACE: case POST_TYPE_OPER_WRITE_REPLACE:
// Request to write a <b>String Single-Instance Resource</b> using the TLV content format. // Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
resource = lwM2MClient.getResourceModel(targetIdVer);
if (resource != null && contentFormat != null) { if (resource != null && contentFormat != null) {
// if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { // if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
if (contentFormat.equals(ContentFormat.TLV)) { if (contentFormat.equals(ContentFormat.TLV)) {
@ -173,55 +173,21 @@ public class LwM2mTransportRequest {
} }
break; break;
case PUT_TYPE_OPER_WRITE_ATTRIBUTES: case PUT_TYPE_OPER_WRITE_ATTRIBUTES:
/** request = createWriteAttributeRequest(target, params);
* As example:
* a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
* to the Server with a minimum interval of 1sec;
* this value is set at theResource level.
* b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
* to the Server with a minimum value (pmin) given by the default one
* (resource 2 of Object Server ID=1),
* or with another value if this Attribute has been set at another level
* (Object or Object Instance: see section5.1.1).
* c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
* will be notified to the Server with a minimum interval of 10 sec;
* this value is set at the Object Instance level.
* d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 20 and new value is 35 due to step condition
* b.old value is 45 and new value is 50 due to gt condition
* c.old value is 50 and new value is 40 due to both gt and step conditions
* d.old value is 35 and new value is 20 due to step conditione)
* Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 17 and new value is 24 due to lt condition
* b.old value is 75 and new value is 90 due to both gt and step conditions
* String uriQueries = "pmin=10&pmax=60";
* AttributeSet attributes = AttributeSet.parse(uriQueries);
* WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
* Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
* Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
* Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
* Attribute [] attrs = {gt, st};
*/
Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.parseInt("1")));
Attribute[] attrs = {pmin};
AttributeSet attrSet = new AttributeSet(attrs);
if (resultIds.isResource()) {
request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), attrSet);
} else if (resultIds.isObjectInstance()) {
request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), attrSet);
} else if (resultIds.getObjectId() >= 0) {
request = new WriteAttributesRequest(resultIds.getObjectId(), attrSet);
}
break; break;
default:
} }
if (request != null) { if (request != null) {
this.sendRequest(registration, lwM2MClient, request, timeoutInMs); try {
} this.sendRequest(registration, lwM2MClient, request, timeoutInMs);
else { } catch (ClientSleepingException e) {
DownlinkRequest finalRequest = request;
long finalTimeoutInMs = timeoutInMs;
lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs));
} catch (Exception e) {
log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper, e);
}
} else {
log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer); log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer);
} }
} }
@ -240,15 +206,19 @@ public class LwM2mTransportRequest {
if (!lwM2MClient.isInit()) { if (!lwM2MClient.isInit()) {
lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration)); lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration));
} }
if (isSuccess(((Response) response.getCoapResponse()).getCode())) { if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) {
this.handleResponse(registration, request.getPath().toString(), response, request); this.handleResponse(registration, request.getPath().toString(), response, request);
if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) { if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) {
LwM2mNode node = ((WriteRequest) request).getNode();
Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(),
((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath());
String msg = String.format("%s: sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client", String msg = String.format("%s: sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client",
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue().toString()); response.getCode().getName(), request.getPath().toString(), value);
serviceImpl.sendLogsToThingsboard(msg, registration); serviceImpl.sendLogsToThingsboard(msg, registration);
log.info("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), log.debug("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue()); ((Response) response.getCoapResponse()).getCode(), response.getCode(),
request.getPath().toString(), value);
} }
} else { } else {
String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR, String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR,
@ -283,7 +253,7 @@ public class LwM2mTransportRequest {
case FLOAT: // Double case FLOAT: // Double
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.parseDouble(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString())); return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.parseDouble(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString()));
case TIME: // Date case TIME: // Date
Date date = new Date(Long.decode(value.toString())); Date date = new Date(Long.decode(value.toString()));
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date); return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date);
case OPAQUE: // byte[] value, base64 case OPAQUE: // byte[] value, base64
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())); return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray()));

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java

@ -51,6 +51,10 @@ public interface LwM2mTransportService {
void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt); void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt);
void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest);
void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse);
void doTrigger(Registration registration, String path); void doTrigger(Registration registration, String path);
void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); void doDisconnect(TransportProtos.SessionInfoProto sessionInfo);

265
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java

@ -74,11 +74,14 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject; import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR;
@ -87,11 +90,11 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectIdFromKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey;
@Slf4j @Slf4j
@ -146,9 +149,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* Next -> Create new LwM2MClient for current session -> setModelClient... * Next -> Create new LwM2MClient for current session -> setModelClient...
* *
* @param registration - Registration LwM2M Client * @param registration - Registration LwM2M Client
* @param previousObsersations - may be null * @param previousObservations - may be null
*/ */
public void onRegistered(Registration registration, Collection<Observation> previousObsersations) { public void onRegistered(Registration registration, Collection<Observation> previousObservations) {
executorRegistered.submit(() -> { executorRegistered.submit(() -> {
try { try {
log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId());
@ -188,6 +191,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2MClient(sessionInfo); LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2MClient(sessionInfo);
if (lwM2MClient.getDeviceId() == null && lwM2MClient.getProfileId() == null) { if (lwM2MClient.getDeviceId() == null && lwM2MClient.getProfileId() == null) {
initLwM2mClient(lwM2MClient, sessionInfo); initLwM2mClient(lwM2MClient, sessionInfo);
} else {
if (registration.getBindingMode().useQueueMode()) {
LwM2mQueuedRequest request;
while ((request = lwM2MClient.getQueuedRequests().poll()) != null) {
request.send();
}
}
} }
log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
@ -239,8 +249,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
} }
} }
@Override
public void onSleepingDev(Registration registration) { public void onSleepingDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is sleeping!", registration);
//TODO: associate endpointId with device information. //TODO: associate endpointId with device information.
} }
@ -355,17 +368,15 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
} }
/** /**
*
* @param resourceUpdateMsgOpt - * @param resourceUpdateMsgOpt -
*/ */
@Override @Override
public void onResourceUpdate (Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) { public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
String idVer = resourceUpdateMsgOpt.get().getResourceKey(); String idVer = resourceUpdateMsgOpt.get().getResourceKey();
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
} }
/** /**
*
* @param resourceDeleteMsgOpt - * @param resourceDeleteMsgOpt -
*/ */
@Override @Override
@ -374,6 +385,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
} }
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest) {
log.info("[{}] toDeviceRpcRequest", toDeviceRequest);
}
public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) {
log.info("[{}] toServerRpcResponse", toServerResponse);
}
/** /**
* Trigger Server path = "/1/0/8" * Trigger Server path = "/1/0/8"
* <p> * <p>
@ -417,6 +436,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/ */
protected void onAwakeDev(Registration registration) { protected void onAwakeDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration);
//TODO: associate endpointId with device information. //TODO: associate endpointId with device information.
} }
@ -485,15 +505,20 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/ */
private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) { private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
Set<String> clientObjects = this.getAllOjectsInClient(registration); Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
if (clientObjects != null && LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { if (clientObjects != null && clientObjects.size() > 0) {
// #2 if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
lwM2MClient.getPendingRequests().addAll(clientObjects); // #2
clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), lwM2MClient.getPendingRequests().addAll(clientObjects);
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(),
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
}
// #1
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_READ, clientObjects);
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE, clientObjects);
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, PUT_TYPE_OPER_WRITE_ATTRIBUTES, clientObjects);
// this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_DISCOVER, clientObjects);
} }
// #1
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE);
} }
/** /**
@ -600,41 +625,61 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/** /**
* Start observe/read: Attr/Telemetry * Start observe/read: Attr/Telemetry
* #1 - Analyze: * #1 - Analyze: path in resource profile == client resource
* #1.1 path in resource profile == client resource
* *
* @param registration - * @param registration -
*/ */
private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, String typeOper) { private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient,
String typeOper, Set<String> clientObjects) {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
Set<String> clientInstances = this.getAllInstancesInClient(registration); Set<String> result = null;
Set<String> result; ConcurrentHashMap<String, Object> params = null;
if (GET_TYPE_OPER_READ.equals(typeOper)) { if (GET_TYPE_OPER_READ.equals(typeOper)) {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), new TypeReference<>() { result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(),
}); new TypeReference<>() {
result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), new TypeReference<>() { });
})); result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(),
} else { new TypeReference<>() {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), new TypeReference<>() { }));
}); } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(),
new TypeReference<>() {
});
} else if (GET_TYPE_OPER_DISCOVER.equals(typeOper)) {
result = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()).keySet();
;
} else if (PUT_TYPE_OPER_WRITE_ATTRIBUTES.equals(typeOper)) {
params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile());
result = params.keySet();
} }
Set<String> pathSend = ConcurrentHashMap.newKeySet(); if (!result.isEmpty()) {
result.forEach(target -> { // #1
// #1.1 Set<String> pathSend = result.stream().filter(target -> {
String[] resPath = target.split("/"); return target.split(LWM2M_SEPARATOR_PATH).length < 3 ?
String instance = "/" + resPath[1] + "/" + resPath[2]; clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]) :
if (clientInstances != null && clientInstances.size() > 0 && clientInstances.contains(instance)) { clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1] + "/" + target.split(LWM2M_SEPARATOR_PATH)[2]);
pathSend.add(target); }
)
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
lwM2MClient.getPendingRequests().addAll(pathSend);
ConcurrentHashMap<String, Object> finalParams = params;
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
null, finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
lwM2MClient.initValue(this, null);
}
} }
});
lwM2MClient.getPendingRequests().addAll(pathSend);
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
lwM2MClient.initValue(this, null);
} }
} }
private ConcurrentHashMap<String, Object> getPathForWriteAttributes(JsonObject objectJson) {
ConcurrentHashMap<String, Object> pathAttributes = new Gson().fromJson(objectJson.toString(),
new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
return pathAttributes;
}
/** /**
* Update parameters device in LwM2MClient * Update parameters device in LwM2MClient
* If new deviceProfile != old deviceProfile => update deviceProfile * If new deviceProfile != old deviceProfile => update deviceProfile
@ -654,21 +699,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
lwM2MClient.setProfileId(device.getDeviceProfileId().getId()); lwM2MClient.setProfileId(device.getDeviceProfileId().getId());
} }
/**
* @param registration -
* @return - all object in client
*/
private Set<String> getAllOjectsInClient(Registration registration) {
Set<String> clientObjects = ConcurrentHashMap.newKeySet();
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
LwM2mPath pathIds = new LwM2mPath(url.getUrl());
if (pathIds.isObjectInstance()) {
clientObjects.add("/" + pathIds.getObjectId());
}
});
return (clientObjects.size() > 0) ? clientObjects : null;
}
/** /**
* @param registration - * @param registration -
* @return all instances in client * @return all instances in client
@ -713,16 +743,18 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
private void addParameters(String path, JsonObject parameters, Registration registration) { private void addParameters(String path, JsonObject parameters, Registration registration) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile();
String resName = names.get(path).getAsString(); if (names != null && names.has(path)) {
if (resName != null && !resName.isEmpty()) { String resName = names.get(path).getAsString();
try { if (resName != null && !resName.isEmpty()) {
String resValue = this.getResourceValueToString(lwM2MClient, path); try {
if (resValue != null) { String resValue = this.getResourceValueToString(lwM2MClient, path);
parameters.addProperty(resName, resValue); parameters.addProperty(resName, resValue);
} catch (Exception e) {
log.error("Failed to add parameters.", e);
} }
} catch (Exception e) {
log.error("Failed to add parameters.", e);
} }
} else {
log.error("Failed to add parameters. path: [{}], names: [{}]", path, names);
} }
} }
@ -739,7 +771,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/** /**
* @param lwM2MClient - * @param lwM2MClient -
* @param path - * @param path -
* @return - return value of Resource by idPath * @return - return value of Resource by idPath
*/ */
private LwM2mResource returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) { private LwM2mResource returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) {
@ -768,6 +800,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* #3.1 Attribute isChange (add&del) * #3.1 Attribute isChange (add&del)
* #3.2 Telemetry isChange (add&del) * #3.2 Telemetry isChange (add&del)
* #3.3 KeyName isChange (add) * #3.3 KeyName isChange (add)
* #3.4 attributeLwm2m isChange (update WrightAttribute: add/update/del)
* #4 update * #4 update
* #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard
* #4.2 del * #4.2 del
@ -778,6 +811,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client * -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client
* #5.3 Observe.del * #5.3 Observe.del
* -- different between newObserve and oldObserve: send Request cancel observe to client * -- different between newObserve and oldObserve: send Request cancel observe to client
* #6
* #6.1 - update WriteAttribute
* #6.2 - del WriteAttribute
* *
* @param registrationIds - * @param registrationIds -
* @param deviceProfile - * @param deviceProfile -
@ -792,6 +828,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
Set<String> telemetrySetOld = this.convertJsonArrayToSet(telemetryOld); Set<String> telemetrySetOld = this.convertJsonArrayToSet(telemetryOld);
JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile(); JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile();
JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile();
JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile();
LwM2mClientProfile lwM2MClientProfileNew = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId()); LwM2mClientProfile lwM2MClientProfileNew = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId());
JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile();
@ -800,32 +837,41 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
Set<String> telemetrySetNew = this.convertJsonArrayToSet(telemetryNew); Set<String> telemetrySetNew = this.convertJsonArrayToSet(telemetryNew);
JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile(); JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile();
JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile(); JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile();
JsonObject attributeLwm2mNew = lwM2MClientProfileNew.getPostAttributeLwm2mProfile();
// #3 // #3
ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters(); ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters();
// #3.1 // #3.1
if (!attributeOld.equals(attributeNew)) { if (!attributeOld.equals(attributeNew)) {
ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, new TypeToken<Set<String>>() { ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld,
}.getType()), attributeSetNew); new TypeToken<Set<String>>() {
}.getType()), attributeSetNew);
sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd());
sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel());
} }
// #3.2 // #3.2
if (!telemetryOld.equals(telemetryNew)) { if (!telemetryOld.equals(telemetryNew)) {
ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, new TypeToken<Set<String>>() { ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld,
}.getType()), telemetrySetNew); new TypeToken<Set<String>>() {
}.getType()), telemetrySetNew);
sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd());
sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel());
} }
// #3.3 // #3.3
if (!keyNameOld.equals(keyNameNew)) { if (!keyNameOld.equals(keyNameNew)) {
ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), new TypeToken<ConcurrentHashMap<String, String>>() { ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(),
new TypeToken<ConcurrentHashMap<String, String>>() {
}.getType()), }.getType()),
new Gson().fromJson(keyNameNew.toString(), new TypeToken<ConcurrentHashMap<String, String>>() { new Gson().fromJson(keyNameNew.toString(), new TypeToken<ConcurrentHashMap<String, String>>() {
}.getType())); }.getType()));
sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd());
} }
// #3.4, #6
if (!attributeLwm2mOld.equals(attributeLwm2mNew)) {
this.getAnalyzerAttributeLwm2m(registrationIds, attributeLwm2mOld, attributeLwm2mNew);
}
// #4.1 add // #4.1 add
if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) { if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) {
// update value in Resources // update value in Resources
@ -859,10 +905,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
// send Request observe to Client // send Request observe to Client
registrationIds.forEach(registrationId -> { registrationIds.forEach(registrationId -> {
Registration registration = lwM2mClientContext.getRegistration(registrationId); Registration registration = lwM2mClientContext.getRegistration(registrationId);
this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) {
this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE);
}
// 5.3 del // 5.3 del
// send Request cancel observe to Client // send Request cancel observe to Client
this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel()); if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) {
this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel());
}
}); });
} }
} }
@ -922,7 +972,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}); });
} }
private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap<String, String> keyNameOld, ConcurrentMap<String, String> keyNameNew) { private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentHashMap<String, String> keyNameOld, ConcurrentHashMap<String, String> keyNameNew) {
ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
Set<String> paths = keyNameNew.entrySet() Set<String> paths = keyNameNew.entrySet()
.stream() .stream()
@ -932,6 +982,71 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
return analyzerParameters; return analyzerParameters;
} }
/**
* #3.4, #6
* #6
* #6.1 - send update WriteAttribute
* #6.2 - send empty WriteAttribute
*
* @param attributeLwm2mOld -
* @param attributeLwm2mNew -
* @return
*/
private void getAnalyzerAttributeLwm2m(Set<String> registrationIds, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) {
ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
ConcurrentHashMap<String, Object> lwm2mAttributesOld = new Gson().fromJson(attributeLwm2mOld.toString(),
new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
ConcurrentHashMap<String, Object> lwm2mAttributesNew = new Gson().fromJson(attributeLwm2mNew.toString(),
new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
Set<String> pathOld = lwm2mAttributesOld.keySet();
Set<String> pathNew = lwm2mAttributesNew.keySet();
analyzerParameters.setPathPostParametersAdd(pathNew
.stream().filter(p -> !pathOld.contains(p)).collect(Collectors.toSet()));
analyzerParameters.setPathPostParametersDel(pathOld
.stream().filter(p -> !pathNew.contains(p)).collect(Collectors.toSet()));
Set<String> pathCommon = pathNew
.stream().filter(p -> pathOld.contains(p)).collect(Collectors.toSet());
Set<String> pathCommonChange = pathCommon
.stream().filter(p -> !lwm2mAttributesOld.get(p).equals(lwm2mAttributesNew.get(p))).collect(Collectors.toSet());
analyzerParameters.getPathPostParametersAdd().addAll(pathCommonChange);
// #6
// #6.2
if (analyzerParameters.getPathPostParametersAdd().size() > 0) {
registrationIds.forEach(registrationId -> {
Registration registration = this.lwM2mClientContext.getRegistration(registrationId);
Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
Set<String> pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
ConcurrentHashMap<String, Object> finalParams = lwm2mAttributesNew;
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
null, finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
}
});
}
// #6.2
if (analyzerParameters.getPathPostParametersDel().size() > 0) {
registrationIds.forEach(registrationId -> {
Registration registration = this.lwM2mClientContext.getRegistration(registrationId);
Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
Set<String> pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
pathSend.forEach(target -> {
Map<String, Object> params = (Map<String, Object>) lwm2mAttributesOld.get(target);
params.clear();
params.put(OBJECT_VERSION, "");
lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
null, params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout());
});
}
});
}
}
private void cancelObserveIsValue(Registration registration, Set<String> paramAnallyzer) { private void cancelObserveIsValue(Registration registration, Set<String> paramAnallyzer) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
paramAnallyzer.forEach(p -> { paramAnallyzer.forEach(p -> {
@ -1140,11 +1255,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
return new ArrayList<>(namesIsWritable); return new ArrayList<>(namesIsWritable);
} }
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathKey, boolean isWritable) { private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathKey); ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer);
Integer objectId = validateObjectIdFromKey(pathKey); Integer objectId = new LwM2mPath(convertToObjectIdFromIdVer(pathIdVer)).getObjectId();
String objectVer = validateObjectVerFromKey(pathKey); String objectVer = validateObjectVerFromKey(pathIdVer);
return resourceModel != null && (isWritable ? return resourceModel != null && (isWritableNotOptional ?
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() :
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
} }

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -25,13 +25,16 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServiceImpl; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServiceImpl;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -54,6 +57,7 @@ public class LwM2mClient implements Cloneable {
private final Map<String, ResourceValue> resources; private final Map<String, ResourceValue> resources;
private final Map<String, TransportProtos.TsKvProto> delayedRequests; private final Map<String, TransportProtos.TsKvProto> delayedRequests;
private final List<String> pendingRequests; private final List<String> pendingRequests;
private final Queue<LwM2mQueuedRequest> queuedRequests;
private boolean init; private boolean init;
public Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
@ -71,6 +75,7 @@ public class LwM2mClient implements Cloneable {
this.profileId = profileId; this.profileId = profileId;
this.sessionId = sessionId; this.sessionId = sessionId;
this.init = false; this.init = false;
this.queuedRequests = new ConcurrentLinkedQueue<>();
} }
public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) { public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) {

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
public interface LwM2mClientContext { public interface LwM2mClientContext {
@ -51,4 +52,6 @@ public interface LwM2mClientContext {
Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles); Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles);
boolean addUpdateProfileParameters(DeviceProfile deviceProfile); boolean addUpdateProfileParameters(DeviceProfile deviceProfile);
Set<String> getSupportedIdVerInClient(Registration registration);
} }

23
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.client; package org.thingsboard.server.transport.lwm2m.server.client;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -27,11 +28,14 @@ import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler;
import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
@Service @Service
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
@ -90,7 +94,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override @Override
public LwM2mClient updateInSessionsLwM2MClient(Registration registration) { public LwM2mClient updateInSessionsLwM2MClient(Registration registration) {
if (this.lwM2mClients.get(registration.getEndpoint()) == null) { if (this.lwM2mClients.get(registration.getEndpoint()) == null) {
addLwM2mClientToSession(registration.getEndpoint()); this.addLwM2mClientToSession(registration.getEndpoint());
} }
LwM2mClient lwM2MClient = lwM2mClients.get(registration.getEndpoint()); LwM2mClient lwM2MClient = lwM2mClients.get(registration.getEndpoint());
lwM2MClient.setRegistration(registration); lwM2MClient.setRegistration(registration);
@ -169,4 +173,21 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
return false; return false;
} }
/**
* if isVer - ok or default ver=DEFAULT_LWM2M_VERSION
* @param registration -
* @return - all objectIdVer in client
*/
@Override
public Set<String> getSupportedIdVerInClient(Registration registration) {
Set<String> clientObjects = ConcurrentHashMap.newKeySet();
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
LwM2mPath pathIds = new LwM2mPath(url.getUrl());
if (!pathIds.isRoot()) {
clientObjects.add(convertToIdVerFromObjectId(url.getUrl(), registration));
}
});
return (clientObjects.size() > 0) ? clientObjects : null;
}
} }

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java

@ -56,6 +56,13 @@ public class LwM2mClientProfile {
*/ */
private JsonArray postObserveProfile; private JsonArray postObserveProfile;
/**
* "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"},
* "/3_1.0/0": {"gt": 17},
* "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
*/
private JsonObject postAttributeLwm2mProfile;
public LwM2mClientProfile clone() { public LwM2mClientProfile clone() {
LwM2mClientProfile lwM2mClientProfile = new LwM2mClientProfile(); LwM2mClientProfile lwM2mClientProfile = new LwM2mClientProfile();
lwM2mClientProfile.postClientLwM2mSettings = this.deepCopy(this.postClientLwM2mSettings, JsonObject.class); lwM2mClientProfile.postClientLwM2mSettings = this.deepCopy(this.postClientLwM2mSettings, JsonObject.class);
@ -63,6 +70,7 @@ public class LwM2mClientProfile {
lwM2mClientProfile.postAttributeProfile = this.deepCopy(this.postAttributeProfile, JsonArray.class); lwM2mClientProfile.postAttributeProfile = this.deepCopy(this.postAttributeProfile, JsonArray.class);
lwM2mClientProfile.postTelemetryProfile = this.deepCopy(this.postTelemetryProfile, JsonArray.class); lwM2mClientProfile.postTelemetryProfile = this.deepCopy(this.postTelemetryProfile, JsonArray.class);
lwM2mClientProfile.postObserveProfile = this.deepCopy(this.postObserveProfile, JsonArray.class); lwM2mClientProfile.postObserveProfile = this.deepCopy(this.postObserveProfile, JsonArray.class);
lwM2mClientProfile.postAttributeLwm2mProfile = this.deepCopy(this.postAttributeLwm2mProfile, JsonObject.class);
return lwM2mClientProfile; return lwM2mClientProfile;
} }

4
dao/pom.xml

@ -227,10 +227,6 @@
<groupId>org.elasticsearch.client</groupId> <groupId>org.elasticsearch.client</groupId>
<artifactId>rest</artifactId> <artifactId>rest</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>

7
dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java

@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -30,7 +31,6 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -111,6 +111,11 @@ public class BaseEventService implements EventService {
return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit); return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit);
} }
@Override
public PageData<Event> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) {
return eventDao.findEventByFilter(tenantId.getId(), entityId, eventFilter, pageLink);
}
@Override @Override
public void removeEvents(TenantId tenantId, EntityId entityId) { public void removeEvents(TenantId tenantId, EntityId entityId) {
PageData<Event> eventPageData; PageData<Event> eventPageData;

3
dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.event;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -88,6 +89,8 @@ public interface EventDao extends Dao<Event> {
*/ */
PageData<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink); PageData<Event> findEvents(UUID tenantId, EntityId entityId, String eventType, TimePageLink pageLink);
PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
/** /**
* Find latest events by tenantId, entityId and eventType. * Find latest events by tenantId, entityId and eventType.
* *

122
dao/src/main/java/org/thingsboard/server/dao/resource/BaseTbResourceService.java → dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -17,10 +17,6 @@ package org.thingsboard.server.dao.resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
@ -29,9 +25,6 @@ import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mInstance;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -41,63 +34,30 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID; import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@Service @Service
@Slf4j @Slf4j
public class BaseTbResourceService implements TbResourceService { public class BaseResourceService implements ResourceService {
public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId "; public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId ";
private final TbResourceDao resourceDao; private final TbResourceDao resourceDao;
private final TbResourceInfoDao resourceInfoDao; private final TbResourceInfoDao resourceInfoDao;
private final TenantDao tenantDao; private final TenantDao tenantDao;
private final DDFFileParser ddfFileParser;
public BaseTbResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) {
public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) {
this.resourceDao = resourceDao; this.resourceDao = resourceDao;
this.resourceInfoDao = resourceInfoDao; this.resourceInfoDao = resourceInfoDao;
this.tenantDao = tenantDao; this.tenantDao = tenantDao;
this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
} }
@Override @Override
public TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException { public TbResource saveResource(TbResource resource) {
log.trace("Executing saveResource [{}]", resource);
if (StringUtils.isEmpty(resource.getData())) {
throw new DataValidationException("Resource data should be specified!");
}
if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
List<ObjectModel> objectModels =
ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
if (!objectModels.isEmpty()) {
ObjectModel objectModel = objectModels.get(0);
String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion();
String name = objectModel.name;
resource.setResourceKey(resourceKey);
if (resource.getId() == null) {
resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion());
}
resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name);
} else {
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
}
} else {
resource.setResourceKey(resource.getFileName());
}
resourceValidator.validate(resource, TbResourceInfo::getTenantId); resourceValidator.validate(resource, TbResourceInfo::getTenantId);
try { try {
@ -111,7 +71,6 @@ public class BaseTbResourceService implements TbResourceService {
throw t; throw t;
} }
} }
} }
@Override @Override
@ -156,31 +115,17 @@ public class BaseTbResourceService implements TbResourceService {
} }
@Override @Override
public List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) { public List<TbResource> findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType resourceType, String[] objectIds) {
log.trace("Executing findByTenantId [{}]", tenantId); log.trace("Executing findTenantResourcesByResourceTypeAndObjectIds [{}][{}][{}]", tenantId, resourceType, objectIds);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
PageData<TbResource> resourcePageData = resourceDao.findResourcesByTenantIdAndResourceType( return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, objectIds, null);
tenantId,
ResourceType.LWM2M_MODEL, pageLink);
return resourcePageData.getData().stream()
.map(this::toLwM2mObject)
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
} }
@Override @Override
public List<LwM2mObject> findLwM2mObject(TenantId tenantId, String sortOrder, public PageData<TbResource> findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType resourceType, PageLink pageLink) {
String sortProperty, log.trace("Executing findTenantResourcesByResourceTypeAndPageLink [{}][{}][{}]", tenantId, resourceType, pageLink);
String[] objectIds) {
log.trace("Executing findByTenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
List<TbResource> resources = resourceDao.findResourcesByTenantIdAndResourceType(tenantId, ResourceType.LWM2M_MODEL, return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, pageLink);
objectIds,
null);
return resources.stream()
.map(this::toLwM2mObject)
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
} }
@Override @Override
@ -190,50 +135,6 @@ public class BaseTbResourceService implements TbResourceService {
tenantResourcesRemover.removeEntities(tenantId, tenantId); tenantResourcesRemover.removeEntities(tenantId, tenantId);
} }
private LwM2mObject toLwM2mObject(TbResource resource) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
List<ObjectModel> objectModels =
ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
if (objectModels.size() == 0) {
return null;
} else {
ObjectModel obj = objectModels.get(0);
LwM2mObject lwM2mObject = new LwM2mObject();
lwM2mObject.setId(obj.id);
lwM2mObject.setKeyId(resource.getResourceKey());
lwM2mObject.setName(obj.name);
lwM2mObject.setMultiple(obj.multiple);
lwM2mObject.setMandatory(obj.mandatory);
LwM2mInstance instance = new LwM2mInstance();
instance.setId(0);
List<LwM2mResourceObserve> resources = new ArrayList<>();
obj.resources.forEach((k, v) -> {
if (!v.operations.isExecutable()) {
LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false);
resources.add(lwM2MResourceObserve);
}
});
instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
lwM2mObject.setInstances(new LwM2mInstance[]{instance});
return lwM2mObject;
}
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e);
return null;
}
}
private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
Comparator<LwM2mObject> comparator;
if ("name".equals(sortProperty)) {
comparator = Comparator.comparing(LwM2mObject::getName);
} else {
comparator = Comparator.comparingLong(LwM2mObject::getId);
}
return "DESC".equals(sortOrder) ? comparator.reversed() : comparator;
}
private DataValidator<TbResource> resourceValidator = new DataValidator<>() { private DataValidator<TbResource> resourceValidator = new DataValidator<>() {
@Override @Override
@ -259,9 +160,6 @@ public class BaseTbResourceService implements TbResourceService {
throw new DataValidationException("Resource is referencing to non-existent tenant!"); throw new DataValidationException("Resource is referencing to non-existent tenant!");
} }
} }
if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource) == null) {
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
}
} }
}; };

2
dao/src/main/java/org/thingsboard/server/dao/sql/component/AbstractComponentDescriptorInsertRepository.java

@ -51,11 +51,11 @@ public abstract class AbstractComponentDescriptorInsertRepository implements Com
TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
try { try {
componentDescriptorEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict); componentDescriptorEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict);
transactionManager.commit(transaction);
} catch (Throwable th) { } catch (Throwable th) {
log.trace("Could not execute the update statement for Component Descriptor with id {}, name {} and entityType {}", entity.getUuid(), entity.getName(), entity.getType()); log.trace("Could not execute the update statement for Component Descriptor with id {}, name {} and entityType {}", entity.getUuid(), entity.getName(), entity.getType());
transactionManager.rollback(transaction); transactionManager.rollback(transaction);
} }
transactionManager.commit(transaction);
} else { } else {
log.trace("Could not execute the insert statement for Component Descriptor with id {}, name {} and entityType {}", entity.getUuid(), entity.getName(), entity.getType()); log.trace("Could not execute the insert statement for Component Descriptor with id {}, name {} and entityType {}", entity.getUuid(), entity.getName(), entity.getType());
} }

2
dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java

@ -51,11 +51,11 @@ public abstract class AbstractEventInsertRepository implements EventInsertReposi
TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW); TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
try { try {
eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict); eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict);
transactionManager.commit(transaction);
} catch (Throwable th) { } catch (Throwable th) {
log.trace("Could not execute the update statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); log.trace("Could not execute the update statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType());
transactionManager.rollback(transaction); transactionManager.rollback(transaction);
} }
transactionManager.commit(transaction);
} else { } else {
log.trace("Could not execute the insert statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); log.trace("Could not execute the insert statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType());
} }

171
dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java

@ -44,11 +44,11 @@ public interface EventRepository extends PagingAndSortingRepository<EventEntity,
@Query("SELECT e FROM EventEntity e WHERE e.tenantId = :tenantId AND e.entityType = :entityType " + @Query("SELECT e FROM EventEntity e WHERE e.tenantId = :tenantId AND e.entityType = :entityType " +
"AND e.entityId = :entityId AND e.eventType = :eventType ORDER BY e.eventType DESC, e.id DESC") "AND e.entityId = :entityId AND e.eventType = :eventType ORDER BY e.eventType DESC, e.id DESC")
List<EventEntity> findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType( List<EventEntity> findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType(
@Param("tenantId") UUID tenantId, @Param("tenantId") UUID tenantId,
@Param("entityType") EntityType entityType, @Param("entityType") EntityType entityType,
@Param("entityId") UUID entityId, @Param("entityId") UUID entityId,
@Param("eventType") String eventType, @Param("eventType") String eventType,
Pageable pageable); Pageable pageable);
@Query("SELECT e FROM EventEntity e WHERE " + @Query("SELECT e FROM EventEntity e WHERE " +
"e.tenantId = :tenantId " + "e.tenantId = :tenantId " +
@ -80,4 +80,165 @@ public interface EventRepository extends PagingAndSortingRepository<EventEntity,
@Param("endTime") Long endTime, @Param("endTime") Long endTime,
Pageable pageable); Pageable pageable);
@Query(nativeQuery = true,
value = "SELECT e.id, e.created_time, e.body, e.entity_id, e.entity_type, e.event_type, e.event_uid, e.tenant_id, ts FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = :eventType " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:type IS NULL OR lower(json_body->>'type') LIKE concat('%', lower(:type\\:\\:varchar), '%')) " +
"AND (:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:entityName IS NULL OR lower(json_body->>'entityName') LIKE concat('%', lower(:entityName\\:\\:varchar), '%')) " +
"AND (:relationType IS NULL OR lower(json_body->>'relationType') LIKE concat('%', lower(:relationType\\:\\:varchar), '%')) " +
"AND (:bodyEntityId IS NULL OR lower(json_body->>'entityId') LIKE concat('%', lower(:bodyEntityId\\:\\:varchar), '%')) " +
"AND (:msgType IS NULL OR lower(json_body->>'msgType') LIKE concat('%', lower(:msgType\\:\\:varchar), '%')) " +
"AND ((:isError = FALSE) OR (json_body->>'error') IS NOT NULL) " +
"AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%')) " +
"AND (:data IS NULL OR lower(json_body->>'data') LIKE concat('%', lower(:data\\:\\:varchar), '%')) " +
"AND (:metadata IS NULL OR lower(json_body->>'metadata') LIKE concat('%', lower(:metadata\\:\\:varchar), '%')) ",
countQuery = "SELECT count(*) FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = :eventType " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:type IS NULL OR lower(json_body->>'type') LIKE concat('%', lower(:type\\:\\:varchar), '%')) " +
"AND (:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:entityName IS NULL OR lower(json_body->>'entityName') LIKE concat('%', lower(:entityName\\:\\:varchar), '%')) " +
"AND (:relationType IS NULL OR lower(json_body->>'relationType') LIKE concat('%', lower(:relationType\\:\\:varchar), '%')) " +
"AND (:bodyEntityId IS NULL OR lower(json_body->>'entityId') LIKE concat('%', lower(:bodyEntityId\\:\\:varchar), '%')) " +
"AND (:msgType IS NULL OR lower(json_body->>'msgType') LIKE concat('%', lower(:msgType\\:\\:varchar), '%')) " +
"AND ((:isError = FALSE) OR (json_body->>'error') IS NOT NULL) " +
"AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%')) " +
"AND (:data IS NULL OR lower(json_body->>'data') LIKE concat('%', lower(:data\\:\\:varchar), '%')) " +
"AND (:metadata IS NULL OR lower(json_body->>'metadata') LIKE concat('%', lower(:metadata\\:\\:varchar), '%'))"
)
Page<EventEntity> findDebugRuleNodeEvents(@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId,
@Param("entityType") String entityType,
@Param("eventType") String eventType,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("type") String type,
@Param("server") String server,
@Param("entityName") String entityName,
@Param("relationType") String relationType,
@Param("bodyEntityId") String bodyEntityId,
@Param("msgType") String msgType,
@Param("isError") boolean isError,
@Param("error") String error,
@Param("data") String data,
@Param("metadata") String metadata,
Pageable pageable);
@Query(nativeQuery = true,
value = "SELECT e.id, e.created_time, e.body, e.entity_id, e.entity_type, e.event_type, e.event_uid, e.tenant_id, ts FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = 'ERROR' " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:method IS NULL OR lower(json_body->>'method') LIKE concat('%', lower(:method\\:\\:varchar), '%')) " +
"AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))",
countQuery = "SELECT count(*) FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = 'ERROR' " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:method IS NULL OR lower(json_body->>'method') LIKE concat('%', lower(:method\\:\\:varchar), '%')) " +
"AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))")
Page<EventEntity> findErrorEvents(@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId,
@Param("entityType") String entityType,
@Param("startTime") Long startTime,
@Param("endTime") Long endTIme,
@Param("server") String server,
@Param("method") String method,
@Param("error") String error,
Pageable pageable);
@Query(nativeQuery = true,
value = "SELECT e.id, e.created_time, e.body, e.entity_id, e.entity_type, e.event_type, e.event_uid, e.tenant_id, ts FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = 'LC_EVENT' " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:event IS NULL OR lower(json_body->>'event') LIKE concat('%', lower(:event\\:\\:varchar), '%')) " +
"AND ((:statusFilterEnabled = FALSE) OR lower(json_body->>'success')\\:\\:boolean = :statusFilter) " +
"AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))"
,
countQuery = "SELECT count(*) FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = 'LC_EVENT' " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:server IS NULL OR lower(json_body->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:event IS NULL OR lower(json_body->>'event') LIKE concat('%', lower(:event\\:\\:varchar), '%')) " +
"AND ((:statusFilterEnabled = FALSE) OR lower(json_body->>'success')\\:\\:boolean = :statusFilter) " +
"AND (:error IS NULL OR lower(json_body->>'error') LIKE concat('%', lower(:error\\:\\:varchar), '%'))"
)
Page<EventEntity> findLifeCycleEvents(@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId,
@Param("entityType") String entityType,
@Param("startTime") Long startTime,
@Param("endTime") Long endTIme,
@Param("server") String server,
@Param("event") String event,
@Param("statusFilterEnabled") boolean statusFilterEnabled,
@Param("statusFilter") boolean statusFilter,
@Param("error") String error,
Pageable pageable);
@Query(nativeQuery = true,
value = "SELECT e.id, e.created_time, e.body, e.entity_id, e.entity_type, e.event_type, e.event_uid, e.tenant_id, ts FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = 'STATS' " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:server IS NULL OR lower(e.body\\:\\:json->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:messagesProcessed = 0 OR (json_body->>'messagesProcessed')\\:\\:integer >= :messagesProcessed) " +
"AND (:errorsOccurred = 0 OR (json_body->>'errorsOccurred')\\:\\:integer >= :errorsOccurred) ",
countQuery = "SELECT count(*) FROM " +
"(SELECT *, e.body\\:\\:jsonb as json_body FROM event e WHERE " +
"e.tenant_id = :tenantId " +
"AND e.entity_type = :entityType " +
"AND e.entity_id = :entityId " +
"AND e.event_type = 'LC_EVENT' " +
"AND e.created_time >= :startTime AND (:endTime = 0 OR e.created_time <= :endTime) " +
") AS e WHERE " +
"(:server IS NULL OR lower(e.body\\:\\:json->>'server') LIKE concat('%', lower(:server\\:\\:varchar), '%')) " +
"AND (:messagesProcessed = 0 OR (json_body->>'messagesProcessed')\\:\\:integer >= :messagesProcessed) " +
"AND (:errorsOccurred = 0 OR (json_body->>'errorsOccurred')\\:\\:integer >= :errorsOccurred) ")
Page<EventEntity> findStatisticsEvents(@Param("tenantId") UUID tenantId,
@Param("entityId") UUID entityId,
@Param("entityType") String entityType,
@Param("startTime") Long startTime,
@Param("endTime") Long endTIme,
@Param("server") String server,
@Param("messagesProcessed") Integer messagesProcessed,
@Param("errorsOccurred") Integer errorsOccurred,
Pageable pageable);
} }

106
dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java

@ -24,6 +24,12 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.DebugEvent;
import org.thingsboard.server.common.data.event.ErrorEventFilter;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.event.LifeCycleEventFilter;
import org.thingsboard.server.common.data.event.StatisticsEventFilter;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EventId; import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -147,6 +153,98 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen
DaoUtil.toPageable(pageLink))); DaoUtil.toPageable(pageLink)));
} }
@Override
public PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink) {
if (eventFilter.hasFilterForJsonBody()) {
switch (eventFilter.getEventType()) {
case DEBUG_RULE_NODE:
case DEBUG_RULE_CHAIN:
return findEventByFilter(tenantId, entityId, (DebugEvent) eventFilter, pageLink);
case LC_EVENT:
return findEventByFilter(tenantId, entityId, (LifeCycleEventFilter) eventFilter, pageLink);
case ERROR:
return findEventByFilter(tenantId, entityId, (ErrorEventFilter) eventFilter, pageLink);
case STATS:
return findEventByFilter(tenantId, entityId, (StatisticsEventFilter) eventFilter, pageLink);
default:
throw new RuntimeException("Not supported event type: " + eventFilter.getEventType());
}
} else {
return findEvents(tenantId, entityId, eventFilter.getEventType().name(), pageLink);
}
}
private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, DebugEvent eventFilter, TimePageLink pageLink) {
return DaoUtil.toPageData(
eventRepository.findDebugRuleNodeEvents(
tenantId,
entityId.getId(),
entityId.getEntityType().name(),
eventFilter.getEventType().name(),
notNull(pageLink.getStartTime()),
notNull(pageLink.getEndTime()),
eventFilter.getMsgDirectionType(),
eventFilter.getServer(),
eventFilter.getEntityName(),
eventFilter.getRelationType(),
eventFilter.getEntityId(),
eventFilter.getMsgType(),
eventFilter.isError(),
eventFilter.getError(),
eventFilter.getDataSearch(),
eventFilter.getMetadataSearch(),
DaoUtil.toPageable(pageLink)));
}
private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, ErrorEventFilter eventFilter, TimePageLink pageLink) {
return DaoUtil.toPageData(
eventRepository.findErrorEvents(
tenantId,
entityId.getId(),
entityId.getEntityType().name(),
notNull(pageLink.getStartTime()),
notNull(pageLink.getEndTime()),
eventFilter.getServer(),
eventFilter.getMethod(),
eventFilter.getError(),
DaoUtil.toPageable(pageLink))
);
}
private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, LifeCycleEventFilter eventFilter, TimePageLink pageLink) {
boolean statusFilterEnabled = !StringUtils.isEmpty(eventFilter.getStatus());
boolean statusFilter = statusFilterEnabled && eventFilter.getStatus().equalsIgnoreCase("Success");
return DaoUtil.toPageData(
eventRepository.findLifeCycleEvents(
tenantId,
entityId.getId(),
entityId.getEntityType().name(),
notNull(pageLink.getStartTime()),
notNull(pageLink.getEndTime()),
eventFilter.getServer(),
eventFilter.getEvent(),
statusFilterEnabled,
statusFilter,
eventFilter.getError(),
DaoUtil.toPageable(pageLink))
);
}
private PageData<Event> findEventByFilter(UUID tenantId, EntityId entityId, StatisticsEventFilter eventFilter, TimePageLink pageLink) {
return DaoUtil.toPageData(
eventRepository.findStatisticsEvents(
tenantId,
entityId.getId(),
entityId.getEntityType().name(),
notNull(pageLink.getStartTime()),
notNull(pageLink.getEndTime()),
eventFilter.getServer(),
notNull(eventFilter.getMessagesProcessed()),
notNull(eventFilter.getErrorsOccurred()),
DaoUtil.toPageable(pageLink))
);
}
@Override @Override
public List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) { public List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit) {
List<EventEntity> latest = eventRepository.findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType( List<EventEntity> latest = eventRepository.findLatestByTenantIdAndEntityTypeAndEntityIdAndEventType(
@ -177,4 +275,12 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen
return Optional.of(DaoUtil.getData(eventInsertRepository.saveOrUpdate(entity))); return Optional.of(DaoUtil.getData(eventInsertRepository.saveOrUpdate(entity)));
} }
private long notNull(Long value) {
return value != null ? value : 0;
}
private int notNull(Integer value) {
return value != null ? value : 0;
}
} }

4
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -35,7 +35,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.TbResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
@ -90,7 +90,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
private RuleChainService ruleChainService; private RuleChainService ruleChainService;
@Autowired @Autowired
private TbResourceService resourceService; private ResourceService resourceService;
@Override @Override
public Tenant findTenantById(TenantId tenantId) { public Tenant findTenantById(TenantId tenantId) {

151
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java

@ -62,6 +62,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal;
@ -107,6 +109,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
private PreparedStatement[] fetchStmtsDesc; private PreparedStatement[] fetchStmtsDesc;
private PreparedStatement deleteStmt; private PreparedStatement deleteStmt;
private PreparedStatement deletePartitionStmt; private PreparedStatement deletePartitionStmt;
private final Lock stmtCreationLock = new ReentrantLock();
private boolean isInstall() { private boolean isInstall() {
return environment.acceptsProfiles(Profiles.of("install")); return environment.acceptsProfiles(Profiles.of("install"));
@ -545,13 +548,20 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
private PreparedStatement getDeleteStmt() { private PreparedStatement getDeleteStmt() {
if (deleteStmt == null) { if (deleteStmt == null) {
deleteStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_CF + stmtCreationLock.lock();
" WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM try {
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM if (deleteStmt == null) {
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM deleteStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_CF +
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " >= ? " + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " < ?"); + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " >= ? "
+ "AND " + ModelConstants.TS_COLUMN + " < ?");
}
} finally {
stmtCreationLock.unlock();
}
} }
return deleteStmt; return deleteStmt;
} }
@ -585,27 +595,41 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
private PreparedStatement getDeletePartitionStmt() { private PreparedStatement getDeletePartitionStmt() {
if (deletePartitionStmt == null) { if (deletePartitionStmt == null) {
deletePartitionStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_PARTITIONS_CF + stmtCreationLock.lock();
" WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM try {
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM if (deletePartitionStmt == null) {
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM deletePartitionStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_PARTITIONS_CF +
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM); " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM);
}
} finally {
stmtCreationLock.unlock();
}
} }
return deletePartitionStmt; return deletePartitionStmt;
} }
private PreparedStatement getSaveStmt(DataType dataType) { private PreparedStatement getSaveStmt(DataType dataType) {
if (saveStmts == null) { if (saveStmts == null) {
saveStmts = new PreparedStatement[DataType.values().length]; stmtCreationLock.lock();
for (DataType type : DataType.values()) { try {
saveStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + if (saveStmts == null) {
"(" + ModelConstants.ENTITY_TYPE_COLUMN + saveStmts = new PreparedStatement[DataType.values().length];
"," + ModelConstants.ENTITY_ID_COLUMN + for (DataType type : DataType.values()) {
"," + ModelConstants.KEY_COLUMN + saveStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF +
"," + ModelConstants.PARTITION_COLUMN + "(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.TS_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN +
"," + getColumnName(type) + ")" + "," + ModelConstants.KEY_COLUMN +
" VALUES(?, ?, ?, ?, ?, ?)"); "," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.TS_COLUMN +
"," + getColumnName(type) + ")" +
" VALUES(?, ?, ?, ?, ?, ?)");
}
}
} finally {
stmtCreationLock.unlock();
} }
} }
return saveStmts[dataType.ordinal()]; return saveStmts[dataType.ordinal()];
@ -613,16 +637,23 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
private PreparedStatement getSaveTtlStmt(DataType dataType) { private PreparedStatement getSaveTtlStmt(DataType dataType) {
if (saveTtlStmts == null) { if (saveTtlStmts == null) {
saveTtlStmts = new PreparedStatement[DataType.values().length]; stmtCreationLock.lock();
for (DataType type : DataType.values()) { try {
saveTtlStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + if (saveTtlStmts == null) {
"(" + ModelConstants.ENTITY_TYPE_COLUMN + saveTtlStmts = new PreparedStatement[DataType.values().length];
"," + ModelConstants.ENTITY_ID_COLUMN + for (DataType type : DataType.values()) {
"," + ModelConstants.KEY_COLUMN + saveTtlStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF +
"," + ModelConstants.PARTITION_COLUMN + "(" + ModelConstants.ENTITY_TYPE_COLUMN +
"," + ModelConstants.TS_COLUMN + "," + ModelConstants.ENTITY_ID_COLUMN +
"," + getColumnName(type) + ")" + "," + ModelConstants.KEY_COLUMN +
" VALUES(?, ?, ?, ?, ?, ?) USING TTL ?"); "," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.TS_COLUMN +
"," + getColumnName(type) + ")" +
" VALUES(?, ?, ?, ?, ?, ?) USING TTL ?");
}
}
} finally {
stmtCreationLock.unlock();
} }
} }
return saveTtlStmts[dataType.ordinal()]; return saveTtlStmts[dataType.ordinal()];
@ -630,24 +661,38 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
private PreparedStatement getPartitionInsertStmt() { private PreparedStatement getPartitionInsertStmt() {
if (partitionInsertStmt == null) { if (partitionInsertStmt == null) {
partitionInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF + stmtCreationLock.lock();
"(" + ModelConstants.ENTITY_TYPE_COLUMN + try {
"," + ModelConstants.ENTITY_ID_COLUMN + if (partitionInsertStmt == null) {
"," + ModelConstants.PARTITION_COLUMN + partitionInsertStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF +
"," + ModelConstants.KEY_COLUMN + ")" + "(" + ModelConstants.ENTITY_TYPE_COLUMN +
" VALUES(?, ?, ?, ?)"); "," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.KEY_COLUMN + ")" +
" VALUES(?, ?, ?, ?)");
}
} finally {
stmtCreationLock.unlock();
}
} }
return partitionInsertStmt; return partitionInsertStmt;
} }
private PreparedStatement getPartitionInsertTtlStmt() { private PreparedStatement getPartitionInsertTtlStmt() {
if (partitionInsertTtlStmt == null) { if (partitionInsertTtlStmt == null) {
partitionInsertTtlStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF + stmtCreationLock.lock();
"(" + ModelConstants.ENTITY_TYPE_COLUMN + try {
"," + ModelConstants.ENTITY_ID_COLUMN + if (partitionInsertTtlStmt == null) {
"," + ModelConstants.PARTITION_COLUMN + partitionInsertTtlStmt = prepare(INSERT_INTO + ModelConstants.TS_KV_PARTITIONS_CF +
"," + ModelConstants.KEY_COLUMN + ")" + "(" + ModelConstants.ENTITY_TYPE_COLUMN +
" VALUES(?, ?, ?, ?) USING TTL ?"); "," + ModelConstants.ENTITY_ID_COLUMN +
"," + ModelConstants.PARTITION_COLUMN +
"," + ModelConstants.KEY_COLUMN + ")" +
" VALUES(?, ?, ?, ?) USING TTL ?");
}
} finally {
stmtCreationLock.unlock();
}
} }
return partitionInsertTtlStmt; return partitionInsertTtlStmt;
} }
@ -713,12 +758,26 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD
switch (orderBy) { switch (orderBy) {
case ASC_ORDER: case ASC_ORDER:
if (fetchStmtsAsc == null) { if (fetchStmtsAsc == null) {
fetchStmtsAsc = initFetchStmt(orderBy); stmtCreationLock.lock();
try {
if (fetchStmtsAsc == null) {
fetchStmtsAsc = initFetchStmt(orderBy);
}
} finally {
stmtCreationLock.unlock();
}
} }
return fetchStmtsAsc[aggType.ordinal()]; return fetchStmtsAsc[aggType.ordinal()];
case DESC_ORDER: case DESC_ORDER:
if (fetchStmtsDesc == null) { if (fetchStmtsDesc == null) {
fetchStmtsDesc = initFetchStmt(orderBy); stmtCreationLock.lock();
try {
if (fetchStmtsDesc == null) {
fetchStmtsDesc = initFetchStmt(orderBy);
}
} finally {
stmtCreationLock.unlock();
}
} }
return fetchStmtsDesc[aggType.ordinal()]; return fetchStmtsDesc[aggType.ordinal()];
default: default:

6
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -54,7 +54,7 @@ import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -152,9 +152,9 @@ public abstract class AbstractServiceTest {
protected DeviceProfileService deviceProfileService; protected DeviceProfileService deviceProfileService;
@Autowired @Autowired
protected TbResourceService resourceService; protected ResourceService resourceService;
class IdComparator<D extends HasId> implements Comparator<D> { public class IdComparator<D extends HasId> implements Comparator<D> {
@Override @Override
public int compare(D o1, D o2) { public int compare(D o1, D o2) {
return o1.getId().getId().compareTo(o2.getId().getId()); return o1.getId().getId().compareTo(o2.getId().getId());

2
pom.xml

@ -99,7 +99,7 @@
org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
</sonar.exclusions> </sonar.exclusions>
<elasticsearch.version>5.0.2</elasticsearch.version> <elasticsearch.version>5.0.2</elasticsearch.version>
<delight-nashorn-sandbox.version>0.1.31</delight-nashorn-sandbox.version> <delight-nashorn-sandbox.version>0.1.16</delight-nashorn-sandbox.version>
<kafka.version>2.6.0</kafka.version> <kafka.version>2.6.0</kafka.version>
<bucket4j.version>4.1.1</bucket4j.version> <bucket4j.version>4.1.1</bucket4j.version>
<fst.version>2.57</fst.version> <fst.version>2.57</fst.version>

2
rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

@ -1338,7 +1338,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
HttpEntity.EMPTY, DeviceProfile.class, deviceProfileId).getBody(); HttpEntity.EMPTY, DeviceProfile.class, deviceProfileId).getBody();
} }
public PageData<DeviceProfile> getTenantDevices(PageLink pageLink) { public PageData<DeviceProfile> getDeviceProfiles(PageLink pageLink) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink); addPageLinkToParam(params, pageLink);
return restTemplate.exchange( return restTemplate.exchange(

8
ui-ngx/src/app/core/http/event.service.ts

@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http';
import { TimePageLink } from '@shared/models/page/page-link'; import { TimePageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { DebugEventType, Event, EventType } from '@shared/models/event.models'; import { DebugEventType, Event, EventType, FilterEventBody } from '@shared/models/event.models';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -39,4 +39,10 @@ export class EventService {
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));
} }
public getFilterEvents(entityId: EntityId, eventType: EventType | DebugEventType, tenantId: string,
filters: FilterEventBody, pageLink: TimePageLink, config?: RequestConfig): Observable<PageData<Event>> {
return this.http.post<PageData<Event>>(`/api/events/${entityId.entityType}/${entityId.id}` +
`${pageLink.toQuery()}&tenantId=${tenantId}`, {...filters, eventType}, defaultHttpOptionsFromConfig(config));
}
} }

30
ui-ngx/src/app/core/http/resource.service.ts

@ -18,10 +18,10 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { Resource, ResourceInfo } from '@shared/models/resource.models'; import { Resource, ResourceInfo } from '@shared/models/resource.models';
import { map } from 'rxjs/operators'; import { catchError, map, mergeMap } from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -43,14 +43,17 @@ export class ResourceService {
} }
public downloadResource(resourceId: string): Observable<any> { public downloadResource(resourceId: string): Observable<any> {
return this.http.get(`/api/resource/${resourceId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( return this.http.get(`/api/resource/${resourceId}/download`, {
responseType: 'arraybuffer',
observe: 'response'
}).pipe(
map((response) => { map((response) => {
const headers = response.headers; const headers = response.headers;
const filename = headers.get('x-filename'); const filename = headers.get('x-filename');
const contentType = headers.get('content-type'); const contentType = headers.get('content-type');
const linkElement = document.createElement('a'); const linkElement = document.createElement('a');
try { try {
const blob = new Blob([response.body], { type: contentType }); const blob = new Blob([response.body], {type: contentType});
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
linkElement.setAttribute('href', url); linkElement.setAttribute('href', url);
linkElement.setAttribute('download', filename); linkElement.setAttribute('download', filename);
@ -70,6 +73,25 @@ export class ResourceService {
); );
} }
public saveResources(resources: Resource[], config?: RequestConfig): Observable<Resource[]> {
let partSize = 100;
partSize = resources.length > partSize ? partSize : resources.length;
const resourceObservables: Observable<Resource>[] = [];
for (let i = 0; i < partSize; i++) {
resourceObservables.push(this.saveResource(resources[i], config).pipe(catchError(() => of({} as Resource))));
}
return forkJoin(resourceObservables).pipe(
mergeMap((resource) => {
resources.splice(0, partSize);
if (resources.length) {
return this.saveResources(resources, config);
} else {
return of(resource);
}
})
);
}
public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> { public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> {
return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config)); return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config));
} }

20
ui-ngx/src/app/core/services/time.service.ts

@ -31,7 +31,7 @@ import { isDefined } from '@core/utils';
export interface TimeInterval { export interface TimeInterval {
name: string; name: string;
translateParams: {[key: string]: any}; translateParams: { [key: string]: any };
value: number; value: number;
} }
@ -56,14 +56,14 @@ export class TimeService {
public loadMaxDatapointsLimit(): Observable<number> { public loadMaxDatapointsLimit(): Observable<number> {
return this.http.get<number>('/api/dashboard/maxDatapointsLimit', return this.http.get<number>('/api/dashboard/maxDatapointsLimit',
defaultHttpOptions(true)).pipe( defaultHttpOptions(true)).pipe(
map( (limit) => { map((limit) => {
this.maxDatapointsLimit = limit; this.maxDatapointsLimit = limit;
if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) { if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) {
this.maxDatapointsLimit = MIN_LIMIT + 1; this.maxDatapointsLimit = MIN_LIMIT + 1;
} }
return this.maxDatapointsLimit; return this.maxDatapointsLimit;
}) })
); );
} }
public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean { public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean {
@ -79,7 +79,7 @@ export class TimeService {
public boundMinInterval(min: number): number { public boundMinInterval(min: number): number {
if (isDefined(min)) { if (isDefined(min)) {
min = Math.floor(min / 1000) * 1000; min = Math.ceil(min / 1000) * 1000;
} }
return this.toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL); return this.toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL);
} }

6
ui-ngx/src/app/core/utils.ts

@ -127,6 +127,10 @@ export function isEmpty(obj: any): boolean {
return true; return true;
} }
export function isLiteralObject(value: any) {
return (!!value) && (value.constructor === Object);
}
export function formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined { export function formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined {
if (isDefinedAndNotNull(value) && isNumeric(value) && if (isDefinedAndNotNull(value) && isNumeric(value) &&
(isDefinedAndNotNull(dec) || isDefinedAndNotNull(units) || Number(value).toString() === value)) { (isDefinedAndNotNull(dec) || isDefinedAndNotNull(units) || Number(value).toString() === value)) {
@ -287,7 +291,7 @@ export function deepClone<T>(target: T, ignoreFields?: string[]): T {
return cp.map((n: any) => deepClone<any>(n)) as any; return cp.map((n: any) => deepClone<any>(n)) as any;
} }
if (typeof target === 'object' && target !== {}) { if (typeof target === 'object' && target !== {}) {
const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any }; const cp = {...(target as { [key: string]: any })} as { [key: string]: any };
Object.keys(cp).forEach(k => { Object.keys(cp).forEach(k => {
if (!ignoreFields || ignoreFields.indexOf(k) === -1) { if (!ignoreFields || ignoreFields.indexOf(k) === -1) {
cp[k] = deepClone<any>(cp[k]); cp[k] = deepClone<any>(cp[k]);

2
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -56,6 +56,8 @@
[syncStateWithQueryParam]="syncStateWithQueryParam" [syncStateWithQueryParam]="syncStateWithQueryParam"
[states]="dashboardConfiguration.states"> [states]="dashboardConfiguration.states">
</tb-states-component> </tb-states-component>
<img *ngIf="showDashboardLogo() && isFullscreen" [src]="dashboardLogo"
aria-label="dashboard_logo" class="dashboard_logo"/>
</div> </div>
<div class="tb-dashboard-action-panel" fxFlex="1 0 auto" fxLayout="row-reverse" <div class="tb-dashboard-action-panel" fxFlex="1 0 auto" fxLayout="row-reverse"
fxLayoutAlign.gt-sm="start center" fxLayoutAlign="space-between center" fxLayoutGap="12px"> fxLayoutAlign.gt-sm="start center" fxLayoutAlign="space-between center" fxLayoutGap="12px">

5
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss

@ -54,6 +54,11 @@ div.tb-dashboard-page {
z-index: 13; z-index: 13;
pointer-events: none; pointer-events: none;
.dashboard_logo{
height: 75%;
margin-right: 16px;
}
&.tb-dashboard-toolbar-opened { &.tb-dashboard-toolbar-opened {
right: 0; right: 0;
// transition: right .3s cubic-bezier(.55, 0, .55, .2); // transition: right .3s cubic-bezier(.55, 0, .55, .2);

29
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -90,7 +90,6 @@ import {
} from '@home/components/alias/entity-aliases-dialog.component'; } from '@home/components/alias/entity-aliases-dialog.component';
import { EntityAliases } from '@app/shared/models/alias.models'; import { EntityAliases } from '@app/shared/models/alias.models';
import { EditWidgetComponent } from '@home/components/dashboard-page/edit-widget.component'; import { EditWidgetComponent } from '@home/components/dashboard-page/edit-widget.component';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { import {
AddWidgetDialogComponent, AddWidgetDialogComponent,
AddWidgetDialogData AddWidgetDialogData
@ -118,8 +117,7 @@ import { ComponentPortal } from '@angular/cdk/portal';
import { import {
DISPLAY_WIDGET_TYPES_PANEL_DATA, DISPLAY_WIDGET_TYPES_PANEL_DATA,
DisplayWidgetTypesPanelComponent, DisplayWidgetTypesPanelComponent,
DisplayWidgetTypesPanelData, DisplayWidgetTypesPanelData
WidgetTypes
} from '@home/components/dashboard-page/widget-types-panel.component'; } from '@home/components/dashboard-page/widget-types-panel.component';
import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component'; import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component';
import {AliasEntityType, EntityType} from "@shared/models/entity-type.models"; import {AliasEntityType, EntityType} from "@shared/models/entity-type.models";
@ -193,6 +191,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
addingLayoutCtx: DashboardPageLayoutContext; addingLayoutCtx: DashboardPageLayoutContext;
logo = 'assets/logo_title_white.svg';
dashboardCtx: DashboardContext = { dashboardCtx: DashboardContext = {
instanceId: this.utils.guid(), instanceId: this.utils.guid(),
@ -486,6 +485,19 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
} }
} }
public showDashboardLogo(): boolean {
if (this.dashboard.configuration.settings &&
isDefined(this.dashboard.configuration.settings.showDashboardLogo)) {
return this.dashboard.configuration.settings.showDashboardLogo;
} else {
return false;
}
}
public get dashboardLogo(): string {
return this.dashboard.configuration.settings.dashboardLogoUrl || this.logo;
}
public showRightLayoutSwitch(): boolean { public showRightLayoutSwitch(): boolean {
return this.isMobile && this.layouts.right.show; return this.isMobile && this.layouts.right.show;
} }
@ -607,7 +619,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
settings: deepClone(this.dashboard.configuration.settings), settings: deepClone(this.dashboard.configuration.settings),
gridSettings gridSettings,
} }
}).afterClosed().subscribe((data) => { }).afterClosed().subscribe((data) => {
if (data) { if (data) {
@ -1189,13 +1201,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
overlayRef.dispose(); overlayRef.dispose();
}); });
const filterWidgetTypes = this.dashboardWidgetSelectComponent.filterWidgetTypes;
const widgetTypesList = Array.from(this.dashboardWidgetSelectComponent.widgetTypes.values()).map(type => {
return {type, display: filterWidgetTypes === null ? true : filterWidgetTypes.includes(type)};
});
const providers: StaticProvider[] = [ const providers: StaticProvider[] = [
{ {
provide: DISPLAY_WIDGET_TYPES_PANEL_DATA, provide: DISPLAY_WIDGET_TYPES_PANEL_DATA,
useValue: { useValue: {
types: Array.from(this.dashboardWidgetSelectComponent.widgetTypes.values()).map(type => { types: widgetTypesList,
return {type, display: true};
}),
typesUpdated: (newTypes) => { typesUpdated: (newTypes) => {
this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type); this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type);
} }

10
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html

@ -52,7 +52,8 @@
formControlName="titleColor"> formControlName="titleColor">
</tb-color-input> </tb-color-input>
</div> </div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.lt-md="column" fxLayoutAlign.lt-md fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 8px;"
fxLayout.lt-md="column" fxLayoutAlign.lt-md fxLayoutGap="8px">
<mat-checkbox fxFlex formControlName="showDashboardsSelect"> <mat-checkbox fxFlex formControlName="showDashboardsSelect">
{{ 'dashboard.display-dashboards-selection' | translate }} {{ 'dashboard.display-dashboards-selection' | translate }}
</mat-checkbox> </mat-checkbox>
@ -69,6 +70,13 @@
{{ 'dashboard.display-dashboard-export' | translate }} {{ 'dashboard.display-dashboard-export' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
<mat-checkbox formControlName="showDashboardLogo">
{{ 'dashboard.display-dashboard-toolbar-logo' | translate }}
</mat-checkbox>
<tb-image-input fxFlex *ngIf="settingsFormGroup.get('showDashboardLogo').value"
label="{{'dashboard.dashboard-logo-image' | translate}}"
formControlName="dashboardLogoUrl">
</tb-image-input>
</div> </div>
<div *ngIf="gridSettings" [formGroup]="gridSettingsFormGroup"> <div *ngIf="gridSettings" [formGroup]="gridSettingsFormGroup">
<tb-color-input fxFlex <tb-color-input fxFlex

2
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts

@ -79,6 +79,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []], showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []],
showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []], showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []],
showFilters: [isUndefined(this.settings.showFilters) ? true : this.settings.showFilters, []], showFilters: [isUndefined(this.settings.showFilters) ? true : this.settings.showFilters, []],
showDashboardLogo: [isUndefined(this.settings.showDashboardLogo) ? false : this.settings.showDashboardLogo, []],
dashboardLogoUrl: [isUndefined(this.settings.dashboardLogoUrl) ? null : this.settings.dashboardLogoUrl, []],
showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []], showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []],
showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []] showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []]
}); });

4
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts

@ -77,6 +77,10 @@ export class DashboardWidgetSelectComponent implements OnInit {
this.filterWidgetTypes$.next(widgetTypes); this.filterWidgetTypes$.next(widgetTypes);
} }
get filterWidgetTypes(): Array<widgetType> {
return this.filterWidgetTypes$.value;
}
@Output() @Output()
widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>(); widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>();

2
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html

@ -80,7 +80,7 @@
(mousedown)="widgetMouseDown($event, widget)" (mousedown)="widgetMouseDown($event, widget)"
(click)="widgetClicked($event, widget)" (click)="widgetClicked($event, widget)"
(contextmenu)="openWidgetContextMenu($event, widget)"> (contextmenu)="openWidgetContextMenu($event, widget)">
<div *ngIf="widgetComponent.widgetContext?.inited" fxLayout="row" fxLayoutAlign="space-between start"> <div *ngIf="!!widgetComponent.widgetContext?.inited" fxLayout="row" fxLayoutAlign="space-between start">
<div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" *ngIf="widget.showWidgetTitlePanel"> <div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" *ngIf="widget.showWidgetTitlePanel">
<span *ngIf="widget.showTitle" <span *ngIf="widget.showTitle"
[ngClass]="{'single-row': widget.hasTimewindow}" [ngClass]="{'single-row': widget.hasTimewindow}"

73
ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html

@ -0,0 +1,73 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form fxLayout="column" class="mat-content mat-padding" [formGroup]="eventFilterFormGroup" (ngSubmit)="update()">
<ng-container *ngFor="let column of showColumns">
<ng-container [ngSwitch]="column.key">
<ng-template [ngSwitchCase]="isSelector(column.key)">
<mat-form-field>
<mat-label>{{ column.title | translate}}</mat-label>
<mat-select [formControlName]="column.key">
<mat-option [value]="">{{ 'event.all-events' | translate}}</mat-option>
<mat-option *ngFor="let value of selectorValues(column.key)" [value]="value">
{{ value }}
</mat-option>
</mat-select>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="isNumberFields(column.key)">
<mat-form-field>
<mat-label>{{ column.title | translate}}</mat-label>
<input matInput type="number" min="0" [name]="column.key" [formControlName]="column.key">
<mat-error *ngIf="eventFilterFormGroup.get(column.key).hasError('min')">
{{ 'event.min-value' | translate }}
</mat-error>
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="'isError'">
<tb-checkbox formControlName="isError" [falseValue]="''">
{{ 'event.has-error' | translate }}
</tb-checkbox>
</ng-template>
<ng-template [ngSwitchCase]="'error'">
<mat-form-field fxHide [fxShow]="showErrorMsgFields()">
<mat-label>{{ column.title | translate}}</mat-label>
<input matInput type="text" name="errorSearchText" formControlName="error">
</mat-form-field>
</ng-template>
<ng-container *ngSwitchDefault>
<mat-form-field>
<mat-label>{{ column.title | translate}}</mat-label>
<input matInput type="text" [name]="column.key" [formControlName]="column.key">
</mat-form-field>
</ng-container>
</ng-container>
</ng-container>
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
<button type="button"
mat-button
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button type="submit"
mat-raised-button
color="primary"
[disabled]="eventFilterFormGroup.invalid || !eventFilterFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
</div>
</form>

35
ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.scss

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
width: 100%;
min-width: 300px;
overflow: auto;
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
.mat-content {
overflow: hidden;
background-color: #fff;
}
.mat-padding {
padding: 16px;
}
}

109
ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts

@ -0,0 +1,109 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, InjectionToken } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { OverlayRef } from '@angular/cdk/overlay';
import { EntityType } from '@shared/models/entity-type.models';
import { FilterEventBody } from '@shared/models/event.models';
import { deepTrim } from '@core/utils';
export const EVENT_FILTER_PANEL_DATA = new InjectionToken<any>('AlarmFilterPanelData');
export interface EventFilterPanelData {
filterParams: FilterEventBody;
columns: Array<FilterEntityColumn>;
}
export interface FilterEntityColumn {
key: string;
title: string;
}
@Component({
selector: 'tb-event-filter-panel',
templateUrl: './event-filter-panel.component.html',
styleUrls: ['./event-filter-panel.component.scss']
})
export class EventFilterPanelComponent {
eventFilterFormGroup: FormGroup;
result: EventFilterPanelData;
private conditionError = false;
private msgDirectionTypes = ['IN', 'OUT'];
private statusTypes = ['Success', 'Failure'];
private entityTypes = Object.keys(EntityType);
showColumns: FilterEntityColumn[] = [];
constructor(@Inject(EVENT_FILTER_PANEL_DATA)
public data: EventFilterPanelData,
public overlayRef: OverlayRef,
private fb: FormBuilder) {
this.eventFilterFormGroup = this.fb.group({});
this.data.columns.forEach((column) => {
this.showColumns.push(column);
const validators = [];
if (this.isNumberFields(column.key)) {
validators.push(Validators.min(0));
}
this.eventFilterFormGroup.addControl(column.key, this.fb.control(this.data.filterParams[column.key] || '', validators));
if (column.key === 'isError') {
this.conditionError = true;
}
});
}
isSelector(key: string): string {
return ['msgDirectionType', 'status', 'entityName'].includes(key) ? key : '';
}
isNumberFields(key: string): string {
return ['messagesProcessed', 'errorsOccurred'].includes(key) ? key : '';
}
selectorValues(key: string): string[] {
switch (key) {
case 'msgDirectionType':
return this.msgDirectionTypes;
case 'status':
return this.statusTypes;
case 'entityName':
return this.entityTypes;
}
}
update() {
const filter = deepTrim(Object.fromEntries(Object.entries(this.eventFilterFormGroup.value).filter(([_, v]) => v !== '')));
this.result = {
filterParams: filter,
columns: this.data.columns
};
this.overlayRef.dispose();
}
showErrorMsgFields() {
return !this.conditionError || this.eventFilterFormGroup.get('isError').value !== '';
}
cancel() {
this.overlayRef.dispose();
}
}

122
ui-ngx/src/app/modules/home/components/event/event-table-config.ts

@ -20,7 +20,7 @@ import {
EntityTableColumn, EntityTableColumn,
EntityTableConfig EntityTableConfig
} from '@home/models/entity/entities-table-config.models'; } from '@home/models/entity/entities-table-config.models';
import { DebugEventType, Event, EventType } from '@shared/models/event.models'; import { DebugEventType, Event, EventType, FilterEventBody } from '@shared/models/event.models';
import { TimePageLink } from '@shared/models/page/page-link'; import { TimePageLink } from '@shared/models/page/page-link';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
@ -38,16 +38,29 @@ import {
EventContentDialogComponent, EventContentDialogComponent,
EventContentDialogData EventContentDialogData
} from '@home/components/event/event-content-dialog.component'; } from '@home/components/event/event-content-dialog.component';
import { sortObjectKeys } from '@core/utils'; import { isEqual, sortObjectKeys } from '@core/utils';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ChangeDetectorRef, Injector, StaticProvider, ViewContainerRef } from '@angular/core';
import { ComponentPortal } from '@angular/cdk/portal';
import {
EVENT_FILTER_PANEL_DATA,
EventFilterPanelComponent,
EventFilterPanelData,
FilterEntityColumn
} from '@home/components/event/event-filter-panel.component';
export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
eventTypeValue: EventType | DebugEventType; eventTypeValue: EventType | DebugEventType;
private filterParams: FilterEventBody = {};
private filterColumns: FilterEntityColumn[] = [];
set eventType(eventType: EventType | DebugEventType) { set eventType(eventType: EventType | DebugEventType) {
if (this.eventTypeValue !== eventType) { if (this.eventTypeValue !== eventType) {
this.eventTypeValue = eventType; this.eventTypeValue = eventType;
this.updateColumns(true); this.updateColumns(true);
this.updateFilterColumns();
} }
} }
@ -66,7 +79,10 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
public tenantId: string, public tenantId: string,
private defaultEventType: EventType | DebugEventType, private defaultEventType: EventType | DebugEventType,
private disabledEventTypes: Array<EventType | DebugEventType> = null, private disabledEventTypes: Array<EventType | DebugEventType> = null,
private debugEventTypes: Array<DebugEventType> = null) { private debugEventTypes: Array<DebugEventType> = null,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private cd: ChangeDetectorRef) {
super(); super();
this.loadDataOnInit = false; this.loadDataOnInit = false;
this.tableTitle = ''; this.tableTitle = '';
@ -101,10 +117,20 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.updateColumns(); this.updateColumns();
this.updateFilterColumns();
this.headerActionDescriptors.push({
name: this.translate.instant('event.events-filter'),
icon: 'filter_list',
isEnabled: () => true,
onAction: ($event) => {
this.editEventFilter($event);
}
});
} }
fetchEvents(pageLink: TimePageLink): Observable<PageData<Event>> { fetchEvents(pageLink: TimePageLink): Observable<PageData<Event>> {
return this.eventService.getEvents(this.entityId, this.eventType, this.tenantId, pageLink); return this.eventService.getFilterEvents(this.entityId, this.eventType, this.tenantId, this.filterParams, pageLink);
} }
updateColumns(updateTableColumns: boolean = false): void { updateColumns(updateTableColumns: boolean = false): void {
@ -169,7 +195,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
}), false, key => ({ }), false, key => ({
padding: '0 12px 0 0' padding: '0 12px 0 0'
})), })),
new EntityTableColumn<Event>('entity', 'event.entity', '100px', new EntityTableColumn<Event>('entityName', 'event.entity-type', '100px',
(entity) => entity.body.entityName, entity => ({ (entity) => entity.body.entityName, entity => ({
padding: '0 12px 0 0', padding: '0 12px 0 0',
}), false, key => ({ }), false, key => ({
@ -249,5 +275,91 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
} }
}); });
} }
private updateFilterColumns() {
this.filterParams = {};
this.filterColumns = [{key: 'server', title: 'event.server'}];
switch (this.eventType) {
case EventType.ERROR:
this.filterColumns.push(
{key: 'method', title: 'event.method'},
{key: 'error', title: 'event.error'}
);
break;
case EventType.LC_EVENT:
this.filterColumns.push(
{key: 'method', title: 'event.event'},
{key: 'status', title: 'event.status'},
{key: 'error', title: 'event.error'}
);
break;
case EventType.STATS:
this.filterColumns.push(
{key: 'messagesProcessed', title: 'event.min-messages-processed'},
{key: 'errorsOccurred', title: 'event.min-errors-occurred'}
);
break;
case DebugEventType.DEBUG_RULE_NODE:
case DebugEventType.DEBUG_RULE_CHAIN:
this.filterColumns.push(
{key: 'msgDirectionType', title: 'event.type'},
{key: 'entityId', title: 'event.entity-id'},
{key: 'entityName', title: 'event.entity-type'},
{key: 'msgType', title: 'event.message-type'},
{key: 'relationType', title: 'event.relation-type'},
{key: 'dataSearch', title: 'event.data'},
{key: 'metadataSearch', title: 'event.metadata'},
{key: 'isError', title: 'event.error'},
{key: 'error', title: 'event.error'}
);
break;
}
}
private editEventFilter($event: MouseEvent) {
if ($event) {
$event.stopPropagation();
}
const target = $event.target || $event.srcElement || $event.currentTarget;
const config = new OverlayConfig();
config.backdropClass = 'cdk-overlay-transparent-backdrop';
config.hasBackdrop = true;
const connectedPosition: ConnectedPosition = {
originX: 'end',
originY: 'bottom',
overlayX: 'end',
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const providers: StaticProvider[] = [
{
provide: EVENT_FILTER_PANEL_DATA,
useValue: {
columns: this.filterColumns,
filterParams: this.filterParams
} as EventFilterPanelData
},
{
provide: OverlayRef,
useValue: overlayRef
}
];
const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
const componentRef = overlayRef.attach(new ComponentPortal(EventFilterPanelComponent,
this.viewContainerRef, injector));
componentRef.onDestroy(() => {
if (componentRef.instance.result && !isEqual(this.filterParams, componentRef.instance.result.filterParams)) {
this.filterParams = componentRef.instance.result.filterParams;
this.table.updateData();
}
});
this.cd.detectChanges();
}
} }

13
ui-ngx/src/app/modules/home/components/event/event-table.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -24,6 +24,7 @@ import { EventTableConfig } from './event-table-config';
import { EventService } from '@core/http/event.service'; import { EventService } from '@core/http/event.service';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { DebugEventType, EventType } from '@shared/models/event.models'; import { DebugEventType, EventType } from '@shared/models/event.models';
import { Overlay } from '@angular/cdk/overlay';
@Component({ @Component({
selector: 'tb-event-table', selector: 'tb-event-table',
@ -80,7 +81,10 @@ export class EventTableComponent implements OnInit {
private dialogService: DialogService, private dialogService: DialogService,
private translate: TranslateService, private translate: TranslateService,
private datePipe: DatePipe, private datePipe: DatePipe,
private dialog: MatDialog) { private dialog: MatDialog,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private cd: ChangeDetectorRef) {
} }
ngOnInit() { ngOnInit() {
@ -95,7 +99,10 @@ export class EventTableComponent implements OnInit {
this.tenantId, this.tenantId,
this.defaultEventType, this.defaultEventType,
this.disabledEventTypes, this.disabledEventTypes,
this.debugEventTypes this.debugEventTypes,
this.overlay,
this.viewContainerRef,
this.cd
); );
} }

36
ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.ts

@ -15,11 +15,21 @@
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { import {
BooleanFilterPredicate, BooleanFilterPredicate,
BooleanOperation, BooleanOperation,
booleanOperationTranslationMap, EntityKeyValueType, booleanOperationTranslationMap,
EntityKeyValueType,
FilterPredicateType FilterPredicateType
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
@ -32,10 +42,15 @@ import {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BooleanFilterPredicateComponent), useExisting: forwardRef(() => BooleanFilterPredicateComponent),
multi: true multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => BooleanFilterPredicateComponent),
multi: true
} }
] ]
}) })
export class BooleanFilterPredicateComponent implements ControlValueAccessor, OnInit { export class BooleanFilterPredicateComponent implements ControlValueAccessor, Validator, OnInit {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -73,7 +88,7 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
setDisabledState?(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) { if (this.disabled) {
this.booleanFilterPredicateFormGroup.disable({emitEvent: false}); this.booleanFilterPredicateFormGroup.disable({emitEvent: false});
@ -82,17 +97,20 @@ export class BooleanFilterPredicateComponent implements ControlValueAccessor, On
} }
} }
validate(): ValidationErrors | null {
return this.booleanFilterPredicateFormGroup ? null : {
booleanFilterPredicate: {valid: false}
};
}
writeValue(predicate: BooleanFilterPredicate): void { writeValue(predicate: BooleanFilterPredicate): void {
this.booleanFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false}); this.booleanFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false});
this.booleanFilterPredicateFormGroup.get('value').patchValue(predicate.value, {emitEvent: false}); this.booleanFilterPredicateFormGroup.get('value').patchValue(predicate.value, {emitEvent: false});
} }
private updateModel() { private updateModel() {
let predicate: BooleanFilterPredicate = null; const predicate: BooleanFilterPredicate = this.booleanFilterPredicateFormGroup.getRawValue();
if (this.booleanFilterPredicateFormGroup.valid) { predicate.type = FilterPredicateType.BOOLEAN;
predicate = this.booleanFilterPredicateFormGroup.getRawValue();
predicate.type = FilterPredicateType.BOOLEAN;
}
this.propagateChange(predicate); this.propagateChange(predicate);
} }

8
ui-ngx/src/app/modules/home/components/filter/complex-filter-predicate.component.ts

@ -16,11 +16,7 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { import { ComplexFilterPredicateInfo, EntityKeyValueType } from '@shared/models/query/query.models';
ComplexFilterPredicate,
ComplexFilterPredicateInfo,
EntityKeyValueType
} from '@shared/models/query/query.models';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { import {
ComplexFilterPredicateDialogComponent, ComplexFilterPredicateDialogComponent,
@ -71,7 +67,7 @@ export class ComplexFilterPredicateComponent implements ControlValueAccessor, On
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
setDisabledState?(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
} }

18
ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.ts

@ -21,7 +21,10 @@ import {
FormArray, FormArray,
FormBuilder, FormBuilder,
FormGroup, FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { Observable, of, Subscription } from 'rxjs'; import { Observable, of, Subscription } from 'rxjs';
@ -49,10 +52,15 @@ import { map } from 'rxjs/operators';
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FilterPredicateListComponent), useExisting: forwardRef(() => FilterPredicateListComponent),
multi: true multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => FilterPredicateListComponent),
multi: true
} }
] ]
}) })
export class FilterPredicateListComponent implements ControlValueAccessor, OnInit { export class FilterPredicateListComponent implements ControlValueAccessor, Validator, OnInit {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -108,6 +116,12 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
} }
} }
validate(control: AbstractControl): ValidationErrors | null {
return this.filterListFormGroup.valid ? null : {
filterList: {valid: false}
};
}
writeValue(predicates: Array<KeyFilterPredicateInfo>): void { writeValue(predicates: Array<KeyFilterPredicateInfo>): void {
if (this.valueChangeSubscription) { if (this.valueChangeSubscription) {
this.valueChangeSubscription.unsubscribe(); this.valueChangeSubscription.unsubscribe();
@ -178,7 +192,7 @@ export class FilterPredicateListComponent implements ControlValueAccessor, OnIni
private updateModel() { private updateModel() {
const predicates: Array<KeyFilterPredicateInfo> = this.filterListFormGroup.getRawValue().predicates; const predicates: Array<KeyFilterPredicateInfo> = this.filterListFormGroup.getRawValue().predicates;
if (this.filterListFormGroup.valid && predicates.length) { if (predicates.length) {
this.propagateChange(predicates); this.propagateChange(predicates);
} else { } else {
this.propagateChange(null); this.propagateChange(null);

84
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts

@ -19,7 +19,10 @@ import {
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
FormGroup, FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn, ValidatorFn,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
@ -39,10 +42,15 @@ import {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FilterPredicateValueComponent), useExisting: forwardRef(() => FilterPredicateValueComponent),
multi: true multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => FilterPredicateValueComponent),
multi: true
} }
] ]
}) })
export class FilterPredicateValueComponent implements ControlValueAccessor, OnInit { export class FilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit {
private readonly inheritModeForSources: DynamicValueSourceType[] = [ private readonly inheritModeForSources: DynamicValueSourceType[] = [
DynamicValueSourceType.CURRENT_CUSTOMER, DynamicValueSourceType.CURRENT_CUSTOMER,
@ -62,7 +70,22 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
} }
} }
@Input() onlyUserDynamicSource = false; private onlyUserDynamicSourceValue = false;
@Input()
set onlyUserDynamicSource(dynamicMode: boolean) {
this.onlyUserDynamicSourceValue = dynamicMode;
if (this.filterPredicateValueFormGroup) {
this.updateValidationDynamicMode();
setTimeout(() => {
this.updateModel();
}, 0);
}
}
get onlyUserDynamicSource(): boolean {
return this.onlyUserDynamicSourceValue;
}
@Input() @Input()
valueType: EntityKeyValueType; valueType: EntityKeyValueType;
@ -83,6 +106,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
allow = true; allow = true;
private propagateChange = null; private propagateChange = null;
private propagateChangePending = false;
constructor(private fb: FormBuilder) { constructor(private fb: FormBuilder) {
} }
@ -126,6 +150,7 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
this.updateShowInheritMode(sourceType); this.updateShowInheritMode(sourceType);
} }
); );
this.updateValidationDynamicMode();
this.filterPredicateValueFormGroup.valueChanges.subscribe(() => { this.filterPredicateValueFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
}); });
@ -133,12 +158,18 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
if (this.propagateChangePending) {
this.propagateChangePending = false;
setTimeout(() => {
this.updateModel();
}, 0);
}
} }
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
setDisabledState?(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) { if (this.disabled) {
this.filterPredicateValueFormGroup.disable({emitEvent: false}); this.filterPredicateValueFormGroup.disable({emitEvent: false});
@ -147,28 +178,35 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
} }
} }
validate(): ValidationErrors | null {
return this.filterPredicateValueFormGroup.valid ? null : {
filterPredicateValue: {valid: false}
};
}
writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void { writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void {
this.propagateChangePending = false;
this.filterPredicateValueFormGroup.get('defaultValue').patchValue(predicateValue.defaultValue, {emitEvent: false}); this.filterPredicateValueFormGroup.get('defaultValue').patchValue(predicateValue.defaultValue, {emitEvent: false});
this.filterPredicateValueFormGroup.get('dynamicValue.sourceType').patchValue(predicateValue.dynamicValue ? this.filterPredicateValueFormGroup.get('dynamicValue').patchValue({
predicateValue.dynamicValue.sourceType : null, {emitEvent: false}); sourceType: predicateValue.dynamicValue ? predicateValue.dynamicValue.sourceType : null,
this.filterPredicateValueFormGroup.get('dynamicValue.sourceAttribute').patchValue(predicateValue.dynamicValue ? sourceAttribute: predicateValue.dynamicValue ? predicateValue.dynamicValue.sourceAttribute : null,
predicateValue.dynamicValue.sourceAttribute : null, {emitEvent: false}); inherit: predicateValue.dynamicValue ? predicateValue.dynamicValue.inherit : false
this.filterPredicateValueFormGroup.get('dynamicValue.inherit').patchValue(predicateValue.dynamicValue ? }, {emitEvent: this.onlyUserDynamicSource});
predicateValue.dynamicValue.inherit : false, {emitEvent: false});
this.updateShowInheritMode(predicateValue?.dynamicValue?.sourceType); this.updateShowInheritMode(predicateValue?.dynamicValue?.sourceType);
} }
private updateModel() { private updateModel() {
let predicateValue: FilterPredicateValue<string | number | boolean> = null; const predicateValue: FilterPredicateValue<string | number | boolean> = this.filterPredicateValueFormGroup.getRawValue();
if (this.filterPredicateValueFormGroup.valid) { if (predicateValue.dynamicValue) {
predicateValue = this.filterPredicateValueFormGroup.getRawValue(); if (!predicateValue.dynamicValue.sourceType || !predicateValue.dynamicValue.sourceAttribute) {
if (predicateValue.dynamicValue) { predicateValue.dynamicValue = null;
if (!predicateValue.dynamicValue.sourceType || !predicateValue.dynamicValue.sourceAttribute) {
predicateValue.dynamicValue = null;
}
} }
} }
this.propagateChange(predicateValue); if (this.propagateChange) {
this.propagateChange(predicateValue);
} else {
this.propagateChangePending = true;
}
} }
private updateShowInheritMode(sourceType: DynamicValueSourceType) { private updateShowInheritMode(sourceType: DynamicValueSourceType) {
@ -179,4 +217,16 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, OnIn
this.inheritMode = false; this.inheritMode = false;
} }
} }
private updateValidationDynamicMode() {
if (this.onlyUserDynamicSource) {
this.filterPredicateValueFormGroup.get('dynamicValue.sourceType').setValidators(Validators.required);
this.filterPredicateValueFormGroup.get('dynamicValue.sourceAttribute').setValidators(Validators.required);
} else {
this.filterPredicateValueFormGroup.get('dynamicValue.sourceType').clearValidators();
this.filterPredicateValueFormGroup.get('dynamicValue.sourceAttribute').clearValidators();
}
this.filterPredicateValueFormGroup.get('dynamicValue.sourceType').updateValueAndValidity({emitEvent: false});
this.filterPredicateValueFormGroup.get('dynamicValue.sourceAttribute').updateValueAndValidity({emitEvent: false});
}
} }

29
ui-ngx/src/app/modules/home/components/filter/filter-predicate.component.ts

@ -15,11 +15,17 @@
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { import {
EntityKeyValueType, ControlValueAccessor,
FilterPredicateType, KeyFilterPredicate, KeyFilterPredicateInfo FormBuilder,
} from '@shared/models/query/query.models'; FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { EntityKeyValueType, FilterPredicateType, KeyFilterPredicateInfo } from '@shared/models/query/query.models';
@Component({ @Component({
selector: 'tb-filter-predicate', selector: 'tb-filter-predicate',
@ -30,10 +36,15 @@ import {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FilterPredicateComponent), useExisting: forwardRef(() => FilterPredicateComponent),
multi: true multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => FilterPredicateComponent),
multi: true
} }
] ]
}) })
export class FilterPredicateComponent implements ControlValueAccessor, OnInit { export class FilterPredicateComponent implements ControlValueAccessor, Validator, OnInit {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -75,7 +86,7 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
setDisabledState?(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) { if (this.disabled) {
this.filterPredicateFormGroup.disable({emitEvent: false}); this.filterPredicateFormGroup.disable({emitEvent: false});
@ -84,6 +95,12 @@ export class FilterPredicateComponent implements ControlValueAccessor, OnInit {
} }
} }
validate(): ValidationErrors | null {
return this.filterPredicateFormGroup.valid ? null : {
filterPredicate: {valid: false}
};
}
writeValue(predicate: KeyFilterPredicateInfo): void { writeValue(predicate: KeyFilterPredicateInfo): void {
this.type = predicate.keyFilterPredicate.type; this.type = predicate.keyFilterPredicate.type;
this.filterPredicateFormGroup.get('predicate').patchValue(predicate.keyFilterPredicate, {emitEvent: false}); this.filterPredicateFormGroup.get('predicate').patchValue(predicate.keyFilterPredicate, {emitEvent: false});

16
ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts

@ -22,7 +22,10 @@ import {
FormBuilder, FormBuilder,
FormControl, FormControl,
FormGroup, FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
@ -46,10 +49,15 @@ import { EntityId } from '@shared/models/id/entity-id';
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => KeyFilterListComponent), useExisting: forwardRef(() => KeyFilterListComponent),
multi: true multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => KeyFilterListComponent),
multi: true
} }
] ]
}) })
export class KeyFilterListComponent implements ControlValueAccessor, OnInit { export class KeyFilterListComponent implements ControlValueAccessor, Validator, OnInit {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -104,6 +112,12 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
} }
} }
validate(): ValidationErrors | null {
return this.keyFilterListFormGroup.valid && this.keyFiltersControl.valid ? null : {
keyFilterList: {valid: false}
};
}
writeValue(keyFilters: Array<KeyFilterInfo>): void { writeValue(keyFilters: Array<KeyFilterInfo>): void {
if (this.valueChangeSubscription) { if (this.valueChangeSubscription) {
this.valueChangeSubscription.unsubscribe(); this.valueChangeSubscription.unsubscribe();

33
ui-ngx/src/app/modules/home/components/filter/numeric-filter-predicate.component.ts

@ -15,7 +15,16 @@
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { import {
EntityKeyValueType, EntityKeyValueType,
FilterPredicateType, FilterPredicateType,
@ -33,10 +42,15 @@ import {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NumericFilterPredicateComponent), useExisting: forwardRef(() => NumericFilterPredicateComponent),
multi: true multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => NumericFilterPredicateComponent),
multi: true
} }
] ]
}) })
export class NumericFilterPredicateComponent implements ControlValueAccessor, OnInit { export class NumericFilterPredicateComponent implements ControlValueAccessor, Validator, OnInit {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -76,7 +90,7 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
} }
setDisabledState?(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) { if (this.disabled) {
this.numericFilterPredicateFormGroup.disable({emitEvent: false}); this.numericFilterPredicateFormGroup.disable({emitEvent: false});
@ -85,17 +99,20 @@ export class NumericFilterPredicateComponent implements ControlValueAccessor, On
} }
} }
validate(): ValidationErrors | null {
return this.numericFilterPredicateFormGroup.valid ? null : {
numericFilterPredicate: {valid: false}
};
}
writeValue(predicate: NumericFilterPredicate): void { writeValue(predicate: NumericFilterPredicate): void {
this.numericFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false}); this.numericFilterPredicateFormGroup.get('operation').patchValue(predicate.operation, {emitEvent: false});
this.numericFilterPredicateFormGroup.get('value').patchValue(predicate.value, {emitEvent: false}); this.numericFilterPredicateFormGroup.get('value').patchValue(predicate.value, {emitEvent: false});
} }
private updateModel() { private updateModel() {
let predicate: NumericFilterPredicate = null; const predicate: NumericFilterPredicate = this.numericFilterPredicateFormGroup.getRawValue();
if (this.numericFilterPredicateFormGroup.valid) { predicate.type = FilterPredicateType.NUMERIC;
predicate = this.numericFilterPredicateFormGroup.getRawValue();
predicate.type = FilterPredicateType.NUMERIC;
}
this.propagateChange(predicate); this.propagateChange(predicate);
} }

31
ui-ngx/src/app/modules/home/components/filter/string-filter-predicate.component.ts

@ -15,7 +15,16 @@
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators
} from '@angular/forms';
import { import {
EntityKeyValueType, EntityKeyValueType,
FilterPredicateType, FilterPredicateType,
@ -33,10 +42,15 @@ import {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => StringFilterPredicateComponent), useExisting: forwardRef(() => StringFilterPredicateComponent),
multi: true multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => StringFilterPredicateComponent),
multi: true
} }
] ]
}) })
export class StringFilterPredicateComponent implements ControlValueAccessor, OnInit { export class StringFilterPredicateComponent implements ControlValueAccessor, Validator, OnInit {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -90,12 +104,15 @@ export class StringFilterPredicateComponent implements ControlValueAccessor, OnI
this.stringFilterPredicateFormGroup.get('ignoreCase').patchValue(predicate.ignoreCase, {emitEvent: false}); this.stringFilterPredicateFormGroup.get('ignoreCase').patchValue(predicate.ignoreCase, {emitEvent: false});
} }
validate(c): ValidationErrors {
return this.stringFilterPredicateFormGroup.valid ? null : {
stringFilterPredicate: {valid: false}
};
}
private updateModel() { private updateModel() {
let predicate: StringFilterPredicate = null; const predicate: StringFilterPredicate = this.stringFilterPredicateFormGroup.getRawValue();
if (this.stringFilterPredicateFormGroup.valid) { predicate.type = FilterPredicateType.STRING;
predicate = this.stringFilterPredicateFormGroup.getRawValue();
predicate.type = FilterPredicateType.STRING;
}
this.propagateChange(predicate); this.propagateChange(predicate);
} }

2
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -25,6 +25,7 @@ import { AuditLogDetailsDialogComponent } from '@home/components/audit-log/audit
import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component'; import { AuditLogTableComponent } from '@home/components/audit-log/audit-log-table.component';
import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component'; import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
import { EventTableComponent } from '@home/components/event/event-table.component'; import { EventTableComponent } from '@home/components/event/event-table.component';
import { EventFilterPanelComponent } from '@home/components/event/event-filter-panel.component';
import { RelationTableComponent } from '@home/components/relation/relation-table.component'; import { RelationTableComponent } from '@home/components/relation/relation-table.component';
import { RelationDialogComponent } from '@home/components/relation/relation-dialog.component'; import { RelationDialogComponent } from '@home/components/relation/relation-dialog.component';
import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component'; import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component';
@ -149,6 +150,7 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag
EventContentDialogComponent, EventContentDialogComponent,
EventTableHeaderComponent, EventTableHeaderComponent,
EventTableComponent, EventTableComponent,
EventFilterPanelComponent,
EdgeDownlinkTableHeaderComponent, EdgeDownlinkTableHeaderComponent,
EdgeDownlinkTableComponent, EdgeDownlinkTableComponent,
RelationTableComponent, RelationTableComponent,

25
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts

@ -14,12 +14,12 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {DeviceProfileTransportConfiguration, DeviceTransportType} from '@shared/models/device.models'; import { DeviceProfileTransportConfiguration } from '@shared/models/device.models';
import {Component, forwardRef, Inject, Input} from '@angular/core'; import { Component, forwardRef, Inject, Input } from '@angular/core';
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {Store} from '@ngrx/store'; import { Store } from '@ngrx/store';
import {AppState} from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import {coerceBooleanProperty} from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { import {
ATTRIBUTE, ATTRIBUTE,
DEFAULT_BINDING, DEFAULT_BINDING,
@ -34,11 +34,11 @@ import {
RESOURCES, RESOURCES,
TELEMETRY TELEMETRY
} from './lwm2m-profile-config.models'; } from './lwm2m-profile-config.models';
import {DeviceProfileService} from '@core/http/device-profile.service'; import { DeviceProfileService } from '@core/http/device-profile.service';
import {deepClone, isDefinedAndNotNull, isEmpty, isUndefined} from '@core/utils'; import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/utils';
import {WINDOW} from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import {JsonArray, JsonObject} from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point';
import {Direction} from '@shared/models/page/sort-order'; import { Direction } from '@shared/models/page/sort-order';
@Component({ @Component({
selector: 'tb-profile-lwm2m-device-transport-configuration', selector: 'tb-profile-lwm2m-device-transport-configuration',
@ -173,7 +173,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
} }
private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => { private updateObserveAttrTelemetryObjectFormGroup = (objectsList: ObjectLwM2M[]): void => {
this.lwm2mDeviceProfileFormGroup.patchValue({ this.lwm2mDeviceProfileFormGroup.patchValue({
observeAttrTelemetry: deepClone(this.getObserveAttrTelemetryObjects(objectsList)) observeAttrTelemetry: deepClone(this.getObserveAttrTelemetryObjects(objectsList))
}, },
{emitEvent: false}); {emitEvent: false});
@ -358,6 +358,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro
this.configurationValue.observeAttr.observe = observeArray; this.configurationValue.observeAttr.observe = observeArray;
this.configurationValue.observeAttr.attribute = attributeArray; this.configurationValue.observeAttr.attribute = attributeArray;
this.configurationValue.observeAttr.telemetry = telemetryArray; this.configurationValue.observeAttr.telemetry = telemetryArray;
this.configurationValue.observeAttr.keyName = this.sortObjectKeyPathJson(KEY_NAME, keyNameNew);
this.configurationValue.observeAttr.attributeLwm2m = attributeLwm2m; this.configurationValue.observeAttr.attributeLwm2m = attributeLwm2m;
} }
} }

3
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html

@ -47,6 +47,7 @@
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div fxLayout="column" fxLayoutGap="8px" formArrayName="instances"> <div fxLayout="column" fxLayoutGap="8px" formArrayName="instances">
<mat-expansion-panel <mat-expansion-panel
class="instance-list"
*ngFor="let instances of instancesLwm2mFormArray(objectLwM2M).controls; let y = index;" *ngFor="let instances of instancesLwm2mFormArray(objectLwM2M).controls; let y = index;"
[formGroupName]="y" [formGroupName]="y"
[expanded]="getExpended(objectLwM2M)" [expanded]="getExpended(objectLwM2M)"
@ -96,7 +97,7 @@
</div> </div>
<div fxFlex="10"> <div fxFlex="10">
</div> </div>
<div fxFlex="37" class="resource-name-lw-end" fxFlexOffset="5" disabled="false"> <div fxFlex="37" class="resource-name-lw-end" fxFlexOffset="5">
<tb-profile-lwm2m-attributes <tb-profile-lwm2m-attributes
formControlName="attributeLwm2m" formControlName="attributeLwm2m"
[attributeLwm2m]="instances.get('attributeLwm2m').value" [attributeLwm2m]="instances.get('attributeLwm2m').value"

8
ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss

@ -22,3 +22,11 @@
padding-left: 22px; padding-left: 22px;
text-align:center; text-align:center;
} }
:host{
.instance-list {
mat-expansion-panel-header {
color: inherit;
}
}
}

69
ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html

@ -0,0 +1,69 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-json-input" tb-toast toastTarget="{{ toastTargetId }}">
<form *ngIf="attributeUpdateFormGroup"
fxLayout="column"
class="tb-json-input__form"
[formGroup]="attributeUpdateFormGroup"
(ngSubmit)="save()">
<div fxLayout="column" fxLayoutGap="10px" fxFlex *ngIf="entityDetected && isValidParameter && dataKeyDetected">
<fieldset fxFlex>
<tb-json-object-edit
[editorStyle]="{minHeight: '100px'}"
fillHeight="true"
[required]="settings.attributeRequired"
label="{{ settings.showLabel ? labelValue : '' }}"
formControlName="currentValue"
(focusin)="isFocused = true;"
(focusout)="isFocused = false;"
></tb-json-object-edit>
</fieldset>
<div class="tb-json-input-form__actions" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="20px">
<button mat-button color="primary"
type="button"
[disabled]="!attributeUpdateFormGroup.dirty"
(click)="discard()"
matTooltip="{{ 'widgets.input-widgets.discard-changes' | translate }}"
matTooltipPosition="above">
{{ "action.undo" | translate }}
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty">
{{ "action.save" | translate }}
</button>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center center" fxFlex *ngIf="!entityDetected || !dataKeyDetected || !isValidParameter">
<div class="tb-json-input__error"
*ngIf="!entityDetected">
{{ 'widgets.input-widgets.no-entity-selected' | translate }}
</div>
<div class="tb-json-input__error"
*ngIf="entityDetected && !dataKeyDetected">
{{ 'widgets.input-widgets.no-datakey-selected' | translate }}
</div>
<div class="tb-json-input__error"
*ngIf="dataKeyDetected && !isValidParameter">
{{ errorMessage | translate }}
</div>
</div>
</form>
</div>

23
dao/src/test/java/org/thingsboard/server/dao/service/sql/TbResourceServiceSqlTest.java → ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.scss

@ -13,11 +13,24 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.service.sql;
import org.thingsboard.server.dao.service.BaseTbResourceServiceTest; .tb-json-input {
import org.thingsboard.server.dao.service.DaoSqlTest; width: 100%;
height: 100%;
padding: 5px;
@DaoSqlTest &__form {
public class TbResourceServiceSqlTest extends BaseTbResourceServiceTest { overflow: auto;
height: 100%;
}
&__error {
text-align: center;
font-size: 18px;
color: #a0a0a0;
}
}
.tb-toast {
font-size: 14px!important;
} }

227
ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.ts

@ -0,0 +1,227 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Input, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { WidgetContext } from '@home/models/widget-component.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { AttributeService } from '@core/http/attribute.service';
import { AttributeData, AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models';
import { createLabelFromDatasource } from '@core/utils';
import { Observable } from 'rxjs';
enum JsonInputWidgetMode {
ATTRIBUTE = 'ATTRIBUTE',
TIME_SERIES = 'TIME_SERIES',
}
interface JsonInputWidgetSettings {
widgetTitle: string;
widgetMode: JsonInputWidgetMode;
attributeScope?: AttributeScope;
showLabel: boolean;
labelValue?: string;
attributeRequired: boolean;
showResultMessage: boolean;
}
@Component({
selector: 'tb-json-input-widget ',
templateUrl: './json-input-widget.component.html',
styleUrls: ['./json-input-widget.component.scss']
})
export class JsonInputWidgetComponent extends PageComponent implements OnInit {
@Input()
ctx: WidgetContext;
public settings: JsonInputWidgetSettings;
private widgetConfig: WidgetConfig;
private subscription: IWidgetSubscription;
private datasource: Datasource;
labelValue: string;
entityDetected = false;
dataKeyDetected = false;
isValidParameter = false;
errorMessage: string;
isFocused: boolean;
originalValue: any;
attributeUpdateFormGroup: FormGroup;
toastTargetId = 'json-input-widget' + this.utils.guid();
constructor(protected store: Store<AppState>,
private utils: UtilsService,
private fb: FormBuilder,
private attributeService: AttributeService,
private translate: TranslateService) {
super(store);
}
ngOnInit(): void {
this.ctx.$scope.jsonInputWidget = this;
this.settings = this.ctx.settings;
this.widgetConfig = this.ctx.widgetConfig;
this.subscription = this.ctx.defaultSubscription;
this.datasource = this.subscription.datasources[0];
this.initializeConfig();
this.validateDatasources();
this.buildForm();
this.ctx.updateWidgetParams();
}
private initializeConfig() {
if (this.settings.widgetTitle && this.settings.widgetTitle.length) {
const title = createLabelFromDatasource(this.datasource, this.settings.widgetTitle);
this.ctx.widgetTitle = this.utils.customTranslation(title, title);
} else {
this.ctx.widgetTitle = this.ctx.widgetConfig.title;
}
if (this.settings.labelValue && this.settings.labelValue.length) {
const label = createLabelFromDatasource(this.datasource, this.settings.labelValue);
this.labelValue = this.utils.customTranslation(label, label);
} else {
this.labelValue = this.translate.instant('widgets.input-widgets.value');
}
}
private validateDatasources() {
if (this.datasource?.type === DatasourceType.entity) {
this.entityDetected = true;
if (this.datasource.dataKeys.length) {
this.dataKeyDetected = true;
if (this.settings.widgetMode === JsonInputWidgetMode.ATTRIBUTE) {
if (this.datasource.dataKeys[0].type === DataKeyType.attribute) {
if (this.settings.attributeScope === AttributeScope.SERVER_SCOPE || this.datasource.entityType === EntityType.DEVICE) {
this.isValidParameter = true;
} else {
this.errorMessage = 'widgets.input-widgets.not-allowed-entity';
}
} else {
this.errorMessage = 'widgets.input-widgets.no-attribute-selected';
}
} else {
if (this.datasource.dataKeys[0].type === DataKeyType.timeseries) {
this.isValidParameter = true;
} else {
this.errorMessage = 'widgets.input-widgets.no-timeseries-selected';
}
}
}
}
}
private buildForm() {
const validators: ValidatorFn[] = [];
if (this.settings.attributeRequired) {
validators.push(Validators.required);
}
this.attributeUpdateFormGroup = this.fb.group({
currentValue: [{}, validators]
});
this.attributeUpdateFormGroup.valueChanges.subscribe(() => {
this.ctx.detectChanges();
});
}
private updateWidgetData(data: Array<DatasourceData>) {
if (this.isValidParameter) {
let value = {};
if (data[0].data[0][1] !== '') {
try {
value = JSON.parse(data[0].data[0][1]);
} catch (e) {
value = data[0].data[0][1];
}
}
this.originalValue = value;
if (!this.isFocused) {
this.attributeUpdateFormGroup.get('currentValue').patchValue(this.originalValue);
this.ctx.detectChanges();
}
}
}
public onDataUpdated() {
this.updateWidgetData(this.subscription.data);
}
public save() {
this.isFocused = false;
const attributeToSave: AttributeData = {
key: this.datasource.dataKeys[0].name,
value: this.attributeUpdateFormGroup.get('currentValue').value
};
const entityId: EntityId = {
entityType: this.datasource.entityType,
id: this.datasource.entityId
};
let saveAttributeObservable: Observable<any>;
if (this.settings.widgetMode === JsonInputWidgetMode.ATTRIBUTE) {
saveAttributeObservable = this.attributeService.saveEntityAttributes(
entityId,
this.settings.attributeScope,
[attributeToSave],
{}
);
} else {
saveAttributeObservable = this.attributeService.saveEntityTimeseries(
entityId,
LatestTelemetry.LATEST_TELEMETRY,
[attributeToSave],
{}
);
}
saveAttributeObservable.subscribe(
() => {
this.attributeUpdateFormGroup.markAsPristine();
this.ctx.detectChanges();
if (this.settings.showResultMessage) {
this.ctx.showSuccessToast(this.translate.instant('widgets.input-widgets.update-successful'),
1000, 'bottom', 'left', this.toastTargetId);
}
},
() => {
if (this.settings.showResultMessage) {
this.ctx.showErrorToast(this.translate.instant('widgets.input-widgets.update-failed'),
'bottom', 'left', this.toastTargetId);
}
});
}
public discard() {
this.attributeUpdateFormGroup.reset({currentValue: this.originalValue}, {emitEvent: false});
this.attributeUpdateFormGroup.markAsPristine();
this.isFocused = false;
}
}

25
ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts

@ -19,7 +19,8 @@ import {
createLabelFromDatasource, createLabelFromDatasource,
hashCode, hashCode,
isDefined, isDefined,
isDefinedAndNotNull, isFunction, isDefinedAndNotNull,
isFunction,
isNumber, isNumber,
isUndefined, isUndefined,
padValue padValue
@ -30,7 +31,7 @@ import { Datasource, DatasourceData } from '@shared/models/widget.models';
import _ from 'lodash'; import _ from 'lodash';
import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes'; import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes';
import { addCondition, mergeSchemes } from '@core/schema-utils'; import { addCondition, mergeSchemes } from '@core/schema-utils';
import L, {Projection} from "leaflet"; import L from 'leaflet';
export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) { export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) {
const providerSchema = _.cloneDeep(mapProviderSchema); const providerSchema = _.cloneDeep(mapProviderSchema);
@ -318,22 +319,24 @@ export const parseWithTranslation = {
}; };
export function parseData(input: DatasourceData[]): FormattedData[] { export function parseData(input: DatasourceData[]): FormattedData[] {
return _(input).groupBy(el => el?.datasource?.entityName) return _(input).groupBy(el => el?.datasource.entityId + el?.datasource.entityType)
.values().value().map((entityArray, i) => { .values().value().map((entityArray, i) => {
const obj: FormattedData = { const obj: FormattedData = {
entityName: entityArray[0]?.datasource?.entityName, entityName: entityArray[0]?.datasource?.entityName,
entityId: entityArray[0]?.datasource?.entityId, entityId: entityArray[0].datasource.entityId,
entityType: entityArray[0]?.datasource?.entityType, entityType: entityArray[0].datasource.entityType,
$datasource: entityArray[0]?.datasource, $datasource: entityArray[0].datasource,
dsIndex: i, dsIndex: i,
deviceType: null deviceType: null
}; };
entityArray.filter(el => el.data.length).forEach(el => { entityArray.filter(el => el.data.length).forEach(el => {
const indexDate = el?.data?.length ? el.data.length - 1 : 0; const indexDate = el.data.length ? el.data.length - 1 : 0;
obj[el?.dataKey?.label] = el?.data[indexDate][1]; if (!obj.hasOwnProperty(el.dataKey.label) || el.data[indexDate][1] !== '') {
obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0]; obj[el.dataKey.label] = el.data[indexDate][1];
if (el?.dataKey?.label === 'type') { obj[el.dataKey.label + '|ts'] = el.data[indexDate][0];
obj.deviceType = el?.data[indexDate][1]; if (el.dataKey.label === 'type') {
obj.deviceType = el.data[indexDate][1];
}
} }
}); });
return obj; return obj;

15
ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts

@ -121,7 +121,7 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
this.switchResize$ = new ResizeObserver(() => { this.switchResize$ = new ResizeObserver(() => {
this.resize(); this.resize();
}) });
this.switchResize$.observe(this.switchContainerRef.nativeElement); this.switchResize$.observe(this.switchContainerRef.nativeElement);
this.init(); this.init();
} }
@ -202,13 +202,13 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
let width = this.switchContainer.width(); let width = this.switchContainer.width();
let height = this.switchContainer.height(); let height = this.switchContainer.height();
if (this.showOnOffLabels) { if (this.showOnOffLabels) {
height = height*2/3; height = height * 2 / 3;
} }
const ratio = width/height; const ratio = width / height;
if (ratio > switchAspectRation) { if (ratio > switchAspectRation) {
width = height*switchAspectRation; width = height * switchAspectRation;
} else { } else {
height = width/switchAspectRation; height = width / switchAspectRation;
} }
this.switchElement.css({width, height}); this.switchElement.css({width, height});
this.matSlideToggle.css({width, height, minWidth: width}); this.matSlideToggle.css({width, height, minWidth: width});
@ -232,11 +232,11 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
fontSize--; fontSize--;
textWidth = this.measureTextWidth(text, fontSize); textWidth = this.measureTextWidth(text, fontSize);
} }
element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
} }
private measureTextWidth(text: string, fontSize: number): number { private measureTextWidth(text: string, fontSize: number): number {
this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'});
this.textMeasure.text(text); this.textMeasure.text(text);
return this.textMeasure.width(); return this.textMeasure.width();
} }
@ -260,6 +260,7 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy
this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe(
(responseBody) => { (responseBody) => {
this.setValue(this.parseValueFunction(responseBody)); this.setValue(this.parseValueFunction(responseBody));
this.ctx.detectChanges();
}, },
() => { () => {
const errorText = this.ctx.defaultSubscription.rpcErrorText; const errorText = this.ctx.defaultSubscription.rpcErrorText;

6
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -38,6 +38,7 @@ import { ImportExportService } from '@home/components/import-export/import-expor
import { NavigationCardsWidgetComponent } from '@home/components/widget/lib/navigation-cards-widget.component'; import { NavigationCardsWidgetComponent } from '@home/components/widget/lib/navigation-cards-widget.component';
import { NavigationCardWidgetComponent } from '@home/components/widget/lib/navigation-card-widget.component'; import { NavigationCardWidgetComponent } from '@home/components/widget/lib/navigation-card-widget.component';
import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-overview-widget.component'; import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-overview-widget.component';
import { JsonInputWidgetComponent } from '@home/components/widget/lib/json-input-widget.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -51,6 +52,7 @@ import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-
EdgesOverviewWidgetComponent, EdgesOverviewWidgetComponent,
DateRangeNavigatorWidgetComponent, DateRangeNavigatorWidgetComponent,
DateRangeNavigatorPanelComponent, DateRangeNavigatorPanelComponent,
JsonInputWidgetComponent,
MultipleInputWidgetComponent, MultipleInputWidgetComponent,
TripAnimationComponent, TripAnimationComponent,
PhotoCameraInputWidgetComponent, PhotoCameraInputWidgetComponent,
@ -72,6 +74,7 @@ import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-
EdgesOverviewWidgetComponent, EdgesOverviewWidgetComponent,
RpcWidgetsModule, RpcWidgetsModule,
DateRangeNavigatorWidgetComponent, DateRangeNavigatorWidgetComponent,
JsonInputWidgetComponent,
MultipleInputWidgetComponent, MultipleInputWidgetComponent,
TripAnimationComponent, TripAnimationComponent,
PhotoCameraInputWidgetComponent, PhotoCameraInputWidgetComponent,
@ -84,4 +87,5 @@ import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-
ImportExportService ImportExportService
] ]
}) })
export class WidgetComponentsModule { } export class WidgetComponentsModule {
}

3
ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw

@ -4413,7 +4413,8 @@
"showDashboardExport": false, "showDashboardExport": false,
"toolbarAlwaysOpen": true, "toolbarAlwaysOpen": true,
"titleColor": "rgba(0,0,0,0.870588)", "titleColor": "rgba(0,0,0,0.870588)",
"showFilters": false "showFilters": false,
"showDashboardLogo": false
} }
}, },
"name": "Api Usage" "name": "Api Usage"

6
ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts

@ -89,7 +89,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
this.config.entityTranslations = entityTypeTranslations.get(EntityType.ASSET); this.config.entityTranslations = entityTypeTranslations.get(EntityType.ASSET);
this.config.entityResources = entityTypeResources.get(EntityType.ASSET); this.config.entityResources = entityTypeResources.get(EntityType.ASSET);
this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', { assetName: asset.name }); this.config.deleteEntityTitle = asset => this.translate.instant('asset.delete-asset-title', {assetName: asset.name});
this.config.deleteEntityContent = () => this.translate.instant('asset.delete-asset-text'); this.config.deleteEntityContent = () => this.translate.instant('asset.delete-asset-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('asset.delete-assets-title', {count}); this.config.deleteEntitiesTitle = count => this.translate.instant('asset.delete-assets-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('asset.delete-assets-text'); this.config.deleteEntitiesContent = () => this.translate.instant('asset.delete-assets-text');
@ -104,7 +104,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
)); ));
}; };
this.config.onEntityAction = action => this.onAssetAction(action); this.config.onEntityAction = action => this.onAssetAction(action);
this.config.detailsReadonly = () => this.config.componentsData.assetScope === 'customer_user'; this.config.detailsReadonly = () => (this.config.componentsData.assetScope === 'customer_user' || this.config.componentsData.assetScope === 'edge_customer_user');
this.config.headerComponent = AssetTableHeaderComponent; this.config.headerComponent = AssetTableHeaderComponent;
@ -151,7 +151,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.assetScope); this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.assetScope);
this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.assetScope); this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.assetScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.assetScope); this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.assetScope);
this.config.addEnabled = this.config.componentsData.assetScope !== 'customer_user'; this.config.addEnabled = !(this.config.componentsData.assetScope === 'customer_user' || this.config.componentsData.assetScope === 'edge_customer_user');
this.config.entitiesDeleteEnabled = this.config.componentsData.assetScope === 'tenant'; this.config.entitiesDeleteEnabled = this.config.componentsData.assetScope === 'tenant';
this.config.deleteEnabled = () => this.config.componentsData.assetScope === 'tenant'; this.config.deleteEnabled = () => this.config.componentsData.assetScope === 'tenant';
return this.config; return this.config;

11
ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts

@ -93,7 +93,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.entityResources = entityTypeResources.get(EntityType.DASHBOARD); this.config.entityResources = entityTypeResources.get(EntityType.DASHBOARD);
this.config.deleteEntityTitle = dashboard => this.config.deleteEntityTitle = dashboard =>
this.translate.instant('dashboard.delete-dashboard-title', { dashboardTitle: dashboard.title }); this.translate.instant('dashboard.delete-dashboard-title', {dashboardTitle: dashboard.title});
this.config.deleteEntityContent = () => this.translate.instant('dashboard.delete-dashboard-text'); this.config.deleteEntityContent = () => this.translate.instant('dashboard.delete-dashboard-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('dashboard.delete-dashboards-title', {count}); this.config.deleteEntitiesTitle = count => this.translate.instant('dashboard.delete-dashboards-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text'); this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text');
@ -103,7 +103,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
return this.dashboardService.saveDashboard(dashboard as Dashboard); return this.dashboardService.saveDashboard(dashboard as Dashboard);
}; };
this.config.onEntityAction = action => this.onDashboardAction(action); this.config.onEntityAction = action => this.onDashboardAction(action);
this.config.detailsReadonly = () => this.config.componentsData.dashboardScope === 'customer_user'; this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' || this.config.componentsData.dashboardScope === 'edge_customer_user');
} }
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> { resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> {
@ -147,7 +147,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.dashboardScope); this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.dashboardScope);
this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.dashboardScope); this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.dashboardScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.dashboardScope); this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.dashboardScope);
this.config.addEnabled = this.config.componentsData.dashboardScope !== 'customer_user'; this.config.addEnabled = !(this.config.componentsData.dashboardScope === 'customer_user' || this.config.componentsData.dashboardScope === 'edge_customer_user');
this.config.entitiesDeleteEnabled = this.config.componentsData.dashboardScope === 'tenant'; this.config.entitiesDeleteEnabled = this.config.componentsData.dashboardScope === 'tenant';
this.config.deleteEnabled = () => this.config.componentsData.dashboardScope === 'tenant'; this.config.deleteEnabled = () => this.config.componentsData.dashboardScope === 'tenant';
return this.config; return this.config;
@ -363,8 +363,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.router.navigateByUrl(`customers/${this.config.componentsData.customerId}/dashboards/${dashboard.id.id}`); this.router.navigateByUrl(`customers/${this.config.componentsData.customerId}/dashboards/${dashboard.id.id}`);
} else if (this.config.componentsData.dashboardScope === 'edge') { } else if (this.config.componentsData.dashboardScope === 'edge') {
this.router.navigateByUrl(`edges/${this.config.componentsData.edgeId}/dashboards/${dashboard.id.id}`); this.router.navigateByUrl(`edges/${this.config.componentsData.edgeId}/dashboards/${dashboard.id.id}`);
} } else {
else {
this.router.navigateByUrl(`dashboards/${dashboard.id.id}`); this.router.navigateByUrl(`dashboards/${dashboard.id.id}`);
} }
} }
@ -421,7 +420,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
} }
}).afterClosed() }).afterClosed()
.subscribe(() => { .subscribe(() => {
this.config.table.updateData(); this.config.table.updateData();
}); });
} }
); );

58
ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts

@ -97,7 +97,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
this.config.addDialogStyle = {width: '600px'}; this.config.addDialogStyle = {width: '600px'};
this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', { deviceName: device.name }); this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', {deviceName: device.name});
this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text'); this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count}); this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('device.delete-devices-text'); this.config.deleteEntitiesContent = () => this.translate.instant('device.delete-devices-text');
@ -109,10 +109,10 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
this.broadcast.broadcast('deviceSaved'); this.broadcast.broadcast('deviceSaved');
}), }),
mergeMap((savedDevice) => this.deviceService.getDeviceInfo(savedDevice.id.id) mergeMap((savedDevice) => this.deviceService.getDeviceInfo(savedDevice.id.id)
)); ));
}; };
this.config.onEntityAction = action => this.onDeviceAction(action); this.config.onEntityAction = action => this.onDeviceAction(action);
this.config.detailsReadonly = () => this.config.componentsData.deviceScope === 'customer_user'; this.config.detailsReadonly = () => (this.config.componentsData.deviceScope === 'customer_user' || this.config.componentsData.deviceScope === 'edge_customer_user');
this.config.headerComponent = DeviceTableHeaderComponent; this.config.headerComponent = DeviceTableHeaderComponent;
@ -161,7 +161,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.deviceScope); this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.deviceScope);
this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.deviceScope); this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.deviceScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.deviceScope); this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.deviceScope);
this.config.addEnabled = this.config.componentsData.deviceScope !== 'customer_user'; this.config.addEnabled = !(this.config.componentsData.deviceScope === 'customer_user' || this.config.componentsData.deviceScope === 'edge_customer_user');
this.config.entitiesDeleteEnabled = this.config.componentsData.deviceScope === 'tenant'; this.config.entitiesDeleteEnabled = this.config.componentsData.deviceScope === 'tenant';
this.config.deleteEnabled = () => this.config.componentsData.deviceScope === 'tenant'; this.config.deleteEnabled = () => this.config.componentsData.deviceScope === 'tenant';
return this.config; return this.config;
@ -208,7 +208,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
this.config.entitiesFetchFunction = pageLink => this.config.entitiesFetchFunction = pageLink =>
this.deviceService.getCustomerDeviceInfosByDeviceProfileId(this.customerId, pageLink, this.deviceService.getCustomerDeviceInfosByDeviceProfileId(this.customerId, pageLink,
this.config.componentsData.deviceProfileId !== null ? this.config.componentsData.deviceProfileId !== null ?
this.config.componentsData.deviceProfileId.id : ''); this.config.componentsData.deviceProfileId.id : '');
this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id);
} }
} }
@ -250,26 +250,26 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
); );
} }
if (deviceScope === 'customer') { if (deviceScope === 'customer') {
actions.push( actions.push(
{ {
name: this.translate.instant('device.unassign-from-customer'), name: this.translate.instant('device.unassign-from-customer'),
icon: 'assignment_return', icon: 'assignment_return',
isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic), isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && !entity.customerIsPublic),
onAction: ($event, entity) => this.unassignFromCustomer($event, entity) onAction: ($event, entity) => this.unassignFromCustomer($event, entity)
}, },
{ {
name: this.translate.instant('device.make-private'), name: this.translate.instant('device.make-private'),
icon: 'reply', icon: 'reply',
isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic), isEnabled: (entity) => (entity.customerId && entity.customerId.id !== NULL_UUID && entity.customerIsPublic),
onAction: ($event, entity) => this.unassignFromCustomer($event, entity) onAction: ($event, entity) => this.unassignFromCustomer($event, entity)
}, },
{ {
name: this.translate.instant('device.manage-credentials'), name: this.translate.instant('device.manage-credentials'),
icon: 'security', icon: 'security',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event, entity) => this.manageCredentials($event, entity) onAction: ($event, entity) => this.manageCredentials($event, entity)
} }
); );
} }
if (deviceScope === 'customer_user' || deviceScope === 'edge_customer_user') { if (deviceScope === 'customer_user' || deviceScope === 'edge_customer_user') {
actions.push( actions.push(
@ -452,10 +452,10 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
} }
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.table.updateData();
} }
}); });
} }
unassignFromCustomer($event: Event, device: DeviceInfo) { unassignFromCustomer($event: Event, device: DeviceInfo) {

71
ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts

@ -33,8 +33,11 @@ import { RuleChainType } from '@shared/models/rule-chain.models';
import { import {
importRuleChainBreadcumbLabelFunction, importRuleChainBreadcumbLabelFunction,
ResolvedRuleChainMetaDataResolver, ResolvedRuleChainMetaDataResolver,
ruleChainBreadcumbLabelFunction, RuleChainImportGuard, ruleChainBreadcumbLabelFunction,
RuleChainResolver, RuleNodeComponentsResolver, TooltipsterResolver RuleChainImportGuard,
RuleChainResolver,
RuleNodeComponentsResolver,
TooltipsterResolver
} from '@home/pages/rulechain/rulechain-routing.module'; } from '@home/pages/rulechain/rulechain-routing.module';
const routes: Routes = [ const routes: Routes = [
@ -58,21 +61,6 @@ const routes: Routes = [
entitiesTableConfig: EdgesTableConfigResolver entitiesTableConfig: EdgesTableConfigResolver
} }
}, },
{
path: ':edgeId/ruleChains',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
ruleChainsType: 'edge',
breadcrumb: {
label: 'edge.edge-rulechains',
icon: 'settings_ethernet'
},
},
resolve: {
entitiesTableConfig: RuleChainsTableConfigResolver
}
},
{ {
path: ':edgeId/assets', path: ':edgeId/assets',
component: EntitiesTableComponent, component: EntitiesTableComponent,
@ -156,6 +144,50 @@ const routes: Routes = [
} }
] ]
}, },
{
path: ':edgeId/ruleChains',
data: {
breadcrumb: {
label: 'edge.edge-rulechains',
icon: 'settings_ethernet'
}
},
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'edge.rulechains',
ruleChainsType: 'edge'
},
resolve: {
entitiesTableConfig: RuleChainsTableConfigResolver
}
},
{
path: ':ruleChainId',
component: RuleChainPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: ruleChainBreadcumbLabelFunction,
icon: 'settings_ethernet'
} as BreadCrumbConfig<RuleChainPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'rulechain.edge-rulechain',
import: false,
ruleChainType: RuleChainType.EDGE
},
resolve: {
ruleChain: RuleChainResolver,
ruleChainMetaData: ResolvedRuleChainMetaDataResolver,
ruleNodeComponents: RuleNodeComponentsResolver,
tooltipster: TooltipsterResolver
}
}
]
},
{ {
path: 'ruleChains', path: 'ruleChains',
data: { data: {
@ -220,7 +252,7 @@ const routes: Routes = [
} }
] ]
} }
] ]
}]; }];
@NgModule({ @NgModule({
@ -230,4 +262,5 @@ const routes: Routes = [
EdgesTableConfigResolver EdgesTableConfigResolver
] ]
}) })
export class EdgeRoutingModule { } export class EdgeRoutingModule {
}

24
ui-ngx/src/app/modules/home/pages/edge/edge.component.html

@ -44,31 +44,31 @@
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openEdgeAssets')" (click)="onEntityAction($event, 'openEdgeAssets')"
[fxShow]="!isEdit"> [fxShow]="!isEdit && edgeScope !== 'customer'">
{{'edge.manage-edge-assets' | translate }} {{'edge.manage-edge-assets' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openEdgeDevices')" (click)="onEntityAction($event, 'openEdgeDevices')"
[fxShow]="!isEdit"> [fxShow]="!isEdit && edgeScope !== 'customer'">
{{'edge.manage-edge-devices' | translate }} {{'edge.manage-edge-devices' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openEdgeEntityViews')" (click)="onEntityAction($event, 'openEdgeEntityViews')"
[fxShow]="!isEdit"> [fxShow]="!isEdit && edgeScope !== 'customer'">
{{'edge.manage-edge-entity-views' | translate }} {{'edge.manage-edge-entity-views' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openEdgeDashboards')" (click)="onEntityAction($event, 'openEdgeDashboards')"
[fxShow]="!isEdit"> [fxShow]="!isEdit && edgeScope !== 'customer'">
{{'edge.manage-edge-dashboards' | translate }} {{'edge.manage-edge-dashboards' | translate }}
</button> </button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'openEdgeRuleChains')" (click)="onEntityAction($event, 'openEdgeRuleChains')"
[fxShow]="!isEdit && (edgeScope === 'tenant' || edgeScope === 'customer')"> [fxShow]="!isEdit && edgeScope === 'tenant'">
{{'edge.manage-edge-rulechains' | translate }} {{'edge.manage-edge-rulechains' | translate }}
</button> </button>
</div> </div>
@ -85,7 +85,7 @@
ngxClipboard ngxClipboard
(cbOnSuccess)="onEdgeInfoCopied('key')" (cbOnSuccess)="onEdgeInfoCopied('key')"
[cbContent]="entity?.routingKey" [cbContent]="entity?.routingKey"
[fxShow]="!isEdit && (edgeScope === 'tenant' || edgeScope === 'customer')"> [fxShow]="!isEdit && edgeScope !== 'customer_user'">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>edge.copy-edge-key</span> <span translate>edge.copy-edge-key</span>
</button> </button>
@ -93,7 +93,7 @@
ngxClipboard ngxClipboard
(cbOnSuccess)="onEdgeInfoCopied('secret')" (cbOnSuccess)="onEdgeInfoCopied('secret')"
[cbContent]="entity?.secret" [cbContent]="entity?.secret"
[fxShow]="!isEdit && (edgeScope === 'tenant' || edgeScope === 'customer')"> [fxShow]="!isEdit && edgeScope !== 'customer_user'">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>edge.copy-edge-secret</span> <span translate>edge.copy-edge-secret</span>
</button> </button>
@ -101,7 +101,7 @@
ngxClipboard ngxClipboard
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'syncEdge')" (click)="onEntityAction($event, 'syncEdge')"
[fxShow]="!isEdit && (edgeScope === 'tenant' || edgeScope === 'customer')"> [fxShow]="!isEdit && edgeScope !== 'customer_user'">
<mat-icon svgIcon="mdi:sync"></mat-icon> <mat-icon svgIcon="mdi:sync"></mat-icon>
<span translate>edge.sync</span> <span translate>edge.sync</span>
</button> </button>
@ -132,7 +132,7 @@
[required]="true" [required]="true"
[entityType]="entityType.EDGE"> [entityType]="entityType.EDGE">
</tb-entity-subtype-autocomplete> </tb-entity-subtype-autocomplete>
<div [fxShow]="(edgeScope === 'tenant' || edgeScope === 'customer')"> <div [fxShow]="edgeScope !== 'customer_user'">
<div class="tb-hint" [innerHTML]="'edge.edge-license-key-hint' | translate"></div> <div class="tb-hint" [innerHTML]="'edge.edge-license-key-hint' | translate"></div>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>edge.edge-license-key</mat-label> <mat-label translate>edge.edge-license-key</mat-label>
@ -142,7 +142,7 @@
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
<div [fxShow]="(edgeScope === 'tenant' || edgeScope === 'customer')"> <div [fxShow]="edgeScope !== 'customer_user'">
<div translate class="tb-hint">edge.cloud-endpoint-hint</div> <div translate class="tb-hint">edge.cloud-endpoint-hint</div>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>edge.cloud-endpoint</mat-label> <mat-label translate>edge.cloud-endpoint</mat-label>
@ -153,7 +153,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
</fieldset> </fieldset>
<div fxLayout="row" [fxShow]="(edgeScope === 'tenant' || edgeScope === 'customer')"> <div fxLayout="row" [fxShow]="edgeScope !== 'customer_user'">
<mat-form-field class="mat-block" fxFlex> <mat-form-field class="mat-block" fxFlex>
<mat-label translate>edge.edge-key</mat-label> <mat-label translate>edge.edge-key</mat-label>
<input matInput formControlName="routingKey"> <input matInput formControlName="routingKey">
@ -164,7 +164,7 @@
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
</button> </button>
</div> </div>
<div fxLayout="row" [fxShow]="(edgeScope === 'tenant' || edgeScope === 'customer')"> <div fxLayout="row" [fxShow]="edgeScope !== 'customer_user'">
<mat-form-field class="mat-block" fxFlex> <mat-form-field class="mat-block" fxFlex>
<mat-label translate>edge.edge-secret</mat-label> <mat-label translate>edge.edge-secret</mat-label>
<input matInput formControlName="secret"> <input matInput formControlName="secret">

4
ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts

@ -477,8 +477,8 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
$event.stopPropagation(); $event.stopPropagation();
} }
this.dialogService.confirm( this.dialogService.confirm(
this.translate.instant('edge.unassign-edge-title', {count: edges.length}), this.translate.instant('edge.unassign-edges-title', {count: edges.length}),
this.translate.instant('edge.unassign-edge-text'), this.translate.instant('edge.unassign-edges-text'),
this.translate.instant('action.no'), this.translate.instant('action.no'),
this.translate.instant('action.yes'), this.translate.instant('action.yes'),
true true

9
ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts

@ -89,7 +89,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
this.config.addDialogStyle = {maxWidth: '800px'}; this.config.addDialogStyle = {maxWidth: '800px'};
this.config.deleteEntityTitle = entityView => this.config.deleteEntityTitle = entityView =>
this.translate.instant('entity-view.delete-entity-view-title', { entityViewName: entityView.name }); this.translate.instant('entity-view.delete-entity-view-title', {entityViewName: entityView.name});
this.config.deleteEntityContent = () => this.translate.instant('entity-view.delete-entity-view-text'); this.config.deleteEntityContent = () => this.translate.instant('entity-view.delete-entity-view-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('entity-view.delete-entity-views-title', {count}); this.config.deleteEntitiesTitle = count => this.translate.instant('entity-view.delete-entity-views-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('entity-view.delete-entity-views-text'); this.config.deleteEntitiesContent = () => this.translate.instant('entity-view.delete-entity-views-text');
@ -104,7 +104,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
)); ));
}; };
this.config.onEntityAction = action => this.onEntityViewAction(action); this.config.onEntityAction = action => this.onEntityViewAction(action);
this.config.detailsReadonly = () => this.config.componentsData.entityViewScope === 'customer_user'; this.config.detailsReadonly = () => (this.config.componentsData.entityViewScope === 'customer_user' || this.config.componentsData.entityViewScope === 'edge_customer_user');
this.config.headerComponent = EntityViewTableHeaderComponent; this.config.headerComponent = EntityViewTableHeaderComponent;
@ -143,8 +143,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
this.edgeService.getEdge(this.config.componentsData.edgeId).subscribe( this.edgeService.getEdge(this.config.componentsData.edgeId).subscribe(
edge => this.config.tableTitle = edge.name + ': ' + this.translate.instant('entity-view.entity-views') edge => this.config.tableTitle = edge.name + ': ' + this.translate.instant('entity-view.entity-views')
); );
} } else {
else {
this.config.tableTitle = this.translate.instant('entity-view.entity-views'); this.config.tableTitle = this.translate.instant('entity-view.entity-views');
} }
this.config.columns = this.configureColumns(this.config.componentsData.entityViewScope); this.config.columns = this.configureColumns(this.config.componentsData.entityViewScope);
@ -152,7 +151,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.entityViewScope); this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.entityViewScope);
this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.entityViewScope); this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.entityViewScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.entityViewScope); this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.entityViewScope);
this.config.addEnabled = this.config.componentsData.entityViewScope !== 'customer_user'; this.config.addEnabled = !(this.config.componentsData.entityViewScope === 'customer_user' || this.config.componentsData.entityViewScope !== 'edge_customer_user');
this.config.entitiesDeleteEnabled = this.config.componentsData.entityViewScope === 'tenant'; this.config.entitiesDeleteEnabled = this.config.componentsData.entityViewScope === 'tenant';
this.config.deleteEnabled = () => this.config.componentsData.entityViewScope === 'tenant'; this.config.deleteEnabled = () => this.config.componentsData.entityViewScope === 'tenant';
return this.config; return this.config;

4
ui-ngx/src/app/modules/home/pages/profile/profile.component.html

@ -57,13 +57,13 @@
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>language.language</mat-label> <mat-label translate>language.language</mat-label>
<mat-select matInput formControlName="language"> <mat-select formControlName="language">
<mat-option *ngFor="let lang of languageList" [value]="lang"> <mat-option *ngFor="let lang of languageList" [value]="lang">
{{ lang ? ('language.locales.' + lang | translate) : ''}} {{ lang ? ('language.locales.' + lang | translate) : ''}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<section class="tb-home-dashboard" fxFlex fxLayout="column" fxLayout.gt-sm="row"> <section class="tb-home-dashboard" fxFlex fxLayout="column" fxLayout.gt-sm="row" *ngIf="!isSysAdmin()">
<tb-dashboard-autocomplete <tb-dashboard-autocomplete
fxFlex fxFlex
placeholder="{{ 'dashboard.home-dashboard' | translate }}" placeholder="{{ 'dashboard.home-dashboard' | translate }}"

9
ui-ngx/src/app/modules/home/pages/profile/profile.component.ts

@ -16,7 +16,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { UserService } from '@core/http/user.service'; import { UserService } from '@core/http/user.service';
import { User } from '@shared/models/user.model'; import { AuthUser, User } from '@shared/models/user.model';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -33,6 +33,7 @@ import { DialogService } from '@core/services/dialog.service';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
@Component({ @Component({
selector: 'tb-profile', selector: 'tb-profile',
@ -45,6 +46,7 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir
profile: FormGroup; profile: FormGroup;
user: User; user: User;
languageList = env.supportedLangs; languageList = env.supportedLangs;
private readonly authUser: AuthUser;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -55,6 +57,7 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir
public dialogService: DialogService, public dialogService: DialogService,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store);
this.authUser = getCurrentAuthUser(this.store);
} }
ngOnInit() { ngOnInit() {
@ -134,4 +137,8 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir
return this.profile; return this.profile;
} }
isSysAdmin(): boolean {
return this.authUser.authority === Authority.SYS_ADMIN;
}
} }

53
ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts

@ -24,7 +24,6 @@ import {
import { Resolve } from '@angular/router'; import { Resolve } from '@angular/router';
import { Resource, ResourceInfo, ResourceTypeTranslationMap } from '@shared/models/resource.models'; import { Resource, ResourceInfo, ResourceTypeTranslationMap } from '@shared/models/resource.models';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { Direction } from '@shared/models/page/sort-order';
import { NULL_UUID } from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -34,13 +33,14 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { ResourcesLibraryComponent } from '@home/pages/resource/resources-library.component'; import { ResourcesLibraryComponent } from '@home/pages/resource/resources-library.component';
import { Observable } from 'rxjs/internal/Observable'; import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data'; import { EntityAction } from '@home/models/entity/entity-component.models';
import { map } from 'rxjs/operators';
@Injectable() @Injectable()
export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableConfig<Resource>> { export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableConfig<Resource, PageLink, ResourceInfo>> {
private readonly config: EntityTableConfig<Resource> = new EntityTableConfig<Resource>(); private readonly config: EntityTableConfig<Resource, PageLink, ResourceInfo> = new EntityTableConfig<Resource, PageLink, ResourceInfo>();
private readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; private readonly resourceTypesTranslationMap = ResourceTypeTranslationMap;
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
@ -52,17 +52,16 @@ export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableC
this.config.entityComponent = ResourcesLibraryComponent; this.config.entityComponent = ResourcesLibraryComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.TB_RESOURCE); this.config.entityTranslations = entityTypeTranslations.get(EntityType.TB_RESOURCE);
this.config.entityResources = entityTypeResources.get(EntityType.TB_RESOURCE); this.config.entityResources = entityTypeResources.get(EntityType.TB_RESOURCE);
this.config.defaultSortOrder = {property: 'title', direction: Direction.ASC};
this.config.entityTitle = (resource) => resource ? this.config.entityTitle = (resource) => resource ?
resource.title : ''; resource.title : '';
this.config.columns.push( this.config.columns.push(
new DateEntityTableColumn<ResourceInfo>('createdTime', 'common.created-time', this.datePipe, '150px'), new DateEntityTableColumn<ResourceInfo>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<ResourceInfo>('title', 'widgets-bundle.title', '60%'), new EntityTableColumn<ResourceInfo>('title', 'resource.title', '60%'),
new EntityTableColumn<ResourceInfo>('resourceType', 'resource.resource-type', '40%', new EntityTableColumn<ResourceInfo>('resourceType', 'resource.resource-type', '40%',
entity => this.resourceTypesTranslationMap.get(entity.resourceType)), entity => this.resourceTypesTranslationMap.get(entity.resourceType)),
new EntityTableColumn<ResourceInfo>('tenantId', 'widgets-bundle.system', '60px', new EntityTableColumn<ResourceInfo>('tenantId', 'resource.system', '60px',
entity => { entity => {
return checkBoxCell(entity.tenantId.id === NULL_UUID); return checkBoxCell(entity.tenantId.id === NULL_UUID);
}), }),
@ -83,13 +82,34 @@ export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableC
this.config.deleteEntitiesTitle = count => this.translate.instant('resource.delete-resources-title', {count}); this.config.deleteEntitiesTitle = count => this.translate.instant('resource.delete-resources-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('resource.delete-resources-text'); this.config.deleteEntitiesContent = () => this.translate.instant('resource.delete-resources-text');
this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink) as Observable<PageData<Resource>>; this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink);
this.config.loadEntity = id => this.resourceService.getResource(id.id); this.config.loadEntity = id => this.resourceService.getResource(id.id);
this.config.saveEntity = resource => this.resourceService.saveResource(resource); this.config.saveEntity = resource => this.saveResource(resource);
this.config.deleteEntity = id => this.resourceService.deleteResource(id.id); this.config.deleteEntity = id => this.resourceService.deleteResource(id.id);
this.config.onEntityAction = action => this.onResourceAction(action);
}
saveResource(resource) {
if (Array.isArray(resource.data)) {
const resources = [];
resource.data.forEach((data, index) => {
resources.push({
resourceType: resource.resourceType,
data,
fileName: resource.fileName[index],
title: resource.title
});
});
return this.resourceService.saveResources(resources, {resendRequest: true}).pipe(
map((response) => response[0])
);
} else {
return this.resourceService.saveResource(resource);
}
} }
resolve(): EntityTableConfig<Resource> { resolve(): EntityTableConfig<Resource, PageLink, ResourceInfo> {
this.config.tableTitle = this.translate.instant('resource.resources-library'); this.config.tableTitle = this.translate.instant('resource.resources-library');
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);
this.config.deleteEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); this.config.deleteEnabled = (resource) => this.isResourceEditable(resource, authUser.authority);
@ -105,7 +125,16 @@ export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableC
this.resourceService.downloadResource(resource.id.id).subscribe(); this.resourceService.downloadResource(resource.id.id).subscribe();
} }
private isResourceEditable(resource: Resource, authority: Authority): boolean { onResourceAction(action: EntityAction<ResourceInfo>): boolean {
switch (action.action) {
case 'uploadResource':
this.exportResource(action.event, action.entity);
return true;
}
return false;
}
private isResourceEditable(resource: ResourceInfo, authority: Authority): boolean {
if (authority === Authority.TENANT_ADMIN) { if (authority === Authority.TENANT_ADMIN) {
return resource && resource.tenantId && resource.tenantId.id !== NULL_UUID; return resource && resource.tenantId && resource.tenantId.id !== NULL_UUID;
} else { } else {

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save