ZoneGroups from list to map

This commit is contained in:
dshvaika 2025-09-11 18:04:40 +03:00
parent b54906a9ef
commit ad0b6017e6
9 changed files with 53 additions and 104 deletions

View File

@ -42,7 +42,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY; import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
@ -124,10 +123,7 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState {
Coordinates entityCoordinates = new Coordinates(latitude, longitude); Coordinates entityCoordinates = new Coordinates(latitude, longitude);
var geofencingCfg = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration(); var geofencingCfg = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
Map<String, ZoneGroupConfiguration> zoneGroups = geofencingCfg Map<String, ZoneGroupConfiguration> zoneGroups = geofencingCfg.getZoneGroups();
.getZoneGroups()
.stream()
.collect(Collectors.toMap(ZoneGroupConfiguration::getName, Function.identity()));
ObjectNode resultNode = JacksonUtil.newObjectNode(); ObjectNode resultNode = JacksonUtil.newObjectNode();
List<ListenableFuture<Boolean>> relationFutures = new ArrayList<>(); List<ListenableFuture<Boolean>> relationFutures = new ArrayList<>();

View File

@ -51,7 +51,6 @@ import org.thingsboard.server.controller.CalculatedFieldControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -674,7 +673,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
cfg.setEntityCoordinates(entityCoordinates); cfg.setEntityCoordinates(entityCoordinates);
// Zone groups: ATTRIBUTE on specific assets (one zone per group) // Zone groups: ATTRIBUTE on specific assets (one zone per group)
ZoneGroupConfiguration allowedZonesGroup = new ZoneGroupConfiguration("allowedZones", "zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration allowedZonesGroup = new ZoneGroupConfiguration("zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var allowedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var allowedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
allowedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); allowedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
allowedZoneDynamicSourceConfiguration.setRelationType("AllowedZone"); allowedZoneDynamicSourceConfiguration.setRelationType("AllowedZone");
@ -682,7 +681,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
allowedZoneDynamicSourceConfiguration.setFetchLastLevelOnly(true); allowedZoneDynamicSourceConfiguration.setFetchLastLevelOnly(true);
allowedZonesGroup.setRefDynamicSourceConfiguration(allowedZoneDynamicSourceConfiguration); allowedZonesGroup.setRefDynamicSourceConfiguration(allowedZoneDynamicSourceConfiguration);
ZoneGroupConfiguration restrictedZonesGroup = new ZoneGroupConfiguration("restrictedZones", "zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration restrictedZonesGroup = new ZoneGroupConfiguration("zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var restrictedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var restrictedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
restrictedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); restrictedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
restrictedZoneDynamicSourceConfiguration.setRelationType("RestrictedZone"); restrictedZoneDynamicSourceConfiguration.setRelationType("RestrictedZone");
@ -690,7 +689,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
restrictedZoneDynamicSourceConfiguration.setFetchLastLevelOnly(true); restrictedZoneDynamicSourceConfiguration.setFetchLastLevelOnly(true);
restrictedZonesGroup.setRefDynamicSourceConfiguration(restrictedZoneDynamicSourceConfiguration); restrictedZonesGroup.setRefDynamicSourceConfiguration(restrictedZoneDynamicSourceConfiguration);
cfg.setZoneGroups(List.of(allowedZonesGroup, restrictedZonesGroup)); cfg.setZoneGroups(Map.of("allowedZones", allowedZonesGroup, "restrictedZones", restrictedZonesGroup));
// Output to server attributes // Output to server attributes
Output out = new Output(); Output out = new Output();
@ -783,14 +782,14 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
GeofencingCalculatedFieldConfiguration cfg = new GeofencingCalculatedFieldConfiguration(); GeofencingCalculatedFieldConfiguration cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setEntityCoordinates(new EntityCoordinates(ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY)); cfg.setEntityCoordinates(new EntityCoordinates(ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY));
var allowedZonesGroup = new ZoneGroupConfiguration("allowedZones", "zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); var allowedZonesGroup = new ZoneGroupConfiguration("zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var allowedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var allowedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
allowedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); allowedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
allowedZoneDynamicSourceConfiguration.setRelationType("AllowedZone"); allowedZoneDynamicSourceConfiguration.setRelationType("AllowedZone");
allowedZoneDynamicSourceConfiguration.setMaxLevel(1); allowedZoneDynamicSourceConfiguration.setMaxLevel(1);
allowedZoneDynamicSourceConfiguration.setFetchLastLevelOnly(true); allowedZoneDynamicSourceConfiguration.setFetchLastLevelOnly(true);
allowedZonesGroup.setRefDynamicSourceConfiguration(allowedZoneDynamicSourceConfiguration); allowedZonesGroup.setRefDynamicSourceConfiguration(allowedZoneDynamicSourceConfiguration);
cfg.setZoneGroups(List.of(allowedZonesGroup)); cfg.setZoneGroups(Map.of("allowedZones", allowedZonesGroup));
// Server attributes output // Server attributes output
Output out = new Output(); Output out = new Output();

View File

@ -452,7 +452,7 @@ public class GeofencingCalculatedFieldStateTest {
EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude"); EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
config.setEntityCoordinates(entityCoordinates); config.setEntityCoordinates(entityCoordinates);
ZoneGroupConfiguration allowedZonesGroup = new ZoneGroupConfiguration("allowedZones", "zone", reportStrategy, true); ZoneGroupConfiguration allowedZonesGroup = new ZoneGroupConfiguration("zone", reportStrategy, true);
var allowedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var allowedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
allowedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.TO); allowedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.TO);
allowedZoneDynamicSourceConfiguration.setRelationType("AllowedZone"); allowedZoneDynamicSourceConfiguration.setRelationType("AllowedZone");
@ -462,7 +462,7 @@ public class GeofencingCalculatedFieldStateTest {
allowedZonesGroup.setRelationType("CurrentZone"); allowedZonesGroup.setRelationType("CurrentZone");
allowedZonesGroup.setDirection(EntitySearchDirection.TO); allowedZonesGroup.setDirection(EntitySearchDirection.TO);
ZoneGroupConfiguration restrictedZonesGroup = new ZoneGroupConfiguration("restrictedZones", "zone", reportStrategy, true); ZoneGroupConfiguration restrictedZonesGroup = new ZoneGroupConfiguration("zone", reportStrategy, true);
var restrictedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var restrictedZoneDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
restrictedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.TO); restrictedZoneDynamicSourceConfiguration.setDirection(EntitySearchDirection.TO);
restrictedZoneDynamicSourceConfiguration.setRelationType("RestrictedZone"); restrictedZoneDynamicSourceConfiguration.setRelationType("RestrictedZone");
@ -472,7 +472,7 @@ public class GeofencingCalculatedFieldStateTest {
restrictedZonesGroup.setRelationType("CurrentZone"); restrictedZonesGroup.setRelationType("CurrentZone");
restrictedZonesGroup.setDirection(EntitySearchDirection.TO); restrictedZonesGroup.setDirection(EntitySearchDirection.TO);
config.setZoneGroups(List.of(allowedZonesGroup, restrictedZonesGroup)); config.setZoneGroups(Map.of("allowedZones", allowedZonesGroup, "restrictedZones", restrictedZonesGroup));
Output output = new Output(); Output output = new Output();
output.setType(OutputType.TIME_SERIES); output.setType(OutputType.TIME_SERIES);

View File

@ -25,17 +25,15 @@ import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSuppor
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
@Data @Data
public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduledUpdateSupportedCalculatedFieldConfiguration { public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduledUpdateSupportedCalculatedFieldConfiguration {
private EntityCoordinates entityCoordinates; private EntityCoordinates entityCoordinates;
private List<ZoneGroupConfiguration> zoneGroups; private Map<String, ZoneGroupConfiguration> zoneGroups;
private int scheduledUpdateInterval; private int scheduledUpdateInterval;
private Output output; private Output output;
@ -49,13 +47,13 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
@JsonIgnore @JsonIgnore
public Map<String, Argument> getArguments() { public Map<String, Argument> getArguments() {
Map<String, Argument> args = new HashMap<>(entityCoordinates.toArguments()); Map<String, Argument> args = new HashMap<>(entityCoordinates.toArguments());
zoneGroups.forEach(zg -> args.put(zg.getName(), zg.toArgument())); zoneGroups.forEach((zgName, zgConfig) -> args.put(zgName, zgConfig.toArgument()));
return args; return args;
} }
@Override @Override
public List<EntityId> getReferencedEntities() { public List<EntityId> getReferencedEntities() {
return zoneGroups.stream().map(ZoneGroupConfiguration::getRefEntityId).filter(Objects::nonNull).toList(); return zoneGroups.values().stream().map(ZoneGroupConfiguration::getRefEntityId).filter(Objects::nonNull).toList();
} }
@Override @Override
@ -65,7 +63,7 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
@Override @Override
public boolean isScheduledUpdateEnabled() { public boolean isScheduledUpdateEnabled() {
return scheduledUpdateInterval > 0 && zoneGroups.stream().anyMatch(ZoneGroupConfiguration::hasDynamicSource); return scheduledUpdateInterval > 0 && zoneGroups.values().stream().anyMatch(ZoneGroupConfiguration::hasDynamicSource);
} }
@Override @Override
@ -73,17 +71,11 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
if (entityCoordinates == null) { if (entityCoordinates == null) {
throw new IllegalArgumentException("Geofencing calculated field entity coordinates must be specified!"); throw new IllegalArgumentException("Geofencing calculated field entity coordinates must be specified!");
} }
entityCoordinates.validate();
if (zoneGroups == null || zoneGroups.isEmpty()) { if (zoneGroups == null || zoneGroups.isEmpty()) {
throw new IllegalArgumentException("Geofencing calculated field must contain at least one geofencing zone group defined!"); throw new IllegalArgumentException("Geofencing calculated field must contain at least one geofencing zone group defined!");
} }
entityCoordinates.validate(); zoneGroups.forEach((key, value) -> value.validate(key));
Set<String> seen = new HashSet<>();
for (var zg : zoneGroups) {
if (!seen.add(zg.getName())) {
throw new IllegalArgumentException("Geofencing calculated field zone group name must be unique!");
}
zg.validate();
}
} }
} }

View File

@ -35,7 +35,6 @@ public class ZoneGroupConfiguration {
private EntityId refEntityId; private EntityId refEntityId;
private CfArgumentDynamicSourceConfiguration refDynamicSourceConfiguration; private CfArgumentDynamicSourceConfiguration refDynamicSourceConfiguration;
private final String name;
private final String perimeterKeyName; private final String perimeterKeyName;
private final GeofencingReportStrategy reportStrategy; private final GeofencingReportStrategy reportStrategy;
@ -44,10 +43,7 @@ public class ZoneGroupConfiguration {
private String relationType; private String relationType;
private EntitySearchDirection direction; private EntitySearchDirection direction;
public void validate() { public void validate(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("Zone group name must be specified!");
}
if (EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY.equals(name) || EntityCoordinates.ENTITY_ID_LONGITUDE_ARGUMENT_KEY.equals(name)) { if (EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY.equals(name) || EntityCoordinates.ENTITY_ID_LONGITUDE_ARGUMENT_KEY.equals(name)) {
throw new IllegalArgumentException("Name '" + name + "' is reserved and cannot be used for zone group!"); throw new IllegalArgumentException("Name '" + name + "' is reserved and cannot be used for zone group!");
} }

View File

@ -73,12 +73,12 @@ public class GeofencingCalculatedFieldConfigurationTest {
EntityCoordinates entityCoordinatesMock = mock(EntityCoordinates.class); EntityCoordinates entityCoordinatesMock = mock(EntityCoordinates.class);
cfg.setEntityCoordinates(entityCoordinatesMock); cfg.setEntityCoordinates(entityCoordinatesMock);
var zoneGroupConfiguration = mock(ZoneGroupConfiguration.class); var zoneGroupConfiguration = mock(ZoneGroupConfiguration.class);
cfg.setZoneGroups(List.of(zoneGroupConfiguration)); cfg.setZoneGroups(Map.of("someGroupName", zoneGroupConfiguration));
cfg.validate(); cfg.validate();
verify(entityCoordinatesMock).validate(); verify(entityCoordinatesMock).validate();
verify(zoneGroupConfiguration).validate(); verify(zoneGroupConfiguration).validate("someGroupName");
} }
@Test @Test
@ -89,38 +89,16 @@ public class GeofencingCalculatedFieldConfigurationTest {
var zoneGroupConfigurationA = mock(ZoneGroupConfiguration.class); var zoneGroupConfigurationA = mock(ZoneGroupConfiguration.class);
var zoneGroupConfigurationB = mock(ZoneGroupConfiguration.class); var zoneGroupConfigurationB = mock(ZoneGroupConfiguration.class);
when(zoneGroupConfigurationA.getName()).thenReturn("zoneGroupA"); String zoneGroupAName = "zoneGroupA";
when(zoneGroupConfigurationB.getName()).thenReturn("zoneGroupB"); String zoneGroupBName = "zoneGroupB";
cfg.setZoneGroups(List.of(zoneGroupConfigurationA, zoneGroupConfigurationB)); cfg.setZoneGroups(Map.of("zoneGroupA", zoneGroupConfigurationA, "zoneGroupB", zoneGroupConfigurationB));
assertThatCode(cfg::validate).doesNotThrowAnyException(); assertThatCode(cfg::validate).doesNotThrowAnyException();
verify(entityCoordinatesMock).validate(); verify(entityCoordinatesMock).validate();
verify(zoneGroupConfigurationA).validate(); verify(zoneGroupConfigurationA).validate(zoneGroupAName);
verify(zoneGroupConfigurationB).validate(); verify(zoneGroupConfigurationB).validate(zoneGroupBName);
}
@Test
void validateShouldThrowWhenZoneGroupNamesDuplicated() {
var cfg = new GeofencingCalculatedFieldConfiguration();
EntityCoordinates entityCoordinatesMock = mock(EntityCoordinates.class);
cfg.setEntityCoordinates(entityCoordinatesMock);
var zoneGroupConfigurationA = mock(ZoneGroupConfiguration.class);
var zoneGroupConfigurationB = mock(ZoneGroupConfiguration.class);
when(zoneGroupConfigurationA.getName()).thenReturn("zoneGroupDuplicated");
when(zoneGroupConfigurationB.getName()).thenReturn("zoneGroupDuplicated");
cfg.setZoneGroups(List.of(zoneGroupConfigurationA, zoneGroupConfigurationB));
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Geofencing calculated field zone group name must be unique!");
verify(entityCoordinatesMock).validate();
verify(zoneGroupConfigurationA).validate();
verify(zoneGroupConfigurationB, never()).validate();
} }
@Test @Test
@ -135,7 +113,7 @@ public class GeofencingCalculatedFieldConfigurationTest {
var cfg = new GeofencingCalculatedFieldConfiguration(); var cfg = new GeofencingCalculatedFieldConfiguration();
var zoneGroupConfigurationMock = mock(ZoneGroupConfiguration.class); var zoneGroupConfigurationMock = mock(ZoneGroupConfiguration.class);
when(zoneGroupConfigurationMock.hasDynamicSource()).thenReturn(false); when(zoneGroupConfigurationMock.hasDynamicSource()).thenReturn(false);
cfg.setZoneGroups(List.of(zoneGroupConfigurationMock)); cfg.setZoneGroups(Map.of("someGroupName", zoneGroupConfigurationMock));
cfg.setScheduledUpdateInterval(60); cfg.setScheduledUpdateInterval(60);
assertThat(cfg.isScheduledUpdateEnabled()).isFalse(); assertThat(cfg.isScheduledUpdateEnabled()).isFalse();
} }
@ -145,7 +123,7 @@ public class GeofencingCalculatedFieldConfigurationTest {
var cfg = new GeofencingCalculatedFieldConfiguration(); var cfg = new GeofencingCalculatedFieldConfiguration();
var zoneGroupConfigurationMock = mock(ZoneGroupConfiguration.class); var zoneGroupConfigurationMock = mock(ZoneGroupConfiguration.class);
when(zoneGroupConfigurationMock.hasDynamicSource()).thenReturn(true); when(zoneGroupConfigurationMock.hasDynamicSource()).thenReturn(true);
cfg.setZoneGroups(List.of(zoneGroupConfigurationMock)); cfg.setZoneGroups(Map.of("someGroupName", zoneGroupConfigurationMock));
cfg.setScheduledUpdateInterval(60); cfg.setScheduledUpdateInterval(60);
assertThat(cfg.isScheduledUpdateEnabled()).isTrue(); assertThat(cfg.isScheduledUpdateEnabled()).isTrue();
} }
@ -154,7 +132,7 @@ public class GeofencingCalculatedFieldConfigurationTest {
void testGetArgumentsOverride() { void testGetArgumentsOverride() {
var cfg = new GeofencingCalculatedFieldConfiguration(); var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setEntityCoordinates(new EntityCoordinates(ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY)); cfg.setEntityCoordinates(new EntityCoordinates(ENTITY_ID_LATITUDE_ARGUMENT_KEY, ENTITY_ID_LONGITUDE_ARGUMENT_KEY));
cfg.setZoneGroups(List.of(new ZoneGroupConfiguration("allowedZones", "perimeter", GeofencingReportStrategy.REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false))); cfg.setZoneGroups(Map.of("allowedZones", new ZoneGroupConfiguration("perimeter", GeofencingReportStrategy.REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false)));
Map<String, Argument> arguments = cfg.getArguments(); Map<String, Argument> arguments = cfg.getArguments();

View File

@ -35,21 +35,11 @@ import static org.thingsboard.server.common.data.cf.configuration.geofencing.Geo
public class ZoneGroupConfigurationTest { public class ZoneGroupConfigurationTest {
@ParameterizedTest
@ValueSource(strings = " ")
@NullAndEmptySource
void validateShouldThrowWhenNameIsNullEmptyOrBlank(String name) {
var zoneGroupConfiguration = new ZoneGroupConfiguration(name, "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
assertThatThrownBy(zoneGroupConfiguration::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Zone group name must be specified!");
}
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = {EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY, EntityCoordinates.ENTITY_ID_LONGITUDE_ARGUMENT_KEY}) @ValueSource(strings = {EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY, EntityCoordinates.ENTITY_ID_LONGITUDE_ARGUMENT_KEY})
void validateShouldThrowWhenUsedReservedEntityCoordinateNames(String name) { void validateShouldThrowWhenUsedReservedEntityCoordinateNames(String name) {
var zoneGroupConfiguration = new ZoneGroupConfiguration(name, "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
assertThatThrownBy(zoneGroupConfiguration::validate) assertThatThrownBy(() -> zoneGroupConfiguration.validate(name))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessage("Name '" + name + "' is reserved and cannot be used for zone group!"); .hasMessage("Name '" + name + "' is reserved and cannot be used for zone group!");
} }
@ -58,16 +48,16 @@ public class ZoneGroupConfigurationTest {
@ValueSource(strings = " ") @ValueSource(strings = " ")
@NullAndEmptySource @NullAndEmptySource
void validateShouldThrowWhenPerimeterKeyNameIsNullEmptyOrBlank(String perimeterKeyName) { void validateShouldThrowWhenPerimeterKeyNameIsNullEmptyOrBlank(String perimeterKeyName) {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", perimeterKeyName, REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); var zoneGroupConfiguration = new ZoneGroupConfiguration(perimeterKeyName, REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
assertThatThrownBy(zoneGroupConfiguration::validate) assertThatThrownBy(() -> zoneGroupConfiguration.validate("allowedZonesGroup"))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessage("Perimeter key name must be specified for 'allowedZonesGroup' zone group!"); .hasMessage("Perimeter key name must be specified for 'allowedZonesGroup' zone group!");
} }
@Test @Test
void validateShouldThrowWhenReportStrategyIsNull() { void validateShouldThrowWhenReportStrategyIsNull() {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", "perimeter", null, false); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", null, false);
assertThatThrownBy(zoneGroupConfiguration::validate) assertThatThrownBy(() -> zoneGroupConfiguration.validate("allowedZonesGroup"))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessage("Report strategy must be specified for 'allowedZonesGroup' zone group!"); .hasMessage("Report strategy must be specified for 'allowedZonesGroup' zone group!");
} }
@ -76,40 +66,40 @@ public class ZoneGroupConfigurationTest {
@ValueSource(strings = " ") @ValueSource(strings = " ")
@NullAndEmptySource @NullAndEmptySource
void validateShouldThrowWhenRelationCreationEnabledAndRelationTypeIsNullEmptyOrBlank(String relationType) { void validateShouldThrowWhenRelationCreationEnabledAndRelationTypeIsNullEmptyOrBlank(String relationType) {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, true); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, true);
zoneGroupConfiguration.setRelationType(relationType); zoneGroupConfiguration.setRelationType(relationType);
assertThatThrownBy(zoneGroupConfiguration::validate) assertThatThrownBy(() -> zoneGroupConfiguration.validate("allowedZonesGroup"))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessage("Relation type must be specified for 'allowedZonesGroup' zone group!"); .hasMessage("Relation type must be specified for 'allowedZonesGroup' zone group!");
} }
@Test @Test
void validateShouldThrowWhenRelationCreationEnabledAndDirectionIsNull() { void validateShouldThrowWhenRelationCreationEnabledAndDirectionIsNull() {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, true); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, true);
zoneGroupConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE); zoneGroupConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE);
zoneGroupConfiguration.setDirection(null); zoneGroupConfiguration.setDirection(null);
assertThatThrownBy(zoneGroupConfiguration::validate) assertThatThrownBy(() -> zoneGroupConfiguration.validate("allowedZonesGroup"))
.isInstanceOf(IllegalArgumentException.class) .isInstanceOf(IllegalArgumentException.class)
.hasMessage("Relation direction must be specified for 'allowedZonesGroup' zone group!"); .hasMessage("Relation direction must be specified for 'allowedZonesGroup' zone group!");
} }
@Test @Test
void validateShouldDoesNotThrowAnyExceptionWhenRelationCreationDisabledAndConfigValid() { void validateShouldDoesNotThrowAnyExceptionWhenRelationCreationDisabledAndConfigValid() {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
assertThatCode(zoneGroupConfiguration::validate).doesNotThrowAnyException(); assertThatCode(() -> zoneGroupConfiguration.validate("allowedZonesGroup")).doesNotThrowAnyException();
} }
@Test @Test
void validateShouldDoesNotThrowAnyExceptionWhenRelationCreationEnabledAndConfigValid() { void validateShouldDoesNotThrowAnyExceptionWhenRelationCreationEnabledAndConfigValid() {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, true); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, true);
zoneGroupConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE); zoneGroupConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE);
zoneGroupConfiguration.setDirection(EntitySearchDirection.TO); zoneGroupConfiguration.setDirection(EntitySearchDirection.TO);
assertThatCode(zoneGroupConfiguration::validate).doesNotThrowAnyException(); assertThatCode(() -> zoneGroupConfiguration.validate("allowedZonesGroup")).doesNotThrowAnyException();
} }
@Test @Test
void whenHasDynamicSourceCalled_shouldReturnTrueIfDynamicSourceConfigurationIsNotNull() { void whenHasDynamicSourceCalled_shouldReturnTrueIfDynamicSourceConfigurationIsNotNull() {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
zoneGroupConfiguration.setRefDynamicSourceConfiguration(new RelationQueryDynamicSourceConfiguration()); zoneGroupConfiguration.setRefDynamicSourceConfiguration(new RelationQueryDynamicSourceConfiguration());
assertThat(zoneGroupConfiguration.hasDynamicSource()).isTrue(); assertThat(zoneGroupConfiguration.hasDynamicSource()).isTrue();
} }
@ -124,7 +114,7 @@ public class ZoneGroupConfigurationTest {
@Test @Test
void validateToArgumentsMethodCallWithoutRefEntityId() { void validateToArgumentsMethodCallWithoutRefEntityId() {
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowedZonesGroup", "perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); var zoneGroupConfiguration = new ZoneGroupConfiguration("perimeter", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
Argument zoneGroupArgument = zoneGroupConfiguration.toArgument(); Argument zoneGroupArgument = zoneGroupConfiguration.toArgument();
assertThat(zoneGroupArgument).isNotNull(); assertThat(zoneGroupArgument).isNotNull();
assertThat(zoneGroupArgument.getRefEntityKey()).isEqualTo(new ReferencedEntityKey("perimeter", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE)); assertThat(zoneGroupArgument.getRefEntityKey()).isEqualTo(new ReferencedEntityKey("perimeter", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));

View File

@ -44,7 +44,6 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import java.util.List;
import java.util.Map; import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -112,9 +111,9 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
cfg.setEntityCoordinates(entityCoordinates); cfg.setEntityCoordinates(entityCoordinates);
// Zone-group argument (ATTRIBUTE) no DYNAMIC configuration, so no scheduling even if the scheduled interval is set // Zone-group argument (ATTRIBUTE) no DYNAMIC configuration, so no scheduling even if the scheduled interval is set
ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration("allowed", "allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration("allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
zoneGroupConfiguration.setRefEntityId(device.getId()); zoneGroupConfiguration.setRefEntityId(device.getId());
cfg.setZoneGroups(List.of(zoneGroupConfiguration)); cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
// Set a scheduled interval to some value // Set a scheduled interval to some value
cfg.setScheduledUpdateInterval(600); cfg.setScheduledUpdateInterval(600);
@ -158,13 +157,13 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
cfg.setEntityCoordinates(entityCoordinates); cfg.setEntityCoordinates(entityCoordinates);
// Zone-group argument (ATTRIBUTE) make it DYNAMIC so scheduling is enabled // Zone-group argument (ATTRIBUTE) make it DYNAMIC so scheduling is enabled
ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration("allowed", "allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration("allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
dynamicSourceConfiguration.setMaxLevel(1); dynamicSourceConfiguration.setMaxLevel(1);
dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE); dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE);
zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration); zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration);
cfg.setZoneGroups(List.of(zoneGroupConfiguration)); cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
// Enable scheduling with an interval below tenant min // Enable scheduling with an interval below tenant min
cfg.setScheduledUpdateInterval(600); cfg.setScheduledUpdateInterval(600);
@ -198,13 +197,13 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
cfg.setEntityCoordinates(entityCoordinates); cfg.setEntityCoordinates(entityCoordinates);
// Zone-group argument (ATTRIBUTE) make it DYNAMIC so scheduling is enabled // Zone-group argument (ATTRIBUTE) make it DYNAMIC so scheduling is enabled
ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration("allowed", "allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration( "allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
dynamicSourceConfiguration.setMaxLevel(Integer.MAX_VALUE); dynamicSourceConfiguration.setMaxLevel(Integer.MAX_VALUE);
dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE); dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE);
zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration); zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration);
cfg.setZoneGroups(List.of(zoneGroupConfiguration)); cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
// Create & save Calculated Field // Create & save Calculated Field
CalculatedField cf = new CalculatedField(); CalculatedField cf = new CalculatedField();
@ -234,13 +233,13 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
cfg.setEntityCoordinates(entityCoordinates); cfg.setEntityCoordinates(entityCoordinates);
// Zone-group argument (ATTRIBUTE) make it DYNAMIC so scheduling is enabled // Zone-group argument (ATTRIBUTE) make it DYNAMIC so scheduling is enabled
ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration("allowed", "allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration zoneGroupConfiguration = new ZoneGroupConfiguration( "allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
dynamicSourceConfiguration.setMaxLevel(1); dynamicSourceConfiguration.setMaxLevel(1);
dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE); dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE);
zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration); zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration);
cfg.setZoneGroups(List.of(zoneGroupConfiguration)); cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
// Get tenant profile min. // Get tenant profile min.
int min = tbTenantProfileCache.get(tenantId) int min = tbTenantProfileCache.get(tenantId)

View File

@ -54,7 +54,6 @@ import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; import org.thingsboard.server.msa.ui.utils.EntityPrototypes;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -366,7 +365,7 @@ public class CalculatedFieldTest extends AbstractContainerTest {
cfg.setEntityCoordinates(entityCoordinates); cfg.setEntityCoordinates(entityCoordinates);
// Dynamic groups via relations // Dynamic groups via relations
ZoneGroupConfiguration allowedZoneGroupConfiguration = new ZoneGroupConfiguration("allowedZones", "zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration allowedZoneGroupConfiguration = new ZoneGroupConfiguration("zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var allowedDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var allowedDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
allowedDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); allowedDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
allowedDynamicSourceConfiguration.setMaxLevel(1); allowedDynamicSourceConfiguration.setMaxLevel(1);
@ -374,7 +373,7 @@ public class CalculatedFieldTest extends AbstractContainerTest {
allowedDynamicSourceConfiguration.setRelationType("AllowedZone"); allowedDynamicSourceConfiguration.setRelationType("AllowedZone");
allowedZoneGroupConfiguration.setRefDynamicSourceConfiguration(allowedDynamicSourceConfiguration); allowedZoneGroupConfiguration.setRefDynamicSourceConfiguration(allowedDynamicSourceConfiguration);
ZoneGroupConfiguration restrictedZoneGroupConfiguration = new ZoneGroupConfiguration("restrictedZones", "zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false); ZoneGroupConfiguration restrictedZoneGroupConfiguration = new ZoneGroupConfiguration("zone", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var restrictedDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); var restrictedDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
restrictedDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); restrictedDynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM);
restrictedDynamicSourceConfiguration.setMaxLevel(1); restrictedDynamicSourceConfiguration.setMaxLevel(1);
@ -382,7 +381,7 @@ public class CalculatedFieldTest extends AbstractContainerTest {
restrictedDynamicSourceConfiguration.setRelationType("RestrictedZone"); restrictedDynamicSourceConfiguration.setRelationType("RestrictedZone");
restrictedZoneGroupConfiguration.setRefDynamicSourceConfiguration(restrictedDynamicSourceConfiguration); restrictedZoneGroupConfiguration.setRefDynamicSourceConfiguration(restrictedDynamicSourceConfiguration);
cfg.setZoneGroups(List.of(allowedZoneGroupConfiguration, restrictedZoneGroupConfiguration)); cfg.setZoneGroups(Map.of("allowedZones", allowedZoneGroupConfiguration, "restrictedZones", restrictedZoneGroupConfiguration));
Output out = new Output(); Output out = new Output();
out.setType(OutputType.ATTRIBUTES); out.setType(OutputType.ATTRIBUTES);