Showing
12 changed files
with
197 additions
and
13 deletions
... | ... | @@ -48,6 +48,7 @@ import org.thingsboard.server.dao.attributes.AttributesService; |
48 | 48 | import org.thingsboard.server.dao.audit.AuditLogService; |
49 | 49 | import org.thingsboard.server.dao.customer.CustomerService; |
50 | 50 | import org.thingsboard.server.dao.device.DeviceService; |
51 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
51 | 52 | import org.thingsboard.server.dao.event.EventService; |
52 | 53 | import org.thingsboard.server.dao.relation.RelationService; |
53 | 54 | import org.thingsboard.server.dao.rule.RuleChainService; |
... | ... | @@ -161,6 +162,10 @@ public class ActorSystemContext { |
161 | 162 | |
162 | 163 | @Autowired |
163 | 164 | @Getter |
165 | + private EntityViewService entityViewService; | |
166 | + | |
167 | + @Autowired | |
168 | + @Getter | |
164 | 169 | private TelemetrySubscriptionService tsSubService; |
165 | 170 | |
166 | 171 | @Autowired | ... | ... |
... | ... | @@ -41,6 +41,7 @@ import org.thingsboard.server.dao.asset.AssetService; |
41 | 41 | import org.thingsboard.server.dao.attributes.AttributesService; |
42 | 42 | import org.thingsboard.server.dao.customer.CustomerService; |
43 | 43 | import org.thingsboard.server.dao.device.DeviceService; |
44 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
44 | 45 | import org.thingsboard.server.dao.relation.RelationService; |
45 | 46 | import org.thingsboard.server.dao.rule.RuleChainService; |
46 | 47 | import org.thingsboard.server.dao.tenant.TenantService; |
... | ... | @@ -213,6 +214,11 @@ class DefaultTbContext implements TbContext { |
213 | 214 | } |
214 | 215 | |
215 | 216 | @Override |
217 | + public EntityViewService getEntityViewService() { | |
218 | + return mainCtx.getEntityViewService(); | |
219 | + } | |
220 | + | |
221 | + @Override | |
216 | 222 | public MailService getMailService() { |
217 | 223 | if (mainCtx.isAllowSystemMailService()) { |
218 | 224 | return mainCtx.getMailService(); | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.entityview; |
17 | 17 | |
18 | 18 | import com.datastax.driver.core.Statement; |
19 | 19 | import com.datastax.driver.core.querybuilder.Select; |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
20 | 21 | import lombok.extern.slf4j.Slf4j; |
21 | 22 | import org.springframework.stereotype.Component; |
22 | 23 | import org.thingsboard.server.common.data.EntitySubtype; |
... | ... | @@ -105,4 +106,10 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao<Entit |
105 | 106 | public List<EntityView> findEntityViewsByTenantIdAndCustomerIdAndEntityId(UUID tenantId, UUID customerId, UUID entityId, TextPageLink pageLink) { |
106 | 107 | return null; |
107 | 108 | } |
109 | + | |
110 | + @Override | |
111 | + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { | |
112 | + // TODO: implement this | |
113 | + return null; | |
114 | + } | |
108 | 115 | } | ... | ... |
... | ... | @@ -15,6 +15,8 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.dao.entityview; |
17 | 17 | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import org.thingsboard.server.common.data.Device; | |
18 | 20 | import org.thingsboard.server.common.data.EntityView; |
19 | 21 | import org.thingsboard.server.common.data.page.TextPageLink; |
20 | 22 | import org.thingsboard.server.dao.Dao; |
... | ... | @@ -91,4 +93,6 @@ public interface EntityViewDao extends Dao<EntityView> { |
91 | 93 | UUID customerId, |
92 | 94 | UUID entityId, |
93 | 95 | TextPageLink pageLink); |
96 | + | |
97 | + ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId); | |
94 | 98 | } | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.Device; |
20 | 20 | import org.thingsboard.server.common.data.EntitySubtype; |
21 | 21 | import org.thingsboard.server.common.data.EntityType; |
22 | 22 | import org.thingsboard.server.common.data.EntityView; |
23 | +import org.thingsboard.server.common.data.Tenant; | |
23 | 24 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
24 | 25 | import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; |
25 | 26 | import org.thingsboard.server.common.data.id.*; |
... | ... | @@ -65,4 +66,6 @@ public interface EntityViewService { |
65 | 66 | ListenableFuture<EntityView> findEntityViewByIdAsync(EntityViewId entityViewId); |
66 | 67 | |
67 | 68 | ListenableFuture<List<EntityView>> findEntityViewsByQuery(EntityViewSearchQuery query); |
69 | + | |
70 | + ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId); | |
68 | 71 | } | ... | ... |
... | ... | @@ -279,6 +279,14 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti |
279 | 279 | return entityViews; |
280 | 280 | } |
281 | 281 | |
282 | + @Override | |
283 | + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId) { | |
284 | + log.trace("Executing findEntityViewsByTenantIdAndEntityIdAsync, tenantId [{}], entityId [{}]", tenantId, entityId); | |
285 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | |
286 | + validateId(entityId.getId(), "Incorrect entityId" + entityId); | |
287 | + return entityViewDao.findEntityViewsByTenantIdAndEntityIdAsync(tenantId.getId(), entityId.getId()); | |
288 | + } | |
289 | + | |
282 | 290 | private DataValidator<EntityView> entityViewValidator = |
283 | 291 | new DataValidator<EntityView>() { |
284 | 292 | ... | ... |
... | ... | @@ -19,6 +19,7 @@ import org.springframework.data.domain.Pageable; |
19 | 19 | import org.springframework.data.jpa.repository.Query; |
20 | 20 | import org.springframework.data.repository.CrudRepository; |
21 | 21 | import org.springframework.data.repository.query.Param; |
22 | +import org.thingsboard.server.common.data.EntityView; | |
22 | 23 | import org.thingsboard.server.common.data.id.EntityId; |
23 | 24 | import org.thingsboard.server.dao.model.sql.EntityViewEntity; |
24 | 25 | import org.thingsboard.server.dao.util.SqlDao; |
... | ... | @@ -78,4 +79,6 @@ public interface EntityViewRepository extends CrudRepository<EntityViewEntity, S |
78 | 79 | List<String> entityViewsIds); |
79 | 80 | |
80 | 81 | List<EntityViewEntity> findAllByTenantIdAndIdIn(String tenantId, List<String> entityViewsIds); |
82 | + | |
83 | + List<EntityViewEntity> findAllByTenantIdAndEntityId(String tenantId, String entityId); | |
81 | 84 | } | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import org.springframework.stereotype.Component; |
23 | 23 | import org.thingsboard.server.common.data.EntitySubtype; |
24 | 24 | import org.thingsboard.server.common.data.EntityType; |
25 | 25 | import org.thingsboard.server.common.data.EntityView; |
26 | +import org.thingsboard.server.common.data.UUIDConverter; | |
26 | 27 | import org.thingsboard.server.common.data.id.EntityId; |
27 | 28 | import org.thingsboard.server.common.data.id.TenantId; |
28 | 29 | import org.thingsboard.server.common.data.page.TextPageLink; |
... | ... | @@ -40,6 +41,7 @@ import java.util.Optional; |
40 | 41 | import java.util.UUID; |
41 | 42 | |
42 | 43 | import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
44 | +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUIDs; | |
43 | 45 | import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; |
44 | 46 | |
45 | 47 | /** |
... | ... | @@ -121,4 +123,10 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity, |
121 | 123 | new PageRequest(0, pageLink.getLimit()) |
122 | 124 | )); |
123 | 125 | } |
126 | + | |
127 | + @Override | |
128 | + public ListenableFuture<List<EntityView>> findEntityViewsByTenantIdAndEntityIdAsync(UUID tenantId, UUID entityId) { | |
129 | + return service.submit(() -> DaoUtil.convertDataList( | |
130 | + entityViewRepository.findAllByTenantIdAndEntityId(UUIDConverter.fromTimeUUID(tenantId), UUIDConverter.fromTimeUUID(entityId)))); | |
131 | + } | |
124 | 132 | } | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.asset.AssetService; |
26 | 26 | import org.thingsboard.server.dao.attributes.AttributesService; |
27 | 27 | import org.thingsboard.server.dao.customer.CustomerService; |
28 | 28 | import org.thingsboard.server.dao.device.DeviceService; |
29 | +import org.thingsboard.server.dao.entityview.EntityViewService; | |
29 | 30 | import org.thingsboard.server.dao.relation.RelationService; |
30 | 31 | import org.thingsboard.server.dao.rule.RuleChainService; |
31 | 32 | import org.thingsboard.server.dao.tenant.TenantService; |
... | ... | @@ -83,6 +84,8 @@ public interface TbContext { |
83 | 84 | |
84 | 85 | RelationService getRelationService(); |
85 | 86 | |
87 | + EntityViewService getEntityViewService(); | |
88 | + | |
86 | 89 | ListeningExecutor getJsExecutor(); |
87 | 90 | |
88 | 91 | ListeningExecutor getMailExecutor(); | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.rule.engine.action; | |
17 | + | |
18 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
19 | +import com.google.common.base.Function; | |
20 | +import com.google.common.util.concurrent.Futures; | |
21 | +import com.google.common.util.concurrent.ListenableFuture; | |
22 | +import com.google.gson.JsonParser; | |
23 | +import lombok.extern.slf4j.Slf4j; | |
24 | +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; | |
25 | +import org.thingsboard.rule.engine.api.RuleNode; | |
26 | +import org.thingsboard.rule.engine.api.TbContext; | |
27 | +import org.thingsboard.rule.engine.api.TbNode; | |
28 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | |
29 | +import org.thingsboard.rule.engine.api.TbNodeException; | |
30 | +import org.thingsboard.rule.engine.api.TbRelationTypes; | |
31 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | |
32 | +import org.thingsboard.server.common.data.DataConstants; | |
33 | +import org.thingsboard.server.common.data.EntityView; | |
34 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
35 | +import org.thingsboard.server.common.data.kv.TsKvEntry; | |
36 | +import org.thingsboard.server.common.data.plugin.ComponentType; | |
37 | +import org.thingsboard.server.common.msg.TbMsg; | |
38 | +import org.thingsboard.server.common.msg.session.SessionMsgType; | |
39 | +import org.thingsboard.server.common.transport.adaptor.JsonConverter; | |
40 | + | |
41 | +import javax.annotation.Nullable; | |
42 | +import java.util.ArrayList; | |
43 | +import java.util.List; | |
44 | +import java.util.Set; | |
45 | +import java.util.concurrent.ExecutionException; | |
46 | +import java.util.stream.Collectors; | |
47 | + | |
48 | +import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback; | |
49 | + | |
50 | +@Slf4j | |
51 | +@RuleNode( | |
52 | + type = ComponentType.ACTION, | |
53 | + name = "copy attributes", | |
54 | + configClazz = EmptyNodeConfiguration.class, | |
55 | + nodeDescription = "Copy attributes from asset/device to entity view", | |
56 | + nodeDetails = "Copy attributes from asset/device to related entity view according to entity view configuration. \n " + | |
57 | + "Copy will be done only for attributes that are between start and end dates and according to attribute keys configuration", | |
58 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
59 | + configDirective = "tbNodeEmptyConfig", | |
60 | + icon = "content_copy" | |
61 | +) | |
62 | +public class TbCopyAttributesToEntityViewNode implements TbNode { | |
63 | + | |
64 | + EmptyNodeConfiguration config; | |
65 | + | |
66 | + @Override | |
67 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | |
68 | + this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class); | |
69 | + } | |
70 | + | |
71 | + @Override | |
72 | + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { | |
73 | + if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) || | |
74 | + msg.getType().equals(DataConstants.ATTRIBUTES_DELETED) || | |
75 | + msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) { | |
76 | + long now = System.currentTimeMillis(); | |
77 | + String scope; | |
78 | + if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { | |
79 | + scope = DataConstants.CLIENT_SCOPE; | |
80 | + } else { | |
81 | + scope = msg.getMetaData().getValue("scope"); | |
82 | + } | |
83 | + ListenableFuture<List<EntityView>> entityViewsFuture = | |
84 | + ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator()); | |
85 | + withCallback(entityViewsFuture, | |
86 | + entityViews -> { | |
87 | + List<ListenableFuture<List<Void>>> saveFutures = new ArrayList<>(); | |
88 | + for (EntityView entityView : entityViews) { | |
89 | + if ((entityView.getEndTimeMs() != 0 && entityView.getEndTimeMs() > now && entityView.getStartTimeMs() < now) || | |
90 | + (entityView.getEndTimeMs() == 0 && entityView.getStartTimeMs() < now)) { | |
91 | + Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes(); | |
92 | + List<AttributeKvEntry> filteredAttributes = | |
93 | + attributes.stream() | |
94 | + .filter(attr -> { | |
95 | + switch (scope) { | |
96 | + case DataConstants.CLIENT_SCOPE: | |
97 | + if (entityView.getKeys().getAttributes().getCs().isEmpty()) { | |
98 | + return true; | |
99 | + } | |
100 | + return entityView.getKeys().getAttributes().getCs().contains(attr.getKey()); | |
101 | + case DataConstants.SERVER_SCOPE: | |
102 | + if (entityView.getKeys().getAttributes().getSs().isEmpty()) { | |
103 | + return true; | |
104 | + } | |
105 | + return entityView.getKeys().getAttributes().getSs().contains(attr.getKey()); | |
106 | + case DataConstants.SHARED_SCOPE: | |
107 | + if (entityView.getKeys().getAttributes().getSh().isEmpty()) { | |
108 | + return true; | |
109 | + } | |
110 | + return entityView.getKeys().getAttributes().getSh().contains(attr.getKey()); | |
111 | + } | |
112 | + return false; | |
113 | + }) | |
114 | + .collect(Collectors.toList()); | |
115 | + saveFutures.add(ctx.getAttributesService().save(entityView.getId(), scope, new ArrayList<>(filteredAttributes))); | |
116 | + } | |
117 | + } | |
118 | + Futures.transform(Futures.allAsList(saveFutures), new Function<List<List<Void>>, Object>() { | |
119 | + @Nullable | |
120 | + @Override | |
121 | + public Object apply(@Nullable List<List<Void>> lists) { | |
122 | + ctx.tellNext(msg, TbRelationTypes.SUCCESS); | |
123 | + return null; | |
124 | + } | |
125 | + }); | |
126 | + }, | |
127 | + t -> ctx.tellFailure(msg, t)); | |
128 | + } else { | |
129 | + ctx.tellNext(msg, TbRelationTypes.FAILURE); | |
130 | + } | |
131 | + } | |
132 | + | |
133 | + @Override | |
134 | + public void destroy() { | |
135 | + | |
136 | + } | |
137 | +} | ... | ... |
... | ... | @@ -98,19 +98,19 @@ |
98 | 98 | <section layout="column"> |
99 | 99 | <section layout="row" layout-align="start start"> |
100 | 100 | <mdp-date-picker ng-model="startTimeMs" |
101 | - mdp-max-date="maxStartTimeTs" | |
101 | + mdp-max-date="maxStartTimeMs" | |
102 | 102 | mdp-placeholder="{{ 'entity-view.start-ts' | translate }}"></mdp-date-picker> |
103 | 103 | <mdp-time-picker ng-model="startTimeMs" |
104 | - mdp-max-date="maxStartTimeTs" | |
104 | + mdp-max-date="maxStartTimeMs" | |
105 | 105 | mdp-placeholder="{{ 'entity-view.start-ts' | translate }}" |
106 | 106 | mdp-auto-switch="true"></mdp-time-picker> |
107 | 107 | </section> |
108 | 108 | <section layout="row" layout-align="start start"> |
109 | 109 | <mdp-date-picker ng-model="endTimeMs" |
110 | - mdp-min-date="minEndTimeTs" | |
110 | + mdp-min-date="minEndTimeMs" | |
111 | 111 | mdp-placeholder="{{ 'entity-view.end-ts' | translate }}"></mdp-date-picker> |
112 | 112 | <mdp-time-picker ng-model="endTimeMs" |
113 | - mdp-min-date="minEndTimeTs" | |
113 | + mdp-min-date="minEndTimeMs" | |
114 | 114 | mdp-placeholder="{{ 'entity-view.end-ts' | translate }}" |
115 | 115 | mdp-auto-switch="true"></mdp-time-picker> |
116 | 116 | </section> | ... | ... |
... | ... | @@ -54,8 +54,8 @@ export default function EntityViewDirective($compile, $templateCache, $filter, t |
54 | 54 | if (scope.entityView.startTimeMs > 0) { |
55 | 55 | scope.startTimeMs = new Date(scope.entityView.startTimeMs); |
56 | 56 | } |
57 | - if (scope.entityView.endTimeTs > 0) { | |
58 | - scope.endTimeTs = new Date(scope.entityView.endTimeTs); | |
57 | + if (scope.entityView.endTimeMs > 0) { | |
58 | + scope.endTimeMs = new Date(scope.entityView.endTimeMs); | |
59 | 59 | } |
60 | 60 | if (!scope.entityView.keys) { |
61 | 61 | scope.entityView.keys = {}; |
... | ... | @@ -78,22 +78,22 @@ export default function EntityViewDirective($compile, $templateCache, $filter, t |
78 | 78 | } |
79 | 79 | }); |
80 | 80 | |
81 | - scope.$watch('endTimeTs', function (newDate) { | |
81 | + scope.$watch('endTimeMs', function (newDate) { | |
82 | 82 | if (newDate) { |
83 | - if (newDate.getTime() < scope.minEndTimeTs) { | |
84 | - scope.endTimeTs = angular.copy(scope.minEndTimeTs); | |
83 | + if (newDate.getTime() < scope.minEndTimeMs) { | |
84 | + scope.endTimeMs = angular.copy(scope.minEndTimeMs); | |
85 | 85 | } |
86 | 86 | updateMinMaxDates(); |
87 | 87 | } |
88 | 88 | }); |
89 | 89 | |
90 | 90 | function updateMinMaxDates() { |
91 | - if (scope.endTimeTs) { | |
92 | - scope.maxStartTimeMs = angular.copy(new Date(scope.endTimeTs.getTime())); | |
93 | - scope.entityView.endTimeTs = scope.endTimeTs.getTime(); | |
91 | + if (scope.endTimeMs) { | |
92 | + scope.maxStartTimeMs = angular.copy(new Date(scope.endTimeMs.getTime())); | |
93 | + scope.entityView.endTimeMs = scope.endTimeMs.getTime(); | |
94 | 94 | } |
95 | 95 | if (scope.startTimeMs) { |
96 | - scope.minEndTimeTs = angular.copy(new Date(scope.startTimeMs.getTime())); | |
96 | + scope.minEndTimeMs = angular.copy(new Date(scope.startTimeMs.getTime())); | |
97 | 97 | scope.entityView.startTimeMs = scope.startTimeMs.getTime(); |
98 | 98 | } |
99 | 99 | } | ... | ... |