Merge branch 'master' into develop/2.3
This commit is contained in:
commit
755d0f56c1
@ -96,6 +96,19 @@ public class AlarmController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.DELETE)
|
||||
@ResponseBody
|
||||
public Boolean deleteAlarm(@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException {
|
||||
checkParameter(ALARM_ID, strAlarmId);
|
||||
try {
|
||||
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
|
||||
return alarmService.deleteAlarm(getTenantId(), alarmId);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
|
||||
@ -28,7 +28,7 @@ export LOADER_PATH=${BASE}/conf,${BASE}/extensions
|
||||
export SQL_DATA_FOLDER=${SQL_DATA_FOLDER:-/tmp}
|
||||
|
||||
|
||||
run_user=thingsboard
|
||||
run_user="$USER"
|
||||
|
||||
sudo -u "$run_user" -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
|
||||
-Dinstall.data_dir=${installDir} \
|
||||
|
||||
@ -31,6 +31,8 @@ import java.util.UUID;
|
||||
*/
|
||||
public interface AlarmDao extends Dao<Alarm> {
|
||||
|
||||
Boolean deleteAlarm(TenantId tenantId, Alarm alarm);
|
||||
|
||||
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
|
||||
|
||||
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, UUID key);
|
||||
|
||||
@ -35,6 +35,8 @@ public interface AlarmService {
|
||||
|
||||
Alarm createOrUpdateAlarm(Alarm alarm);
|
||||
|
||||
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
|
||||
|
||||
ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
|
||||
|
||||
ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long ackTs);
|
||||
|
||||
@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.Tenant;
|
||||
import org.thingsboard.server.common.data.alarm.Alarm;
|
||||
import org.thingsboard.server.common.data.alarm.AlarmId;
|
||||
@ -118,6 +119,21 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
|
||||
return alarmDao.findLatestByOriginatorAndType(tenantId, originator, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId) {
|
||||
try {
|
||||
log.debug("Deleting Alarm Id: {}", alarmId);
|
||||
Alarm alarm = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()).get();
|
||||
if (alarm == null) {
|
||||
return false;
|
||||
}
|
||||
deleteEntityRelations(tenantId, alarm.getId());
|
||||
return alarmDao.deleteAlarm(tenantId, alarm);
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Alarm createAlarm(Alarm alarm) throws InterruptedException, ExecutionException {
|
||||
log.debug("New Alarm : {}", alarm);
|
||||
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
|
||||
@ -127,9 +143,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
|
||||
|
||||
private void createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException {
|
||||
if (alarm.isPropagate()) {
|
||||
EntityRelationsQuery query = new EntityRelationsQuery();
|
||||
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
|
||||
List<EntityId> parentEntities = relationService.findByQuery(alarm.getTenantId(), query).get().stream().map(EntityRelation::getFrom).collect(Collectors.toList());
|
||||
List<EntityId> parentEntities = getParentEntities(alarm);
|
||||
for (EntityId parentId : parentEntities) {
|
||||
createAlarmRelation(alarm.getTenantId(), parentId, alarm.getId(), alarm.getStatus(), true);
|
||||
}
|
||||
@ -137,6 +151,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
|
||||
createAlarmRelation(alarm.getTenantId(), alarm.getOriginator(), alarm.getId(), alarm.getStatus(), true);
|
||||
}
|
||||
|
||||
private List<EntityId> getParentEntities(Alarm alarm) throws InterruptedException, ExecutionException {
|
||||
EntityRelationsQuery query = new EntityRelationsQuery();
|
||||
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
|
||||
return relationService.findByQuery(alarm.getTenantId(), query).get().stream().map(EntityRelation::getFrom).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ListenableFuture<Alarm> updateAlarm(Alarm update) {
|
||||
alarmDataValidator.validate(update, Alarm::getTenantId);
|
||||
return getAndUpdate(update.getTenantId(), update.getId(), new Function<Alarm, Alarm>() {
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.thingsboard.server.dao.alarm;
|
||||
|
||||
import com.datastax.driver.core.Statement;
|
||||
import com.datastax.driver.core.querybuilder.QueryBuilder;
|
||||
import com.datastax.driver.core.querybuilder.Select;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
@ -78,6 +79,17 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al
|
||||
return super.save(tenantId, alarm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteAlarm(TenantId tenantId, Alarm alarm) {
|
||||
Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, alarm.getId().getId()))
|
||||
.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId()))
|
||||
.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, alarm.getOriginator().getId()))
|
||||
.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, alarm.getOriginator().getEntityType()))
|
||||
.and(eq(ALARM_TYPE_PROPERTY, alarm.getType()));
|
||||
log.debug("Remove request: {}", delete.toString());
|
||||
return executeWrite(tenantId, delete).wasApplied();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
|
||||
Select select = select().from(ALARM_COLUMN_FAMILY_NAME);
|
||||
|
||||
@ -68,6 +68,11 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
|
||||
return alarmRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteAlarm(TenantId tenantId, Alarm alarm) {
|
||||
return removeById(tenantId, alarm.getUuidId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
|
||||
return service.submit(() -> {
|
||||
|
||||
@ -31,7 +31,9 @@ import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.page.TimePageData;
|
||||
import org.thingsboard.server.common.data.page.TimePageLink;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
|
||||
@ -184,4 +186,85 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
|
||||
Assert.assertEquals(1, alarms.getData().size());
|
||||
Assert.assertEquals(created, alarms.getData().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAlarm() throws ExecutionException, InterruptedException {
|
||||
AssetId parentId = new AssetId(UUIDs.timeBased());
|
||||
AssetId childId = new AssetId(UUIDs.timeBased());
|
||||
|
||||
EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
|
||||
|
||||
Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation).get());
|
||||
|
||||
long ts = System.currentTimeMillis();
|
||||
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
|
||||
.type(TEST_ALARM)
|
||||
.propagate(true)
|
||||
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
|
||||
.startTs(ts).build();
|
||||
|
||||
Alarm created = alarmService.createOrUpdateAlarm(alarm);
|
||||
|
||||
TimePageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
|
||||
.affectedEntityId(childId)
|
||||
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
|
||||
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
|
||||
).build()).get();
|
||||
Assert.assertNotNull(alarms.getData());
|
||||
Assert.assertEquals(1, alarms.getData().size());
|
||||
Assert.assertEquals(created, alarms.getData().get(0));
|
||||
|
||||
// Check parent relation
|
||||
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
|
||||
.affectedEntityId(parentId)
|
||||
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
|
||||
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
|
||||
).build()).get();
|
||||
Assert.assertNotNull(alarms.getData());
|
||||
Assert.assertEquals(1, alarms.getData().size());
|
||||
Assert.assertEquals(created, alarms.getData().get(0));
|
||||
|
||||
List<EntityRelation> toAlarmRelations = relationService.findByTo(tenantId, created.getId(), RelationTypeGroup.ALARM);
|
||||
Assert.assertEquals(8, toAlarmRelations.size());
|
||||
|
||||
List<EntityRelation> fromChildRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
|
||||
Assert.assertEquals(4, fromChildRelations.size());
|
||||
|
||||
List<EntityRelation> fromParentRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
|
||||
Assert.assertEquals(4, fromParentRelations.size());
|
||||
|
||||
|
||||
Assert.assertTrue("Alarm was not deleted when expected", alarmService.deleteAlarm(tenantId, created.getId()));
|
||||
|
||||
Alarm fetched = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
|
||||
|
||||
Assert.assertNull("Alarm was returned when it was expected to be null", fetched);
|
||||
|
||||
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
|
||||
.affectedEntityId(childId)
|
||||
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
|
||||
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
|
||||
).build()).get();
|
||||
Assert.assertNotNull(alarms.getData());
|
||||
Assert.assertEquals(0, alarms.getData().size());
|
||||
|
||||
// Check parent relation
|
||||
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
|
||||
.affectedEntityId(parentId)
|
||||
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
|
||||
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
|
||||
).build()).get();
|
||||
Assert.assertNotNull(alarms.getData());
|
||||
Assert.assertEquals(0, alarms.getData().size());
|
||||
|
||||
toAlarmRelations = relationService.findByTo(tenantId, created.getId(), RelationTypeGroup.ALARM);
|
||||
Assert.assertEquals(0, toAlarmRelations.size());
|
||||
|
||||
fromChildRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
|
||||
Assert.assertEquals(0, fromChildRelations.size());
|
||||
|
||||
fromParentRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
|
||||
Assert.assertEquals(0, fromParentRelations.size());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
6
pom.xml
6
pom.xml
@ -756,12 +756,6 @@
|
||||
<artifactId>hsqldb</artifactId>
|
||||
<version>${hsqldb.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ru.yandex.qatools.embed</groupId>
|
||||
<artifactId>postgresql-embedded</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
|
||||
@ -39,7 +39,9 @@ import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||
import org.thingsboard.server.common.data.page.TextPageData;
|
||||
import org.thingsboard.server.common.data.page.TextPageLink;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
import org.thingsboard.server.dao.customer.CustomerService;
|
||||
@ -47,6 +49,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
|
||||
import org.thingsboard.server.dao.device.DeviceService;
|
||||
import org.thingsboard.server.dao.entityview.EntityViewService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -58,10 +61,8 @@ import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback;
|
||||
public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationActionNodeConfiguration> implements TbNode {
|
||||
|
||||
protected C config;
|
||||
protected EntityId fromId;
|
||||
protected EntityId toId;
|
||||
|
||||
private LoadingCache<Entitykey, EntityContainer> entityIdCache;
|
||||
private LoadingCache<EntityKey, EntityContainer> entityIdCache;
|
||||
|
||||
@Override
|
||||
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
|
||||
@ -84,7 +85,7 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
|
||||
protected ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
|
||||
return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
|
||||
}
|
||||
|
||||
@ -96,40 +97,58 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
|
||||
|
||||
protected ListenableFuture<EntityContainer> getEntity(TbContext ctx, TbMsg msg) {
|
||||
String entityName = TbNodeUtils.processPattern(this.config.getEntityNamePattern(), msg.getMetaData());
|
||||
String type = null;
|
||||
String type;
|
||||
if (this.config.getEntityTypePattern() != null) {
|
||||
type = TbNodeUtils.processPattern(this.config.getEntityTypePattern(), msg.getMetaData());
|
||||
} else {
|
||||
type = null;
|
||||
}
|
||||
EntityType entityType = EntityType.valueOf(this.config.getEntityType());
|
||||
Entitykey key = new Entitykey(entityName, type, entityType);
|
||||
EntityKey key = new EntityKey(entityName, type, entityType);
|
||||
return ctx.getDbCallbackExecutor().executeAsync(() -> {
|
||||
EntityContainer entityContainer = entityIdCache.get(key);
|
||||
if (entityContainer.getEntityId() == null) {
|
||||
throw new RuntimeException("No entity found with type '" + key.getEntityType() + " ' and name '" + key.getEntityName() + "'.");
|
||||
throw new RuntimeException("No entity found with type '" + key.getEntityType() + "' and name '" + key.getEntityName() + "'.");
|
||||
}
|
||||
return entityContainer;
|
||||
});
|
||||
}
|
||||
|
||||
protected void processSearchDirection(TbMsg msg, EntityContainer entityContainer) {
|
||||
protected SearchDirectionIds processSingleSearchDirection(TbMsg msg, EntityContainer entityContainer) {
|
||||
SearchDirectionIds searchDirectionIds = new SearchDirectionIds();
|
||||
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
|
||||
fromId = EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString());
|
||||
toId = msg.getOriginator();
|
||||
searchDirectionIds.setFromId(EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString()));
|
||||
searchDirectionIds.setToId(msg.getOriginator());
|
||||
} else {
|
||||
toId = EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString());
|
||||
fromId = msg.getOriginator();
|
||||
searchDirectionIds.setToId(EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString()));
|
||||
searchDirectionIds.setFromId(msg.getOriginator());
|
||||
}
|
||||
return searchDirectionIds;
|
||||
}
|
||||
|
||||
protected ListenableFuture<List<EntityRelation>> processListSearchDirection(TbContext ctx, TbMsg msg) {
|
||||
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
|
||||
return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
|
||||
} else {
|
||||
return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
private static class Entitykey {
|
||||
private static class EntityKey {
|
||||
private String entityName;
|
||||
private String type;
|
||||
private EntityType entityType;
|
||||
}
|
||||
|
||||
private static class EntityCacheLoader extends CacheLoader<Entitykey, EntityContainer> {
|
||||
@Data
|
||||
protected static class SearchDirectionIds {
|
||||
private EntityId fromId;
|
||||
private EntityId toId;
|
||||
}
|
||||
|
||||
private static class EntityCacheLoader extends CacheLoader<EntityKey, EntityContainer> {
|
||||
|
||||
private final TbContext ctx;
|
||||
private final boolean createIfNotExists;
|
||||
@ -140,11 +159,11 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityContainer load(Entitykey key) {
|
||||
public EntityContainer load(EntityKey key) {
|
||||
return loadEntity(key);
|
||||
}
|
||||
|
||||
private EntityContainer loadEntity(Entitykey entitykey) {
|
||||
private EntityContainer loadEntity(EntityKey entitykey) {
|
||||
EntityType type = entitykey.getEntityType();
|
||||
EntityContainer targetEntity = new EntityContainer();
|
||||
targetEntity.setEntityType(type);
|
||||
|
||||
@ -54,7 +54,8 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfig
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
|
||||
ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
|
||||
String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg.getMetaData());
|
||||
ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
|
||||
return Futures.transformAsync(latest, a -> {
|
||||
if (a != null && !a.getStatus().isCleared()) {
|
||||
return clearAlarm(ctx, msg, a);
|
||||
|
||||
@ -65,92 +65,91 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateR
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
|
||||
processSearchDirection(msg, entityContainer);
|
||||
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON),
|
||||
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
|
||||
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON),
|
||||
result -> {
|
||||
if (!result) {
|
||||
return processCreateRelation(ctx, entityContainer);
|
||||
return processCreateRelation(ctx, entityContainer, sdId);
|
||||
}
|
||||
return Futures.immediateFuture(true);
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer) {
|
||||
private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
|
||||
switch (entityContainer.getEntityType()) {
|
||||
case ASSET:
|
||||
return processAsset(ctx, entityContainer);
|
||||
return processAsset(ctx, entityContainer, sdId);
|
||||
case DEVICE:
|
||||
return processDevice(ctx, entityContainer);
|
||||
return processDevice(ctx, entityContainer, sdId);
|
||||
case CUSTOMER:
|
||||
return processCustomer(ctx, entityContainer);
|
||||
return processCustomer(ctx, entityContainer, sdId);
|
||||
case DASHBOARD:
|
||||
return processDashboard(ctx, entityContainer);
|
||||
return processDashboard(ctx, entityContainer, sdId);
|
||||
case ENTITY_VIEW:
|
||||
return processView(ctx, entityContainer);
|
||||
return processView(ctx, entityContainer, sdId);
|
||||
case TENANT:
|
||||
return processTenant(ctx, entityContainer);
|
||||
return processTenant(ctx, entityContainer, sdId);
|
||||
}
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processView(TbContext ctx, EntityContainer entityContainer) {
|
||||
private ListenableFuture<Boolean> processView(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
|
||||
return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(entityContainer.getEntityId().getId())), entityView -> {
|
||||
if (entityView != null) {
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
} else {
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processDevice(TbContext ctx, EntityContainer entityContainer) {
|
||||
private ListenableFuture<Boolean> processDevice(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
|
||||
return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(entityContainer.getEntityId().getId())), device -> {
|
||||
if (device != null) {
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
} else {
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processAsset(TbContext ctx, EntityContainer entityContainer) {
|
||||
private ListenableFuture<Boolean> processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
|
||||
return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(entityContainer.getEntityId().getId())), asset -> {
|
||||
if (asset != null) {
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
} else {
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processCustomer(TbContext ctx, EntityContainer entityContainer) {
|
||||
private ListenableFuture<Boolean> processCustomer(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
|
||||
return Futures.transformAsync(ctx.getCustomerService().findCustomerByIdAsync(ctx.getTenantId(), new CustomerId(entityContainer.getEntityId().getId())), customer -> {
|
||||
if (customer != null) {
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
} else {
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processDashboard(TbContext ctx, EntityContainer entityContainer) {
|
||||
private ListenableFuture<Boolean> processDashboard(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
|
||||
return Futures.transformAsync(ctx.getDashboardService().findDashboardByIdAsync(ctx.getTenantId(), new DashboardId(entityContainer.getEntityId().getId())), dashboard -> {
|
||||
if (dashboard != null) {
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
} else {
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processTenant(TbContext ctx, EntityContainer entityContainer) {
|
||||
private ListenableFuture<Boolean> processTenant(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
|
||||
return Futures.transformAsync(ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), new TenantId(entityContainer.getEntityId().getId())), tenant -> {
|
||||
if (tenant != null) {
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
|
||||
} else {
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,18 +25,24 @@ import org.thingsboard.rule.engine.api.TbNodeException;
|
||||
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
||||
import org.thingsboard.rule.engine.util.EntityContainer;
|
||||
import org.thingsboard.server.common.data.plugin.ComponentType;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RuleNode(
|
||||
type = ComponentType.ACTION,
|
||||
name = "delete relation",
|
||||
configClazz = TbDeleteRelationNodeConfiguration.class,
|
||||
nodeDescription = "Finds target Entity by entity name pattern and then delete a relation to Originator Entity by type and direction.",
|
||||
nodeDetails = "If the relation successfully deleted - Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
|
||||
nodeDescription = "Finds target Entity by entity name pattern and then delete a relation to Originator Entity by type and direction" +
|
||||
" if 'Delete single entity' is set to true, otherwise rule node will delete all relations to the originator of the message by type and direction.",
|
||||
nodeDetails = "If the relation(s) successfully deleted - Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
|
||||
uiResources = {"static/rulenode/rulenode-core-config.js"},
|
||||
configDirective = "tbActionNodeDeleteRelationConfig",
|
||||
configDirective ="tbActionNodeDeleteRelationConfig",
|
||||
icon = "remove_circle"
|
||||
)
|
||||
public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteRelationNodeConfiguration> {
|
||||
@ -52,23 +58,54 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteR
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
|
||||
return deleteIfExist(ctx, msg, entityContainer);
|
||||
protected ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
|
||||
if(config.isDeleteForSingleEntity()){
|
||||
return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
|
||||
} else {
|
||||
return processList(ctx, msg);
|
||||
}
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> deleteIfExist(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
|
||||
processSearchDirection(msg, entityContainer);
|
||||
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON),
|
||||
@Override
|
||||
protected ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
|
||||
return processSingle(ctx, msg, entityContainer);
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processList(TbContext ctx, TbMsg msg) {
|
||||
return Futures.transformAsync(processListSearchDirection(ctx, msg), entityRelations -> {
|
||||
if(entityRelations.isEmpty()){
|
||||
return Futures.immediateFuture(true);
|
||||
} else {
|
||||
List<ListenableFuture<Boolean>> listenableFutureList = new ArrayList<>();
|
||||
for (EntityRelation entityRelation: entityRelations) {
|
||||
listenableFutureList.add(ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), entityRelation));
|
||||
}
|
||||
return Futures.transformAsync(Futures.allAsList(listenableFutureList), booleans -> {
|
||||
for (Boolean bool : booleans) {
|
||||
if (!bool) {
|
||||
return Futures.immediateFuture(false);
|
||||
}
|
||||
}
|
||||
return Futures.immediateFuture(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
|
||||
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
|
||||
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON),
|
||||
|
||||
result -> {
|
||||
if (result) {
|
||||
return processDeleteRelation(ctx);
|
||||
return processSingleDeleteRelation(ctx, sdId);
|
||||
}
|
||||
return Futures.immediateFuture(true);
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processDeleteRelation(TbContext ctx) {
|
||||
return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON);
|
||||
private ListenableFuture<Boolean> processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId) {
|
||||
return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -22,10 +22,12 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
@Data
|
||||
public class TbDeleteRelationNodeConfiguration extends TbAbstractRelationActionNodeConfiguration implements NodeConfiguration<TbDeleteRelationNodeConfiguration> {
|
||||
|
||||
private boolean deleteForSingleEntity;
|
||||
|
||||
@Override
|
||||
public TbDeleteRelationNodeConfiguration defaultConfiguration() {
|
||||
TbDeleteRelationNodeConfiguration configuration = new TbDeleteRelationNodeConfiguration();
|
||||
configuration.setDeleteForSingleEntity(true);
|
||||
configuration.setDirection(EntitySearchDirection.FROM.name());
|
||||
configuration.setRelationType("Contains");
|
||||
configuration.setEntityNamePattern("");
|
||||
|
||||
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.thingsboard.rule.engine.filter;
|
||||
|
||||
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.RuleNode;
|
||||
import org.thingsboard.rule.engine.api.TbContext;
|
||||
@ -25,13 +27,13 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.id.EntityIdFactory;
|
||||
import org.thingsboard.server.common.data.plugin.ComponentType;
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
|
||||
import javax.management.relation.RelationType;
|
||||
import java.util.List;
|
||||
|
||||
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
|
||||
import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback;
|
||||
|
||||
/**
|
||||
@ -43,8 +45,10 @@ import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback;
|
||||
name = "check relation",
|
||||
configClazz = TbCheckRelationNodeConfiguration.class,
|
||||
relationTypes = {"True", "False"},
|
||||
nodeDescription = "Checks the relation from the selected entity to originator of the message by type and direction",
|
||||
nodeDetails = "If relation exists - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
|
||||
nodeDescription = "Checks the relation from the selected entity to the originator of the message by type and direction" +
|
||||
" if 'Check for single entity' is set to true, otherwise rule node will check if exist" +
|
||||
" any relation to the originator of the message by type and direction.",
|
||||
nodeDetails = "If at least one relation exists - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
|
||||
uiResources = {"static/rulenode/rulenode-core-config.js"},
|
||||
configDirective = "tbFilterNodeCheckRelationConfig")
|
||||
public class TbCheckRelationNode implements TbNode {
|
||||
@ -58,6 +62,16 @@ public class TbCheckRelationNode implements TbNode {
|
||||
|
||||
@Override
|
||||
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
|
||||
ListenableFuture<Boolean> checkRelationFuture;
|
||||
if (config.isCheckForSingleEntity()) {
|
||||
checkRelationFuture = processSingle(ctx, msg);
|
||||
} else {
|
||||
checkRelationFuture = processList(ctx, msg);
|
||||
}
|
||||
withCallback(checkRelationFuture, filterResult -> ctx.tellNext(msg, filterResult ? "True" : "False"), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processSingle(TbContext ctx, TbMsg msg) {
|
||||
EntityId from;
|
||||
EntityId to;
|
||||
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
|
||||
@ -67,8 +81,25 @@ public class TbCheckRelationNode implements TbNode {
|
||||
to = EntityIdFactory.getByTypeAndId(config.getEntityType(), config.getEntityId());
|
||||
from = msg.getOriginator();
|
||||
}
|
||||
withCallback(ctx.getRelationService().checkRelation(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON),
|
||||
filterResult -> ctx.tellNext(msg, filterResult ? "True" : "False"), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
|
||||
return ctx.getRelationService().checkRelation(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON);
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> processList(TbContext ctx, TbMsg msg) {
|
||||
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
|
||||
return Futures.transformAsync(ctx.getRelationService()
|
||||
.findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList);
|
||||
} else {
|
||||
return Futures.transformAsync(ctx.getRelationService()
|
||||
.findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList);
|
||||
}
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> isEmptyList(List<EntityRelation> entityRelations) {
|
||||
if (entityRelations.isEmpty()) {
|
||||
return Futures.immediateFuture(false);
|
||||
} else {
|
||||
return Futures.immediateFuture(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -18,10 +18,6 @@ package org.thingsboard.rule.engine.filter;
|
||||
import lombok.Data;
|
||||
import org.thingsboard.rule.engine.api.NodeConfiguration;
|
||||
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
|
||||
import org.thingsboard.server.common.msg.session.SessionMsgType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by ashvayka on 19.01.18.
|
||||
@ -33,12 +29,14 @@ public class TbCheckRelationNodeConfiguration implements NodeConfiguration<TbChe
|
||||
private String entityId;
|
||||
private String entityType;
|
||||
private String relationType;
|
||||
private boolean checkForSingleEntity;
|
||||
|
||||
@Override
|
||||
public TbCheckRelationNodeConfiguration defaultConfiguration() {
|
||||
TbCheckRelationNodeConfiguration configuration = new TbCheckRelationNodeConfiguration();
|
||||
configuration.setDirection(EntitySearchDirection.FROM.name());
|
||||
configuration.setRelationType("Contains");
|
||||
configuration.setCheckForSingleEntity(true);
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -62,6 +62,7 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
|
||||
}
|
||||
|
||||
scope.subTypeSearchTextChanged = function() {
|
||||
scope.subType = scope.subTypeSearchText;
|
||||
}
|
||||
|
||||
scope.updateView = function () {
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
md-min-length="0"
|
||||
placeholder="{{ selectEntitySubtypeText | translate }}"
|
||||
md-floating-label="{{ entitySubtypeText | translate }}"
|
||||
md-select-on-match="true"
|
||||
md-select-on-match="false"
|
||||
md-menu-class="tb-entity-subtype-autocomplete">
|
||||
<md-item-template>
|
||||
<div class="tb-entity-subtype-item">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user