22 changed files with 750 additions and 33 deletions
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
|
||||
|
public interface HasCustomerId { |
||||
|
|
||||
|
CustomerId getCustomerId(); |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
public interface HasTenantId { |
||||
|
|
||||
|
TenantId getTenantId(); |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine; |
||||
|
|
||||
|
import com.google.common.util.concurrent.FutureCallback; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
|
||||
|
import javax.annotation.Nullable; |
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
public class DonAsynchron { |
||||
|
|
||||
|
public static <T> void withCallback(ListenableFuture<T> future, Consumer<T> onSuccess, Consumer<Throwable> onFailure) { |
||||
|
Futures.addCallback(future, new FutureCallback<T>() { |
||||
|
@Override |
||||
|
public void onSuccess(@Nullable T result) { |
||||
|
try { |
||||
|
onSuccess.accept(result); |
||||
|
} catch (Throwable th) { |
||||
|
onFailure(th); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable t) { |
||||
|
onFailure.accept(t); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.metadata; |
||||
|
|
||||
|
import com.google.common.base.Function; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import org.thingsboard.rule.engine.TbNodeUtils; |
||||
|
import org.thingsboard.rule.engine.api.*; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.KvEntry; |
||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
||||
|
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; |
||||
|
|
||||
|
public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode { |
||||
|
|
||||
|
private TbGetEntityAttrNodeConfiguration config; |
||||
|
|
||||
|
@Override |
||||
|
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { |
||||
|
this.config = TbNodeUtils.convert(configuration, TbGetEntityAttrNodeConfiguration.class); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onMsg(TbContext ctx, TbMsg msg) { |
||||
|
try { |
||||
|
withCallback( |
||||
|
findEntityAsync(ctx, msg.getOriginator()), |
||||
|
entityId -> withCallback( |
||||
|
config.isTelemetry() ? getLatestTelemetry(ctx, entityId) : getAttributesAsync(ctx, entityId), |
||||
|
attributes -> putAttributesAndTell(ctx, msg, attributes), |
||||
|
t -> ctx.tellError(msg, t) |
||||
|
), |
||||
|
t -> ctx.tellError(msg, t)); |
||||
|
} catch (Throwable th) { |
||||
|
ctx.tellError(msg, th); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId) { |
||||
|
ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()); |
||||
|
return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, ? extends List<KvEntry>>) l -> |
||||
|
l.stream().map(i -> (KvEntry) i).collect(Collectors.toList())); |
||||
|
} |
||||
|
|
||||
|
private ListenableFuture<List<KvEntry>> getLatestTelemetry(TbContext ctx, EntityId entityId) { |
||||
|
ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(entityId, config.getAttrMapping().keySet()); |
||||
|
return Futures.transform(latest, (Function<? super List<TsKvEntry>, ? extends List<KvEntry>>) l -> |
||||
|
l.stream().map(i -> (KvEntry) i).collect(Collectors.toList())); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void putAttributesAndTell(TbContext ctx, TbMsg msg, List<KvEntry> attributes) { |
||||
|
attributes.forEach(r -> { |
||||
|
String attrName = config.getAttrMapping().get(r.getKey()); |
||||
|
msg.getMetaData().putValue(attrName, r.getValueAsString()); |
||||
|
}); |
||||
|
ctx.tellNext(msg); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void destroy() { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
protected abstract ListenableFuture<T> findEntityAsync(TbContext ctx, EntityId originator); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.metadata; |
||||
|
|
||||
|
import com.google.common.util.concurrent.AsyncFunction; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeException; |
||||
|
import org.thingsboard.server.common.data.HasCustomerId; |
||||
|
import org.thingsboard.server.common.data.id.*; |
||||
|
|
||||
|
public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> { |
||||
|
|
||||
|
@Override |
||||
|
protected ListenableFuture<CustomerId> findEntityAsync(TbContext ctx, EntityId originator) { |
||||
|
|
||||
|
switch (originator.getEntityType()) { |
||||
|
case CUSTOMER: |
||||
|
return Futures.immediateFuture((CustomerId) originator); |
||||
|
case USER: |
||||
|
return getCustomerAsync(ctx.getUserService().findUserByIdAsync((UserId) originator)); |
||||
|
case ASSET: |
||||
|
return getCustomerAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) originator)); |
||||
|
case DEVICE: |
||||
|
return getCustomerAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) originator)); |
||||
|
default: |
||||
|
return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) { |
||||
|
return Futures.transform(future, (AsyncFunction<HasCustomerId, CustomerId>) in -> { |
||||
|
return in != null ? Futures.immediateFuture(in.getCustomerId()) |
||||
|
: Futures.immediateFailedFuture(new IllegalStateException("Customer not found"));}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.metadata; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
@Data |
||||
|
public class TbGetEntityAttrNodeConfiguration { |
||||
|
|
||||
|
private Map<String, String> attrMapping; |
||||
|
private boolean isTelemetry = false; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.metadata; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
||||
|
|
||||
|
@Data |
||||
|
public class TbGetRelatedAttrNodeConfiguration { |
||||
|
|
||||
|
private String relationType; |
||||
|
private EntitySearchDirection direction; |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.metadata; |
||||
|
|
||||
|
import com.google.common.util.concurrent.AsyncFunction; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import org.apache.commons.collections.CollectionUtils; |
||||
|
import org.thingsboard.rule.engine.TbNodeUtils; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeException; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeState; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
||||
|
import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
||||
|
import org.thingsboard.server.dao.relation.RelationService; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import static org.thingsboard.server.common.data.relation.RelationTypeGroup.COMMON; |
||||
|
|
||||
|
public class TbGetRelatedAttributeNode extends TbEntityGetAttrNode<EntityId> { |
||||
|
|
||||
|
private TbGetRelatedAttrNodeConfiguration config; |
||||
|
|
||||
|
@Override |
||||
|
public void init(TbNodeConfiguration configuration, TbNodeState state) throws TbNodeException { |
||||
|
this.config = TbNodeUtils.convert(configuration, TbGetRelatedAttrNodeConfiguration.class); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected ListenableFuture<EntityId> findEntityAsync(TbContext ctx, EntityId originator) { |
||||
|
RelationService relationService = ctx.getRelationService(); |
||||
|
if (config.getDirection() == EntitySearchDirection.FROM) { |
||||
|
ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByFromAndTypeAsync(originator, config.getRelationType(), COMMON); |
||||
|
return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) |
||||
|
r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) |
||||
|
: Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); |
||||
|
} else if (config.getDirection() == EntitySearchDirection.TO) { |
||||
|
ListenableFuture<List<EntityRelation>> asyncRelation = relationService.findByToAndTypeAsync(originator, config.getRelationType(), COMMON); |
||||
|
return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) |
||||
|
r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) |
||||
|
: Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); |
||||
|
} |
||||
|
|
||||
|
return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,64 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.metadata; |
||||
|
|
||||
|
import com.google.common.util.concurrent.AsyncFunction; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeException; |
||||
|
import org.thingsboard.server.common.data.HasTenantId; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmId; |
||||
|
import org.thingsboard.server.common.data.id.*; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> { |
||||
|
|
||||
|
@Override |
||||
|
protected ListenableFuture<TenantId> findEntityAsync(TbContext ctx, EntityId originator) { |
||||
|
|
||||
|
switch (originator.getEntityType()) { |
||||
|
case TENANT: |
||||
|
return Futures.immediateFuture((TenantId) originator); |
||||
|
case CUSTOMER: |
||||
|
return getTenantAsync(ctx.getCustomerService().findCustomerByIdAsync((CustomerId) originator)); |
||||
|
case USER: |
||||
|
return getTenantAsync(ctx.getUserService().findUserByIdAsync((UserId) originator)); |
||||
|
case RULE: |
||||
|
return getTenantAsync(ctx.getRuleService().findRuleByIdAsync((RuleId) originator)); |
||||
|
case PLUGIN: |
||||
|
return getTenantAsync(ctx.getPluginService().findPluginByIdAsync((PluginId) originator)); |
||||
|
case ASSET: |
||||
|
return getTenantAsync(ctx.getAssetService().findAssetByIdAsync((AssetId) originator)); |
||||
|
case DEVICE: |
||||
|
return getTenantAsync(ctx.getDeviceService().findDeviceByIdAsync((DeviceId) originator)); |
||||
|
case ALARM: |
||||
|
return getTenantAsync(ctx.getAlarmService().findAlarmByIdAsync((AlarmId) originator)); |
||||
|
case RULE_CHAIN: |
||||
|
return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync((RuleChainId) originator)); |
||||
|
default: |
||||
|
return Futures.immediateFailedFuture(new TbNodeException("Unexpected originator EntityType " + originator)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private <T extends HasTenantId> ListenableFuture<TenantId> getTenantAsync(ListenableFuture<T> future) { |
||||
|
return Futures.transform(future, (AsyncFunction<HasTenantId, TenantId>) in -> { |
||||
|
return in != null ? Futures.immediateFuture(in.getTenantId()) |
||||
|
: Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,266 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.rule.engine.metadata; |
||||
|
|
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import com.google.common.collect.Lists; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.junit.runner.RunWith; |
||||
|
import org.mockito.ArgumentCaptor; |
||||
|
import org.mockito.Mock; |
||||
|
import org.mockito.runners.MockitoJUnitRunner; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeException; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.asset.Asset; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.UserId; |
||||
|
import org.thingsboard.server.common.data.kv.*; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsgMetaData; |
||||
|
import org.thingsboard.server.dao.asset.AssetService; |
||||
|
import org.thingsboard.server.dao.attributes.AttributesService; |
||||
|
import org.thingsboard.server.dao.device.DeviceService; |
||||
|
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
||||
|
import org.thingsboard.server.dao.user.UserService; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
import static org.junit.Assert.assertEquals; |
||||
|
import static org.junit.Assert.assertTrue; |
||||
|
import static org.mockito.Matchers.same; |
||||
|
import static org.mockito.Mockito.verify; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; |
||||
|
|
||||
|
@RunWith(MockitoJUnitRunner.class) |
||||
|
public class TbGetCustomerAttributeNodeTest { |
||||
|
|
||||
|
private TbGetCustomerAttributeNode node; |
||||
|
|
||||
|
@Mock |
||||
|
private TbContext ctx; |
||||
|
|
||||
|
@Mock |
||||
|
private AttributesService attributesService; |
||||
|
@Mock |
||||
|
private TimeseriesService timeseriesService; |
||||
|
@Mock |
||||
|
private UserService userService; |
||||
|
@Mock |
||||
|
private AssetService assetService; |
||||
|
@Mock |
||||
|
private DeviceService deviceService; |
||||
|
|
||||
|
private TbMsg msg; |
||||
|
|
||||
|
@Before |
||||
|
public void init() throws TbNodeException { |
||||
|
TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration(); |
||||
|
Map<String, String> attrMapping = new HashMap<>(); |
||||
|
attrMapping.putIfAbsent("temperature", "tempo"); |
||||
|
config.setAttrMapping(attrMapping); |
||||
|
config.setTelemetry(false); |
||||
|
ObjectMapper mapper = new ObjectMapper(); |
||||
|
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); |
||||
|
nodeConfiguration.setData(mapper.valueToTree(config)); |
||||
|
|
||||
|
node = new TbGetCustomerAttributeNode(); |
||||
|
node.init(nodeConfiguration, null); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void errorThrownIfCannotLoadAttributes() { |
||||
|
UserId userId = new UserId(UUIDs.timeBased()); |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
User user = new User(); |
||||
|
user.setCustomerId(customerId); |
||||
|
|
||||
|
msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); |
||||
|
|
||||
|
when(ctx.getUserService()).thenReturn(userService); |
||||
|
when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); |
||||
|
|
||||
|
when(ctx.getAttributesService()).thenReturn(attributesService); |
||||
|
when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature"))) |
||||
|
.thenThrow(new IllegalStateException("something wrong")); |
||||
|
|
||||
|
node.onMsg(ctx, msg); |
||||
|
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); |
||||
|
verify(ctx).tellError(same(msg), captor.capture()); |
||||
|
|
||||
|
Throwable value = captor.getValue(); |
||||
|
assertEquals("something wrong", value.getMessage()); |
||||
|
assertTrue(msg.getMetaData().getData().isEmpty()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void errorThrownIfCannotLoadAttributesAsync() { |
||||
|
UserId userId = new UserId(UUIDs.timeBased()); |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
User user = new User(); |
||||
|
user.setCustomerId(customerId); |
||||
|
|
||||
|
msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); |
||||
|
|
||||
|
when(ctx.getUserService()).thenReturn(userService); |
||||
|
when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); |
||||
|
|
||||
|
when(ctx.getAttributesService()).thenReturn(attributesService); |
||||
|
when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature"))) |
||||
|
.thenReturn(Futures.immediateFailedFuture(new IllegalStateException("something wrong"))); |
||||
|
|
||||
|
node.onMsg(ctx, msg); |
||||
|
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); |
||||
|
verify(ctx).tellError(same(msg), captor.capture()); |
||||
|
|
||||
|
Throwable value = captor.getValue(); |
||||
|
assertEquals("something wrong", value.getMessage()); |
||||
|
assertTrue(msg.getMetaData().getData().isEmpty()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void errorThrownIfCustomerCannotBeFound() { |
||||
|
UserId userId = new UserId(UUIDs.timeBased()); |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
User user = new User(); |
||||
|
user.setCustomerId(customerId); |
||||
|
|
||||
|
msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); |
||||
|
|
||||
|
when(ctx.getUserService()).thenReturn(userService); |
||||
|
when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null)); |
||||
|
|
||||
|
node.onMsg(ctx, msg); |
||||
|
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); |
||||
|
verify(ctx).tellError(same(msg), captor.capture()); |
||||
|
|
||||
|
Throwable value = captor.getValue(); |
||||
|
assertEquals(IllegalStateException.class, value.getClass()); |
||||
|
assertEquals("Customer not found", value.getMessage()); |
||||
|
assertTrue(msg.getMetaData().getData().isEmpty()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void customerAttributeAddedInMetadata() { |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), new byte[4]); |
||||
|
entityAttributeFetched(customerId); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void usersCustomerAttributesFetched() { |
||||
|
UserId userId = new UserId(UUIDs.timeBased()); |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
User user = new User(); |
||||
|
user.setCustomerId(customerId); |
||||
|
|
||||
|
msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), new byte[4]); |
||||
|
|
||||
|
when(ctx.getUserService()).thenReturn(userService); |
||||
|
when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); |
||||
|
|
||||
|
entityAttributeFetched(customerId); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void assetsCustomerAttributesFetched() { |
||||
|
AssetId assetId = new AssetId(UUIDs.timeBased()); |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
Asset asset = new Asset(); |
||||
|
asset.setCustomerId(customerId); |
||||
|
|
||||
|
msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), new byte[4]); |
||||
|
|
||||
|
when(ctx.getAssetService()).thenReturn(assetService); |
||||
|
when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); |
||||
|
|
||||
|
entityAttributeFetched(customerId); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void deviceCustomerAttributesFetched() { |
||||
|
DeviceId deviceId = new DeviceId(UUIDs.timeBased()); |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
Device device = new Device(); |
||||
|
device.setCustomerId(customerId); |
||||
|
|
||||
|
msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]); |
||||
|
|
||||
|
when(ctx.getDeviceService()).thenReturn(deviceService); |
||||
|
when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); |
||||
|
|
||||
|
entityAttributeFetched(customerId); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void deviceCustomerTelemetryFetched() throws TbNodeException { |
||||
|
TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration(); |
||||
|
Map<String, String> attrMapping = new HashMap<>(); |
||||
|
attrMapping.putIfAbsent("temperature", "tempo"); |
||||
|
config.setAttrMapping(attrMapping); |
||||
|
config.setTelemetry(true); |
||||
|
ObjectMapper mapper = new ObjectMapper(); |
||||
|
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(); |
||||
|
nodeConfiguration.setData(mapper.valueToTree(config)); |
||||
|
|
||||
|
node = new TbGetCustomerAttributeNode(); |
||||
|
node.init(nodeConfiguration, null); |
||||
|
|
||||
|
|
||||
|
DeviceId deviceId = new DeviceId(UUIDs.timeBased()); |
||||
|
CustomerId customerId = new CustomerId(UUIDs.timeBased()); |
||||
|
Device device = new Device(); |
||||
|
device.setCustomerId(customerId); |
||||
|
|
||||
|
msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), new byte[4]); |
||||
|
|
||||
|
when(ctx.getDeviceService()).thenReturn(deviceService); |
||||
|
when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); |
||||
|
|
||||
|
List<TsKvEntry> timeseries = Lists.newArrayList(new BasicTsKvEntry(1L, new StringDataEntry("temperature", "highest"))); |
||||
|
|
||||
|
when(ctx.getTimeseriesService()).thenReturn(timeseriesService); |
||||
|
when(timeseriesService.findLatest(customerId, Collections.singleton("temperature"))) |
||||
|
.thenReturn(Futures.immediateFuture(timeseries)); |
||||
|
|
||||
|
node.onMsg(ctx, msg); |
||||
|
verify(ctx).tellNext(msg); |
||||
|
assertEquals(msg.getMetaData().getValue("tempo"), "highest"); |
||||
|
} |
||||
|
|
||||
|
private void entityAttributeFetched(CustomerId customerId) { |
||||
|
List<AttributeKvEntry> attributes = Lists.newArrayList(new BaseAttributeKvEntry(new StringDataEntry("temperature", "high"), 1L)); |
||||
|
|
||||
|
when(ctx.getAttributesService()).thenReturn(attributesService); |
||||
|
when(attributesService.find(customerId, SERVER_SCOPE, Collections.singleton("temperature"))) |
||||
|
.thenReturn(Futures.immediateFuture(attributes)); |
||||
|
|
||||
|
node.onMsg(ctx, msg); |
||||
|
verify(ctx).tellNext(msg); |
||||
|
assertEquals(msg.getMetaData().getValue("tempo"), "high"); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue